explorer fixes
This commit is contained in:
parent
4b2a7f8abc
commit
94cb6e2b80
4 changed files with 84 additions and 55 deletions
|
@ -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" />
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
Loading…
Reference in a new issue