explorer: page review
This commit is contained in:
parent
b51769e2c6
commit
a5c5d5bad6
3 changed files with 255 additions and 141 deletions
151
lib/widgets/explorer/app_bar.dart
Normal file
151
lib/widgets/explorer/app_bar.dart
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:aves/app_mode.dart';
|
||||||
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/theme/themes.dart';
|
||||||
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
|
import 'package:aves/view/view.dart';
|
||||||
|
import 'package:aves/widgets/common/app_bar/app_bar_subtitle.dart';
|
||||||
|
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/popup/menu_row.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/aves_app_bar.dart';
|
||||||
|
import 'package:aves/widgets/common/search/route.dart';
|
||||||
|
import 'package:aves/widgets/search/search_delegate.dart';
|
||||||
|
import 'package:aves/widgets/settings/privacy/file_picker/crumb_line.dart';
|
||||||
|
import 'package:aves_model/aves_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class ExplorerAppBar extends StatefulWidget {
|
||||||
|
final ValueNotifier<VolumeRelativeDirectory> directoryNotifier;
|
||||||
|
final void Function(String path) goTo;
|
||||||
|
|
||||||
|
const ExplorerAppBar({
|
||||||
|
super.key,
|
||||||
|
required this.directoryNotifier,
|
||||||
|
required this.goTo,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ExplorerAppBar> createState() => _ExplorerAppBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExplorerAppBarState extends State<ExplorerAppBar> with WidgetsBindingObserver {
|
||||||
|
Set<StorageVolume> get _volumes => androidFileUtils.storageVolumes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
|
||||||
|
return AvesAppBar(
|
||||||
|
contentHeight: appBarContentHeight,
|
||||||
|
pinned: true,
|
||||||
|
leading: const DrawerButton(),
|
||||||
|
title: _buildAppBarTitle(context),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(AIcons.search),
|
||||||
|
onPressed: () => _goToSearch(context),
|
||||||
|
tooltip: MaterialLocalizations.of(context).searchFieldLabel,
|
||||||
|
),
|
||||||
|
if (_volumes.length > 1)
|
||||||
|
FontSizeIconTheme(
|
||||||
|
child: PopupMenuButton<StorageVolume>(
|
||||||
|
itemBuilder: (context) {
|
||||||
|
return _volumes.map((v) {
|
||||||
|
final selected = widget.directoryNotifier.value.volumePath == v.path;
|
||||||
|
final icon = v.isRemovable ? AIcons.storageCard : AIcons.storageMain;
|
||||||
|
return PopupMenuItem(
|
||||||
|
value: v,
|
||||||
|
enabled: !selected,
|
||||||
|
child: MenuRow(
|
||||||
|
text: v.getDescription(context),
|
||||||
|
icon: Icon(icon),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
onSelected: (volume) async {
|
||||||
|
// wait for the popup menu to hide before proceeding with the action
|
||||||
|
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
|
||||||
|
widget.goTo(volume.path);
|
||||||
|
},
|
||||||
|
popUpAnimationStyle: animations.popUpAnimationStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
bottom: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return SizedBox(
|
||||||
|
width: constraints.maxWidth,
|
||||||
|
height: CrumbLine.getPreferredHeight(MediaQuery.textScalerOf(context)),
|
||||||
|
child: ValueListenableBuilder<VolumeRelativeDirectory>(
|
||||||
|
valueListenable: widget.directoryNotifier,
|
||||||
|
builder: (context, directory, child) {
|
||||||
|
return CrumbLine(
|
||||||
|
key: const Key('crumbs'),
|
||||||
|
directory: directory,
|
||||||
|
onTap: widget.goTo,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractiveAppBarTitle _buildAppBarTitle(BuildContext context) {
|
||||||
|
final appMode = context.watch<ValueNotifier<AppMode>>().value;
|
||||||
|
Widget title = Text(
|
||||||
|
context.l10n.explorerPageTitle,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
maxLines: 1,
|
||||||
|
);
|
||||||
|
if (appMode == AppMode.main) {
|
||||||
|
title = SourceStateAwareAppBarTitle(
|
||||||
|
title: title,
|
||||||
|
source: context.read<CollectionSource>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return InteractiveAppBarTitle(
|
||||||
|
onTap: () => _goToSearch(context),
|
||||||
|
child: title,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
double get appBarContentHeight {
|
||||||
|
final textScaler = MediaQuery.textScalerOf(context);
|
||||||
|
return textScaler.scale(kToolbarHeight) + CrumbLine.getPreferredHeight(textScaler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _goToSearch(BuildContext context) {
|
||||||
|
Navigator.maybeOf(context)?.push(
|
||||||
|
SearchPageRoute(
|
||||||
|
delegate: CollectionSearchDelegate(
|
||||||
|
searchFieldLabel: context.l10n.searchCollectionFieldHint,
|
||||||
|
searchFieldStyle: Themes.searchFieldStyle(context),
|
||||||
|
source: context.read<CollectionSource>(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,31 +4,22 @@ import 'dart:io';
|
||||||
import 'package:aves/model/filters/album.dart';
|
import 'package:aves/model/filters/album.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/path.dart';
|
import 'package:aves/model/filters/path.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
|
||||||
import 'package:aves/model/settings/settings.dart';
|
|
||||||
import 'package:aves/model/source/album.dart';
|
import 'package:aves/model/source/album.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/view/view.dart';
|
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/app_bar/app_bar_title.dart';
|
import 'package:aves/widgets/common/basic/insets.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/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/double_back.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/pop/scope.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/behaviour/pop/tv_navigation.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';
|
||||||
import 'package:aves/widgets/common/search/route.dart';
|
import 'package:aves/widgets/explorer/app_bar.dart';
|
||||||
import 'package:aves/widgets/navigation/drawer/app_drawer.dart';
|
import 'package:aves/widgets/navigation/drawer/app_drawer.dart';
|
||||||
import 'package:aves/widgets/search/search_delegate.dart';
|
|
||||||
import 'package:aves/widgets/settings/privacy/file_picker/crumb_line.dart';
|
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -61,8 +52,6 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
return pContext.join(dir.volumePath, dir.relativeDir);
|
return pContext.join(dir.volumePath, dir.relativeDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const double _crumblineHeight = kMinInteractiveDimension;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -75,6 +64,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
_goTo(primaryVolume.path);
|
_goTo(primaryVolume.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_contents.addListener(() => PrimaryScrollController.of(context).jumpTo(0));
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
_subscriptions.add(source.eventBus.on<AlbumsChangedEvent>().listen((event) => _updateContents()));
|
_subscriptions.add(source.eventBus.on<AlbumsChangedEvent>().listen((event) => _updateContents()));
|
||||||
|
@ -86,6 +76,8 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
_subscriptions
|
_subscriptions
|
||||||
..forEach((sub) => sub.cancel())
|
..forEach((sub) => sub.cancel())
|
||||||
..clear();
|
..clear();
|
||||||
|
_directory.dispose();
|
||||||
|
_contents.dispose();
|
||||||
_doubleBackPopHandler.dispose();
|
_doubleBackPopHandler.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
@ -106,78 +98,73 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
_doubleBackPopHandler.pop,
|
_doubleBackPopHandler.pop,
|
||||||
],
|
],
|
||||||
child: AvesScaffold(
|
child: AvesScaffold(
|
||||||
appBar: _buildAppBar(context),
|
|
||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
body: SafeArea(
|
body: GestureAreaProtectorStack(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ValueListenableBuilder<List<Directory>>(
|
child: ValueListenableBuilder<List<Directory>>(
|
||||||
valueListenable: _contents,
|
valueListenable: _contents,
|
||||||
builder: (context, contents, child) {
|
builder: (context, contents, child) {
|
||||||
if (contents.isEmpty) {
|
|
||||||
return Selector<CollectionSource, bool>(
|
|
||||||
selector: (context, source) => source.state == SourceState.loading,
|
|
||||||
builder: (context, loading, child) {
|
|
||||||
Widget? bottom;
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: EmptyContent(
|
|
||||||
icon: AIcons.folder,
|
|
||||||
text: '',
|
|
||||||
bottom: bottom,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final durations = context.watch<DurationsData>();
|
final durations = context.watch<DurationsData>();
|
||||||
return AnimationLimiter(
|
return CustomScrollView(
|
||||||
key: ValueKey(_currentDirectoryPath),
|
// workaround to prevent scrolling the app bar away
|
||||||
child: ListView(
|
// when there is no content and we use `SliverFillRemaining`
|
||||||
children: AnimationConfiguration.toStaggeredList(
|
physics: contents.isEmpty ? const NeverScrollableScrollPhysics() : null,
|
||||||
duration: durations.staggeredAnimation,
|
slivers: [
|
||||||
delay: durations.staggeredAnimationDelay * timeDilation,
|
ExplorerAppBar(
|
||||||
childAnimationBuilder: (child) => SlideAnimation(
|
key: const Key('appbar'),
|
||||||
verticalOffset: 50.0,
|
directoryNotifier: _directory,
|
||||||
child: FadeInAnimation(
|
goTo: _goTo,
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
children: contents.map((v) => _buildContentLine(context, v)).toList(),
|
|
||||||
),
|
),
|
||||||
),
|
AnimationLimiter(
|
||||||
|
// animation limiter should not be above the app bar
|
||||||
|
// so that the crumb line can automatically scroll
|
||||||
|
key: ValueKey(_currentDirectoryPath),
|
||||||
|
child: SliverList.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return AnimationConfiguration.staggeredList(
|
||||||
|
position: index,
|
||||||
|
duration: durations.staggeredAnimation,
|
||||||
|
delay: durations.staggeredAnimationDelay * timeDilation,
|
||||||
|
child: SlideAnimation(
|
||||||
|
verticalOffset: 50.0,
|
||||||
|
child: FadeInAnimation(
|
||||||
|
child: _buildContentLine(context, contents[index]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: contents.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
contents.isEmpty
|
||||||
|
? SliverFillRemaining(
|
||||||
|
child: _buildEmptyContent(),
|
||||||
|
)
|
||||||
|
: const SliverPadding(padding: EdgeInsets.only(bottom: 8)),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 0),
|
const Divider(height: 0),
|
||||||
Padding(
|
SafeArea(
|
||||||
padding: const EdgeInsets.all(8),
|
top: false,
|
||||||
child: ValueListenableBuilder<VolumeRelativeDirectory>(
|
bottom: true,
|
||||||
valueListenable: _directory,
|
child: Padding(
|
||||||
builder: (context, directory, child) {
|
padding: const EdgeInsets.all(8),
|
||||||
return 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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -187,61 +174,34 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppBar _buildAppBar(BuildContext context) {
|
Widget _buildEmptyContent() {
|
||||||
final animations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
|
return Selector<CollectionSource, bool>(
|
||||||
|
selector: (context, source) => source.state == SourceState.loading,
|
||||||
|
builder: (context, loading, child) {
|
||||||
|
Widget? bottom;
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return AppBar(
|
return Center(
|
||||||
title: InteractiveAppBarTitle(
|
child: EmptyContent(
|
||||||
onTap: _goToSearch,
|
icon: AIcons.folder,
|
||||||
child: Text(
|
text: '',
|
||||||
context.l10n.explorerPageTitle,
|
bottom: bottom,
|
||||||
softWrap: false,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
if (_volumes.length > 1)
|
|
||||||
FontSizeIconTheme(
|
|
||||||
child: PopupMenuButton<StorageVolume>(
|
|
||||||
itemBuilder: (context) {
|
|
||||||
return _volumes.map((v) {
|
|
||||||
final selected = _directory.value.volumePath == v.path;
|
|
||||||
final icon = v.isRemovable ? AIcons.storageCard : AIcons.storageMain;
|
|
||||||
return PopupMenuItem(
|
|
||||||
value: v,
|
|
||||||
enabled: !selected,
|
|
||||||
child: MenuRow(
|
|
||||||
text: v.getDescription(context),
|
|
||||||
icon: Icon(icon),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
},
|
|
||||||
onSelected: (volume) async {
|
|
||||||
// wait for the popup menu to hide before proceeding with the action
|
|
||||||
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
|
|
||||||
_goTo(volume.path);
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,18 +262,6 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToSearch() {
|
|
||||||
Navigator.maybeOf(context)?.push(
|
|
||||||
SearchPageRoute(
|
|
||||||
delegate: CollectionSearchDelegate(
|
|
||||||
searchFieldLabel: context.l10n.searchCollectionFieldHint,
|
|
||||||
searchFieldStyle: Themes.searchFieldStyle(context),
|
|
||||||
source: context.read<CollectionSource>(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _goToCollectionPage(BuildContext context, CollectionFilter filter) {
|
void _goToCollectionPage(BuildContext context, CollectionFilter filter) {
|
||||||
Navigator.maybeOf(context)?.push(
|
Navigator.maybeOf(context)?.push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/view/view.dart';
|
import 'package:aves/view/view.dart';
|
||||||
import 'package:aves_model/aves_model.dart';
|
import 'package:aves_model/aves_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class CrumbLine extends StatefulWidget {
|
class CrumbLine extends StatefulWidget {
|
||||||
final VolumeRelativeDirectory directory;
|
final VolumeRelativeDirectory directory;
|
||||||
|
@ -16,6 +18,8 @@ class CrumbLine extends StatefulWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CrumbLine> createState() => _CrumbLineState();
|
State<CrumbLine> createState() => _CrumbLineState();
|
||||||
|
|
||||||
|
static double getPreferredHeight(TextScaler textScaler) => textScaler.scale(kToolbarHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CrumbLineState extends State<CrumbLine> {
|
class _CrumbLineState extends State<CrumbLine> {
|
||||||
|
@ -23,18 +27,29 @@ class _CrumbLineState extends State<CrumbLine> {
|
||||||
|
|
||||||
VolumeRelativeDirectory get directory => widget.directory;
|
VolumeRelativeDirectory get directory => widget.directory;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@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 < widget.directory.relativeDir.length) {
|
||||||
// scroll to show last crumb
|
// scroll to show last crumb
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final animate = context.read<Settings>().animate;
|
||||||
final extent = _scrollController.position.maxScrollExtent;
|
final extent = _scrollController.position.maxScrollExtent;
|
||||||
_scrollController.animateTo(
|
if (animate) {
|
||||||
extent,
|
_scrollController.animateTo(
|
||||||
duration: const Duration(milliseconds: 500),
|
extent,
|
||||||
curve: Curves.easeOutQuad,
|
duration: const Duration(milliseconds: 500),
|
||||||
);
|
curve: Curves.easeOutQuad,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_scrollController.jumpTo(extent);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue