explorer fixes

This commit is contained in:
Thibault Deckers 2024-08-07 20:05:47 +02:00
parent 4b2a7f8abc
commit 94cb6e2b80
4 changed files with 84 additions and 55 deletions

View file

@ -323,8 +323,10 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- as of Flutter v3.22.0 (stable),
Impeller fails to render videos (via `ffmpegkit`), and has random glitches -->
<!--
Impeller is not supported by `media_kit` v1.1.10+1:
https://github.com/media-kit/media-kit/issues/707
-->
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />

View file

@ -26,7 +26,7 @@ import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class ExplorerAppBar extends StatefulWidget {
final ValueNotifier<VolumeRelativeDirectory> directoryNotifier;
final ValueNotifier<VolumeRelativeDirectory?> directoryNotifier;
final void Function(String path) goTo;
const ExplorerAppBar({
@ -67,7 +67,7 @@ class _ExplorerAppBarState extends State<ExplorerAppBar> with WidgetsBindingObse
return SizedBox(
width: constraints.maxWidth,
height: CrumbLine.getPreferredHeight(MediaQuery.textScalerOf(context)),
child: ValueListenableBuilder<VolumeRelativeDirectory>(
child: ValueListenableBuilder<VolumeRelativeDirectory?>(
valueListenable: widget.directoryNotifier,
builder: (context, directory, child) {
return CrumbLine(
@ -128,7 +128,9 @@ class _ExplorerAppBarState extends State<ExplorerAppBar> with WidgetsBindingObse
// wait for the popup menu to hide before proceeding with the action
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
final directory = widget.directoryNotifier.value;
ExplorerActionDelegate(directory: directory).onActionSelected(context, action);
if (directory != null) {
ExplorerActionDelegate(directory: directory).onActionSelected(context, action);
}
},
popUpAnimationStyle: animations.popUpAnimationStyle,
),
@ -137,10 +139,10 @@ class _ExplorerAppBarState extends State<ExplorerAppBar> with WidgetsBindingObse
Widget _buildVolumeSelector(BuildContext context) {
if (_volumes.length == 2) {
return ValueListenableBuilder<VolumeRelativeDirectory>(
return ValueListenableBuilder<VolumeRelativeDirectory?>(
valueListenable: widget.directoryNotifier,
builder: (context, directory, child) {
final currentVolume = directory.volumePath;
final currentVolume = directory?.volumePath;
final otherVolume = _volumes.firstWhere((volume) => volume.path != currentVolume);
final icon = otherVolume.isRemovable ? AIcons.storageCard : AIcons.storageMain;
return IconButton(
@ -155,7 +157,7 @@ class _ExplorerAppBarState extends State<ExplorerAppBar> with WidgetsBindingObse
icon: const Icon(AIcons.storageCard),
onPressed: () async {
_volumes.map((v) {
final selected = widget.directoryNotifier.value.volumePath == v.path;
final selected = widget.directoryNotifier.value?.volumePath == v.path;
final icon = v.isRemovable ? AIcons.storageCard : AIcons.storageMain;
return PopupMenuItem(
value: v,
@ -166,7 +168,7 @@ class _ExplorerAppBarState extends State<ExplorerAppBar> with WidgetsBindingObse
),
);
}).toList();
final volumePath = widget.directoryNotifier.value.volumePath;
final volumePath = widget.directoryNotifier.value?.volumePath;
final initialVolume = _volumes.firstWhereOrNull((v) => v.path == volumePath);
final volume = await showDialog<StorageVolume?>(
context: context,

View file

@ -41,15 +41,13 @@ class ExplorerPage extends StatefulWidget {
class _ExplorerPageState extends State<ExplorerPage> {
final List<StreamSubscription> _subscriptions = [];
final ValueNotifier<VolumeRelativeDirectory> _directory = ValueNotifier(const VolumeRelativeDirectory(volumePath: '', relativeDir: ''));
final ValueNotifier<VolumeRelativeDirectory?> _directory = ValueNotifier(null);
final ValueNotifier<VolumeRelativeDirectory?> _contentsDirectory = ValueNotifier(null);
final ValueNotifier<List<Directory>> _contents = ValueNotifier([]);
Set<StorageVolume> get _volumes => androidFileUtils.storageVolumes;
String get _currentDirectoryPath {
final dir = _directory.value;
return pContext.join(dir.volumePath, dir.relativeDir);
}
String? _pathOf(VolumeRelativeDirectory? dir) => dir != null ? pContext.join(dir.volumePath, dir.relativeDir) : null;
@override
void initState() {
@ -82,15 +80,20 @@ class _ExplorerPageState extends State<ExplorerPage> {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<VolumeRelativeDirectory>(
return ValueListenableBuilder<VolumeRelativeDirectory?>(
valueListenable: _directory,
builder: (context, directory, child) {
final atRoot = directory.relativeDir.isEmpty;
final atRoot = directory?.relativeDir.isEmpty ?? true;
return AvesPopScope(
handlers: [
APopHandler(
canPop: (context) => atRoot,
onPopBlocked: (context) => _goTo(pContext.dirname(_currentDirectoryPath)),
onPopBlocked: (context) {
final path = _pathOf(directory);
if (path != null) {
_goTo(pContext.dirname(path));
}
},
),
tvNavigationPopHandler,
doubleBackPopHandler,
@ -118,7 +121,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
AnimationLimiter(
// animation limiter should not be above the app bar
// so that the crumb line can automatically scroll
key: ValueKey(_currentDirectoryPath),
key: ValueKey(contents),
child: SliverList.builder(
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
@ -147,18 +150,26 @@ class _ExplorerPageState extends State<ExplorerPage> {
),
),
const Divider(height: 0),
SafeArea(
top: false,
bottom: true,
child: Padding(
padding: const EdgeInsets.all(8),
child: AvesFilterChip(
filter: PathFilter(_currentDirectoryPath),
maxWidth: double.infinity,
onTap: (filter) => _goToCollectionPage(context, filter),
onLongPress: null,
),
),
ValueListenableBuilder<VolumeRelativeDirectory?>(
valueListenable: _contentsDirectory,
builder: (context, contentsDirectory, child) {
final dirPath = _pathOf(contentsDirectory);
return dirPath != null
? SafeArea(
top: false,
bottom: true,
child: Padding(
padding: const EdgeInsets.all(8),
child: AvesFilterChip(
filter: PathFilter(dirPath),
maxWidth: double.infinity,
onTap: (filter) => _goToCollectionPage(context, filter),
onLongPress: null,
),
),
)
: const SizedBox();
},
),
],
),
@ -177,15 +188,18 @@ class _ExplorerPageState extends State<ExplorerPage> {
if (loading) {
bottom = const CircularProgressIndicator();
} else {
final source = context.read<CollectionSource>();
final album = _getAlbumPath(source, Directory(_currentDirectoryPath));
if (album != null) {
bottom = AvesFilterChip(
filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
maxWidth: double.infinity,
onTap: (filter) => _goToCollectionPage(context, filter),
onLongPress: null,
);
final dirPath = _pathOf(_contentsDirectory.value);
if (dirPath != null) {
final source = context.read<CollectionSource>();
final album = _getAlbumPath(source, Directory(dirPath));
if (album != null) {
bottom = AvesFilterChip(
filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
maxWidth: double.infinity,
onTap: (filter) => _goToCollectionPage(context, filter),
onLongPress: null,
);
}
}
}
@ -249,11 +263,14 @@ class _ExplorerPageState extends State<ExplorerPage> {
}
void _updateContents() {
final contents = <Directory>[];
final directory = _directory.value;
final dirPath = _pathOf(directory);
if (dirPath == null) return;
final contents = <Directory>[];
final source = context.read<CollectionSource>();
final albums = source.rawAlbums.map((v) => v.toLowerCase()).toSet();
Directory(_currentDirectoryPath).list().listen((event) {
Directory(dirPath).list().listen((event) {
final entity = event.absolute;
if (entity is Directory) {
final dirPath = entity.path.toLowerCase();
@ -268,6 +285,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
final nameB = pContext.split(b.path).last;
return compareAsciiUpperCaseNatural(nameA, nameB);
});
_contentsDirectory.value = directory;
});
}

View file

@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CrumbLine extends StatefulWidget {
final VolumeRelativeDirectory directory;
final VolumeRelativeDirectory? directory;
final void Function(String path) onTap;
const CrumbLine({
@ -25,7 +25,7 @@ class CrumbLine extends StatefulWidget {
class _CrumbLineState extends State<CrumbLine> {
final ScrollController _scrollController = ScrollController();
VolumeRelativeDirectory get directory => widget.directory;
VolumeRelativeDirectory? get directory => widget.directory;
@override
void dispose() {
@ -36,7 +36,7 @@ class _CrumbLineState extends State<CrumbLine> {
@override
void didUpdateWidget(covariant CrumbLine oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.directory.relativeDir.length < widget.directory.relativeDir.length) {
if ((oldWidget.directory?.relativeDir.length ?? 0) < (widget.directory?.relativeDir.length ?? 0)) {
// scroll to show last crumb
WidgetsBinding.instance.addPostFrameCallback((_) {
final animate = context.read<Settings>().animate;
@ -56,10 +56,15 @@ class _CrumbLineState extends State<CrumbLine> {
@override
Widget build(BuildContext context) {
List<String> parts = [
directory.getVolumeDescription(context),
...pContext.split(directory.relativeDir),
];
final _directory = directory;
final parts = <String>[];
if (_directory != null) {
parts.addAll([
_directory.getVolumeDescription(context),
...pContext.split(_directory.relativeDir),
]);
}
final crumbColor = DefaultTextStyle.of(context).style.color;
return ListView.builder(
scrollDirection: Axis.horizontal,
@ -84,13 +89,15 @@ class _CrumbLineState extends State<CrumbLine> {
);
}
return GestureDetector(
onTap: () {
final path = pContext.joinAll([
directory.volumePath,
...parts.skip(1).take(index),
]);
widget.onTap(path);
},
onTap: _directory != null
? () {
final path = pContext.joinAll([
_directory.volumePath,
...parts.skip(1).take(index),
]);
widget.onTap(path);
}
: null,
child: Container(
// use a `Container` with a dummy color to make it expand
// so that we can also detect taps around the title `Text`