explorer fixes

This commit is contained in:
Thibault Deckers 2024-06-24 20:53:12 +02:00
parent ace841212e
commit 218846117c
3 changed files with 121 additions and 83 deletions

View file

@ -42,14 +42,15 @@ class EmptyContent extends StatelessWidget {
), ),
const SizedBox(height: 16) const SizedBox(height: 16)
], ],
Text( if (text.isNotEmpty)
text, Text(
style: TextStyle( text,
color: color, style: TextStyle(
fontSize: fontSize, color: color,
fontSize: fontSize,
),
textAlign: TextAlign.center,
), ),
textAlign: TextAlign.center,
),
if (bottom != null) bottom!, if (bottom != null) bottom!,
], ],
), ),

View file

@ -17,6 +17,9 @@ import 'package:aves/widgets/common/app_bar/app_bar_title.dart';
import 'package:aves/widgets/common/basic/font_size_icon_theme.dart'; import 'package:aves/widgets/common/basic/font_size_icon_theme.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart'; import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/basic/scaffold.dart';
import 'package:aves/widgets/common/behaviour/pop/double_back.dart';
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/identity/empty.dart';
@ -41,80 +44,98 @@ class ExplorerPage extends StatefulWidget {
} }
class _ExplorerPageState extends State<ExplorerPage> { class _ExplorerPageState extends State<ExplorerPage> {
late VolumeRelativeDirectory _directory; final ValueNotifier<VolumeRelativeDirectory> _directory = ValueNotifier(const VolumeRelativeDirectory(volumePath: '', relativeDir: ''));
List<Directory>? _contents; final ValueNotifier<List<Directory>> _contents = ValueNotifier([]);
final DoubleBackPopHandler _doubleBackPopHandler = DoubleBackPopHandler();
Set<StorageVolume> get volumes => androidFileUtils.storageVolumes; Set<StorageVolume> get _volumes => androidFileUtils.storageVolumes;
String get currentDirectoryPath => pContext.join(_directory.volumePath, _directory.relativeDir); String get _currentDirectoryPath {
final dir = _directory.value;
return pContext.join(dir.volumePath, dir.relativeDir);
}
static const double _crumblineHeight = kMinInteractiveDimension;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final primaryVolume = volumes.firstWhereOrNull((v) => v.isPrimary); final primaryVolume = _volumes.firstWhereOrNull((v) => v.isPrimary);
if (primaryVolume != null) { if (primaryVolume != null) {
_goTo(primaryVolume.path); _goTo(primaryVolume.path);
} }
} }
@override @override
Widget build(BuildContext context) { void dispose() {
final l10n = context.l10n; _doubleBackPopHandler.dispose();
final visibleContents = _contents?.where((v) { super.dispose();
final isHidden = pContext.split(v.path).last.startsWith('.'); }
return !isHidden;
}).toList();
return PopScope(
canPop: _directory.relativeDir.isEmpty,
onPopInvoked: (didPop) {
if (didPop) return;
final parent = pContext.dirname(currentDirectoryPath); @override
_goTo(parent); Widget build(BuildContext context) {
setState(() {}); return AvesPopScope(
}, handlers: [
(context) {
if (_directory.value.relativeDir.isNotEmpty) {
final parent = pContext.dirname(_currentDirectoryPath);
_goTo(parent);
return false;
}
return true;
},
TvNavigationPopHandler.pop,
_doubleBackPopHandler.pop,
],
child: AvesScaffold( child: AvesScaffold(
appBar: _buildAppBar(context), appBar: _buildAppBar(context),
drawer: const AppDrawer(), drawer: const AppDrawer(),
body: SafeArea( body: SafeArea(
child: Column( child: Column(
children: [ children: [
SizedBox(
height: kMinInteractiveDimension,
child: CrumbLine(
directory: _directory,
onTap: (path) {
_goTo(path);
setState(() {});
},
),
),
const Divider(height: 0),
Expanded( Expanded(
child: visibleContents == null child: ValueListenableBuilder<List<Directory>>(
? const SizedBox() valueListenable: _contents,
: visibleContents.isEmpty builder: (context, contents, child) {
? Center( if (contents.isEmpty) {
child: EmptyContent( final source = context.read<CollectionSource>();
icon: AIcons.folder, final album = _getAlbumPath(source, Directory(_currentDirectoryPath));
text: l10n.filePickerNoItems, return Center(
), child: EmptyContent(
) icon: AIcons.folder,
: ListView.builder( text: '',
itemCount: visibleContents.length, bottom: album != null
itemBuilder: (context, index) { ? AvesFilterChip(
return index < visibleContents.length ? _buildContentLine(context, visibleContents[index]) : const SizedBox(); filter: AlbumFilter(album, source.getAlbumDisplayName(context, album)),
}, maxWidth: double.infinity,
onTap: (filter) => _goToCollectionPage(context, filter),
onLongPress: null,
)
: null,
), ),
);
}
return ListView.builder(
itemCount: contents.length,
itemBuilder: (context, index) {
return index < contents.length ? _buildContentLine(context, contents[index]) : const SizedBox();
},
);
}),
), ),
const Divider(height: 0), const Divider(height: 0),
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: AvesFilterChip( child: ValueListenableBuilder<VolumeRelativeDirectory>(
filter: PathFilter(currentDirectoryPath), valueListenable: _directory,
maxWidth: double.infinity, builder: (context, directory, child) {
onTap: (filter) => _goToCollectionPage(context, filter), return AvesFilterChip(
onLongPress: null, filter: PathFilter(_currentDirectoryPath),
maxWidth: double.infinity,
onTap: (filter) => _goToCollectionPage(context, filter),
onLongPress: null,
);
},
), ),
), ),
], ],
@ -138,12 +159,12 @@ class _ExplorerPageState extends State<ExplorerPage> {
), ),
), ),
actions: [ actions: [
if (volumes.length > 1) if (_volumes.length > 1)
FontSizeIconTheme( FontSizeIconTheme(
child: PopupMenuButton<StorageVolume>( child: PopupMenuButton<StorageVolume>(
itemBuilder: (context) { itemBuilder: (context) {
return volumes.map((v) { return _volumes.map((v) {
final selected = _directory.volumePath == v.path; final selected = _directory.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,
@ -162,12 +183,26 @@ class _ExplorerPageState extends State<ExplorerPage> {
Navigator.maybeOf(context)?.pop(); Navigator.maybeOf(context)?.pop();
await Future.delayed(ADurations.drawerTransitionAnimation); await Future.delayed(ADurations.drawerTransitionAnimation);
_goTo(volume.path); _goTo(volume.path);
setState(() {});
}, },
popUpAnimationStyle: animations.popUpAnimationStyle, popUpAnimationStyle: animations.popUpAnimationStyle,
), ),
), ),
], ],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(_crumblineHeight),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: _crumblineHeight),
child: ValueListenableBuilder<VolumeRelativeDirectory>(
valueListenable: _directory,
builder: (context, directory, child) {
return CrumbLine(
directory: directory,
onTap: _goTo,
);
},
),
),
),
); );
} }
@ -196,21 +231,17 @@ class _ExplorerPageState extends State<ExplorerPage> {
), ),
) )
: null, : null,
onTap: () { onTap: () => _goTo(content.path),
_goTo(content.path);
setState(() {});
},
); );
} }
void _goTo(String path) { void _goTo(String path) {
_directory = androidFileUtils.relativeDirectoryFromPath(path)!; _directory.value = androidFileUtils.relativeDirectoryFromPath(path)!;
_contents = null;
final contents = <Directory>[]; 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(_currentDirectoryPath).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();
@ -219,8 +250,12 @@ class _ExplorerPageState extends State<ExplorerPage> {
} }
} }
}, onDone: () { }, onDone: () {
_contents = contents..sort((a, b) => compareAsciiUpperCaseNatural(pContext.split(a.path).last, pContext.split(b.path).last)); _contents.value = contents
setState(() {}); ..sort((a, b) {
final nameA = pContext.split(a.path).last;
final nameB = pContext.split(b.path).last;
return compareAsciiUpperCaseNatural(nameA, nameB);
});
}); });
} }

View file

@ -101,20 +101,22 @@ class _HiddenFilters extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: LayoutBuilder(builder: (context, constraints) { child: LayoutBuilder(
return Row( builder: (context, constraints) {
children: [ return Row(
AvesFilterChip( children: [
filter: filter, AvesFilterChip(
maxWidth: constraints.maxWidth, filter: filter,
onTap: onRemove, maxWidth: constraints.maxWidth,
onRemove: onRemove, onTap: onRemove,
onLongPress: null, onRemove: onRemove,
), onLongPress: null,
const Spacer(), ),
], const Spacer(),
); ],
}), );
},
),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Switch( Switch(