diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 5f596ade3..a83c2ec03 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -323,8 +323,10 @@
-
+
diff --git a/lib/widgets/explorer/app_bar.dart b/lib/widgets/explorer/app_bar.dart
index 2f5b86236..cdc0666c2 100644
--- a/lib/widgets/explorer/app_bar.dart
+++ b/lib/widgets/explorer/app_bar.dart
@@ -26,7 +26,7 @@ import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class ExplorerAppBar extends StatefulWidget {
- final ValueNotifier directoryNotifier;
+ final ValueNotifier directoryNotifier;
final void Function(String path) goTo;
const ExplorerAppBar({
@@ -67,7 +67,7 @@ class _ExplorerAppBarState extends State with WidgetsBindingObse
return SizedBox(
width: constraints.maxWidth,
height: CrumbLine.getPreferredHeight(MediaQuery.textScalerOf(context)),
- child: ValueListenableBuilder(
+ child: ValueListenableBuilder(
valueListenable: widget.directoryNotifier,
builder: (context, directory, child) {
return CrumbLine(
@@ -128,7 +128,9 @@ class _ExplorerAppBarState extends State 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 with WidgetsBindingObse
Widget _buildVolumeSelector(BuildContext context) {
if (_volumes.length == 2) {
- return ValueListenableBuilder(
+ return ValueListenableBuilder(
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 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 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(
context: context,
diff --git a/lib/widgets/explorer/explorer_page.dart b/lib/widgets/explorer/explorer_page.dart
index bd7c801fc..859a70f72 100644
--- a/lib/widgets/explorer/explorer_page.dart
+++ b/lib/widgets/explorer/explorer_page.dart
@@ -41,15 +41,13 @@ class ExplorerPage extends StatefulWidget {
class _ExplorerPageState extends State {
final List _subscriptions = [];
- final ValueNotifier _directory = ValueNotifier(const VolumeRelativeDirectory(volumePath: '', relativeDir: ''));
+ final ValueNotifier _directory = ValueNotifier(null);
+ final ValueNotifier _contentsDirectory = ValueNotifier(null);
final ValueNotifier> _contents = ValueNotifier([]);
Set 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 {
@override
Widget build(BuildContext context) {
- return ValueListenableBuilder(
+ return ValueListenableBuilder(
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 {
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 {
),
),
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(
+ 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 {
if (loading) {
bottom = const CircularProgressIndicator();
} else {
- final source = context.read();
- 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();
+ 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 {
}
void _updateContents() {
- final contents = [];
+ final directory = _directory.value;
+ final dirPath = _pathOf(directory);
+ if (dirPath == null) return;
+ final contents = [];
final source = context.read();
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 {
final nameB = pContext.split(b.path).last;
return compareAsciiUpperCaseNatural(nameA, nameB);
});
+ _contentsDirectory.value = directory;
});
}
diff --git a/lib/widgets/settings/privacy/file_picker/crumb_line.dart b/lib/widgets/settings/privacy/file_picker/crumb_line.dart
index d334807cc..6ba442830 100644
--- a/lib/widgets/settings/privacy/file_picker/crumb_line.dart
+++ b/lib/widgets/settings/privacy/file_picker/crumb_line.dart
@@ -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 {
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 {
@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().animate;
@@ -56,10 +56,15 @@ class _CrumbLineState extends State {
@override
Widget build(BuildContext context) {
- List parts = [
- directory.getVolumeDescription(context),
- ...pContext.split(directory.relativeDir),
- ];
+ final _directory = directory;
+ final parts = [];
+ 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 {
);
}
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`