explorer fixes
This commit is contained in:
parent
ace841212e
commit
218846117c
3 changed files with 121 additions and 83 deletions
|
@ -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!,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue