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
|
<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" />
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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`
|
||||||
|
|
Loading…
Reference in a new issue