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 <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> 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 <meta-data
android:name="io.flutter.embedding.android.EnableImpeller" android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" /> android:value="false" />

View file

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

View file

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

View file

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