#437 tv: viewer overlay

This commit is contained in:
Thibault Deckers 2022-12-14 17:10:17 +01:00
parent 737dac8da1
commit 3be4f661cc
35 changed files with 435 additions and 247 deletions

View file

@ -203,7 +203,7 @@ class Settings extends ChangeNotifier {
await settingsStore.init(); await settingsStore.init();
_appliedLocale = null; _appliedLocale = null;
if (monitorPlatformSettings) { if (monitorPlatformSettings) {
_platformSettingsChangeChannel.receiveBroadcastStream().listen((event) => _onPlatformSettingsChange(event as Map?)); _platformSettingsChangeChannel.receiveBroadcastStream().listen((event) => _onPlatformSettingsChanged(event as Map?));
} }
} }
@ -246,10 +246,13 @@ class Settings extends ChangeNotifier {
FavouriteFilter.instance, FavouriteFilter.instance,
RecentlyAddedFilter.instance, RecentlyAddedFilter.instance,
]; ];
showOverlayOnOpening = false;
showOverlayMinimap = false;
showOverlayThumbnailPreview = false;
viewerGestureSideTapNext = false; viewerGestureSideTapNext = false;
viewerUseCutout = true; viewerUseCutout = true;
viewerMaxBrightness = false; viewerMaxBrightness = false;
videoControls = VideoControls.playSeek; videoControls = VideoControls.none;
videoGestureDoubleTapTogglePlay = false; videoGestureDoubleTapTogglePlay = false;
videoGestureSideDoubleTapSeek = false; videoGestureSideDoubleTapSeek = false;
enableBin = false; enableBin = false;
@ -886,7 +889,7 @@ class Settings extends ChangeNotifier {
// platform settings // platform settings
void _onPlatformSettingsChange(Map? fields) { void _onPlatformSettingsChanged(Map? fields) {
fields?.forEach((key, value) { fields?.forEach((key, value) {
switch (key) { switch (key) {
case platformAccelerometerRotationKey: case platformAccelerometerRotationKey:

View file

@ -104,9 +104,7 @@ class DurationsData {
final Duration staggeredAnimation; final Duration staggeredAnimation;
final Duration staggeredAnimationPageTarget; final Duration staggeredAnimationPageTarget;
final Duration quickChooserAnimation; final Duration quickChooserAnimation;
final Duration tvImageFocusAnimation;
// grid animations
final Duration gridTvFocusAnimation;
// viewer animations // viewer animations
final Duration viewerVerticalPageScrollAnimation; final Duration viewerVerticalPageScrollAnimation;
@ -126,7 +124,7 @@ class DurationsData {
this.staggeredAnimation = const Duration(milliseconds: 375), this.staggeredAnimation = const Duration(milliseconds: 375),
this.staggeredAnimationPageTarget = const Duration(milliseconds: 800), this.staggeredAnimationPageTarget = const Duration(milliseconds: 800),
this.quickChooserAnimation = const Duration(milliseconds: 100), this.quickChooserAnimation = const Duration(milliseconds: 100),
this.gridTvFocusAnimation = const Duration(milliseconds: 150), this.tvImageFocusAnimation = const Duration(milliseconds: 150),
this.viewerVerticalPageScrollAnimation = const Duration(milliseconds: 500), this.viewerVerticalPageScrollAnimation = const Duration(milliseconds: 500),
this.viewerOverlayAnimation = const Duration(milliseconds: 200), this.viewerOverlayAnimation = const Duration(milliseconds: 200),
this.viewerOverlayChangeAnimation = const Duration(milliseconds: 150), this.viewerOverlayChangeAnimation = const Duration(milliseconds: 150),
@ -144,7 +142,7 @@ class DurationsData {
staggeredAnimation: Duration.zero, staggeredAnimation: Duration.zero,
staggeredAnimationPageTarget: Duration.zero, staggeredAnimationPageTarget: Duration.zero,
quickChooserAnimation: Duration.zero, quickChooserAnimation: Duration.zero,
gridTvFocusAnimation: Duration.zero, tvImageFocusAnimation: Duration.zero,
viewerVerticalPageScrollAnimation: Duration.zero, viewerVerticalPageScrollAnimation: Duration.zero,
viewerOverlayAnimation: Duration.zero, viewerOverlayAnimation: Duration.zero,
viewerOverlayChangeAnimation: Duration.zero, viewerOverlayChangeAnimation: Duration.zero,

View file

@ -154,7 +154,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
_screenSize = _getScreenSize(); _screenSize = _getScreenSize();
_shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont(); _shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont();
_dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette(); _dynamicColorPaletteLoader = DynamicColorPlugin.getCorePalette();
_mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChange(event as String?)); _mediaStoreChangeChannel.receiveBroadcastStream().listen((event) => _onMediaStoreChanged(event as String?));
_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?)); _newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?));
_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()); _analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion());
_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?)); _errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?));
@ -451,7 +451,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
_mediaStoreSource.updateDerivedFilters(); _mediaStoreSource.updateDerivedFilters();
} }
void _onMediaStoreChange(String? uri) { void _onMediaStoreChanged(String? uri) {
if (uri != null) _changedUris.add(uri); if (uri != null) _changedUris.add(uri);
if (_changedUris.isNotEmpty) { if (_changedUris.isNotEmpty) {
_mediaStoreChangeDebouncer(() async { _mediaStoreChangeDebouncer(() async {
@ -460,7 +460,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
final tempUris = await _mediaStoreSource.refreshUris(todo); final tempUris = await _mediaStoreSource.refreshUris(todo);
if (tempUris.isNotEmpty) { if (tempUris.isNotEmpty) {
_changedUris.addAll(tempUris); _changedUris.addAll(tempUris);
_onMediaStoreChange(null); _onMediaStoreChanged(null);
} }
}); });
} }

View file

@ -102,7 +102,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
duration: context.read<DurationsData>().iconAnimation, duration: context.read<DurationsData>().iconAnimation,
vsync: this, vsync: this,
); );
_isSelectingNotifier.addListener(_onActivityChange); _isSelectingNotifier.addListener(_onActivityChanged);
_registerWidget(widget); _registerWidget(widget);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@ -121,8 +121,10 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
@override @override
void dispose() { void dispose() {
_unregisterWidget(widget); _unregisterWidget(widget);
_queryBarFocusNode.dispose();
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest); _queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
_isSelectingNotifier.removeListener(_onActivityChange); _isSelectingNotifier.removeListener(_onActivityChanged);
_isSelectingNotifier.dispose();
_browseToSelectAnimation.dispose(); _browseToSelectAnimation.dispose();
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
@ -529,7 +531,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
); );
} }
void _onActivityChange() { void _onActivityChanged() {
if (context.read<Selection<AvesEntry>>().isSelecting) { if (context.read<Selection<AvesEntry>>().isSelecting) {
_browseToSelectAnimation.forward(); _browseToSelectAnimation.forward();
} else { } else {

View file

@ -191,7 +191,7 @@ class _CollectionGridContentState extends State<_CollectionGridContent> {
return AnimatedScale( return AnimatedScale(
scale: focusedItem == entry ? 1 : .9, scale: focusedItem == entry ? 1 : .9,
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
duration: context.select<DurationsData, Duration>((v) => v.gridTvFocusAnimation), duration: context.select<DurationsData, Duration>((v) => v.tvImageFocusAnimation),
child: child!, child: child!,
); );
}, },
@ -408,13 +408,13 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
void _registerWidget(_CollectionScrollView widget) { void _registerWidget(_CollectionScrollView widget) {
widget.collection.filterChangeNotifier.addListener(_scrollToTop); widget.collection.filterChangeNotifier.addListener(_scrollToTop);
widget.collection.sortSectionChangeNotifier.addListener(_scrollToTop); widget.collection.sortSectionChangeNotifier.addListener(_scrollToTop);
widget.scrollController.addListener(_onScrollChange); widget.scrollController.addListener(_onScrollChanged);
} }
void _unregisterWidget(_CollectionScrollView widget) { void _unregisterWidget(_CollectionScrollView widget) {
widget.collection.filterChangeNotifier.removeListener(_scrollToTop); widget.collection.filterChangeNotifier.removeListener(_scrollToTop);
widget.collection.sortSectionChangeNotifier.removeListener(_scrollToTop); widget.collection.sortSectionChangeNotifier.removeListener(_scrollToTop);
widget.scrollController.removeListener(_onScrollChange); widget.scrollController.removeListener(_onScrollChanged);
} }
@override @override
@ -570,7 +570,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
void _scrollToTop() => widget.scrollController.jumpTo(0); void _scrollToTop() => widget.scrollController.jumpTo(0);
void _onScrollChange() { void _onScrollChanged() {
widget.isScrollingNotifier.value = true; widget.isScrollingNotifier.value = true;
_stopScrollMonitoringTimer(); _stopScrollMonitoringTimer();
_scrollMonitoringTimer = Timer(Durations.collectionScrollMonitoringTimerDelay, () { _scrollMonitoringTimer = Timer(Durations.collectionScrollMonitoringTimerDelay, () {

View file

@ -59,7 +59,7 @@ class _QueryBarState extends State<QueryBar> {
Expanded( Expanded(
child: TextField( child: TextField(
controller: _controller, controller: _controller,
focusNode: widget.focusNode ?? FocusNode(), focusNode: widget.focusNode,
decoration: InputDecoration( decoration: InputDecoration(
icon: Padding( icon: Padding(
padding: widget.leadingPadding ?? const EdgeInsetsDirectional.only(start: 16), padding: widget.leadingPadding ?? const EdgeInsetsDirectional.only(start: 16),

View file

@ -53,7 +53,7 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
parent: _angleAnimationController, parent: _angleAnimationController,
curve: widget.curve, curve: widget.curve,
)); ));
_angleAnimationController.addStatusListener(_onAnimationStatusChange); _angleAnimationController.addStatusListener(_onAnimationStatusChanged);
_registerWidget(widget); _registerWidget(widget);
} }
@ -66,7 +66,7 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
@override @override
void dispose() { void dispose() {
_angleAnimationController.removeStatusListener(_onAnimationStatusChange); _angleAnimationController.removeStatusListener(_onAnimationStatusChanged);
_angleAnimationController.dispose(); _angleAnimationController.dispose();
_unregisterWidget(widget); _unregisterWidget(widget);
super.dispose(); super.dispose();
@ -101,7 +101,7 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
); );
} }
void _onAnimationStatusChange(AnimationStatus status) { void _onAnimationStatusChanged(AnimationStatus status) {
setState(() {}); setState(() {});
if (status == AnimationStatus.completed) { if (status == AnimationStatus.completed) {
widget.onSweepEnd?.call(); widget.onSweepEnd?.call();

View file

@ -70,7 +70,7 @@ class _GridItemTrackerState<T> extends State<GridItemTracker<T>> with WidgetsBin
void didUpdateWidget(covariant GridItemTracker<T> oldWidget) { void didUpdateWidget(covariant GridItemTracker<T> oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (oldWidget.tileLayout != widget.tileLayout) { if (oldWidget.tileLayout != widget.tileLayout) {
_onLayoutChange(); _onLayoutChanged();
} }
_saveLayoutMetrics(); _saveLayoutMetrics();
} }
@ -83,7 +83,7 @@ class _GridItemTrackerState<T> extends State<GridItemTracker<T>> with WidgetsBin
final orientation = _windowOrientation; final orientation = _windowOrientation;
if (_lastOrientation != orientation) { if (_lastOrientation != orientation) {
_lastOrientation = orientation; _lastOrientation = orientation;
_onLayoutChange(); _onLayoutChanged();
_saveLayoutMetrics(); _saveLayoutMetrics();
} }
} }
@ -147,7 +147,7 @@ class _GridItemTrackerState<T> extends State<GridItemTracker<T>> with WidgetsBin
} }
} }
void _onLayoutChange() { void _onLayoutChanged() {
if (scrollController.positions.length != 1) return; if (scrollController.positions.length != 1) return;
// do not track when view shows top edge // do not track when view shows top edge

View file

@ -162,13 +162,13 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
void initState() { void initState() {
super.initState(); super.initState();
_tapped = false; _tapped = false;
_subscriptions.add(covers.packageChangeStream.listen(_onCoverColorChange)); _subscriptions.add(covers.packageChangeStream.listen(_onCoverColorChanged));
_subscriptions.add(covers.colorChangeStream.listen(_onCoverColorChange)); _subscriptions.add(covers.colorChangeStream.listen(_onCoverColorChanged));
_subscriptions.add(settings.updateStream.where((event) => event.key == Settings.themeColorModeKey).listen((_) { _subscriptions.add(settings.updateStream.where((event) => event.key == Settings.themeColorModeKey).listen((_) {
// delay so that contextual colors reflect the new settings // delay so that contextual colors reflect the new settings
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
_onCoverColorChange(null); _onCoverColorChanged(null);
}); });
})); }));
} }
@ -207,7 +207,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
_outlineColor = context.read<AvesColorsData>().neutral; _outlineColor = context.read<AvesColorsData>().neutral;
} }
void _onCoverColorChange(Set<CollectionFilter>? event) { void _onCoverColorChanged(Set<CollectionFilter>? event) {
if (event == null || event.contains(filter)) { if (event == null || event.contains(filter)) {
_initColorLoader(); _initColorLoader();
setState(() {}); setState(() {});

View file

@ -98,12 +98,12 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
} }
_subscriptions.add(_leafletMapController.mapEventStream.listen((event) => _updateVisibleRegion())); _subscriptions.add(_leafletMapController.mapEventStream.listen((event) => _updateVisibleRegion()));
widget.clusterListenable.addListener(_updateMarkers); widget.clusterListenable.addListener(_updateMarkers);
widget.boundsNotifier.addListener(_onBoundsChange); widget.boundsNotifier.addListener(_onBoundsChanged);
} }
void _unregisterWidget(EntryLeafletMap<T> widget) { void _unregisterWidget(EntryLeafletMap<T> widget) {
widget.clusterListenable.removeListener(_updateMarkers); widget.clusterListenable.removeListener(_updateMarkers);
widget.boundsNotifier.removeListener(_onBoundsChange); widget.boundsNotifier.removeListener(_onBoundsChanged);
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
@ -230,7 +230,7 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
); );
} }
void _onBoundsChange() => _debouncer(_onIdle); void _onBoundsChanged() => _debouncer(_onIdle);
void _onIdle() { void _onIdle() {
if (!mounted) return; if (!mounted) return;

View file

@ -78,14 +78,14 @@ class _ThumbnailScrollerState extends State<ThumbnailScroller> {
void _registerWidget(ThumbnailScroller widget) { void _registerWidget(ThumbnailScroller widget) {
final scrollOffset = indexToScrollOffset(indexNotifier.value ?? 0); final scrollOffset = indexToScrollOffset(indexNotifier.value ?? 0);
_scrollController = ScrollController(initialScrollOffset: scrollOffset); _scrollController = ScrollController(initialScrollOffset: scrollOffset);
_scrollController.addListener(_onScrollChange); _scrollController.addListener(_onScrollChanged);
widget.indexNotifier.addListener(_onIndexChange); widget.indexNotifier.addListener(_onIndexChanged);
} }
void _unregisterWidget(ThumbnailScroller widget) { void _unregisterWidget(ThumbnailScroller widget) {
_scrollController.removeListener(_onScrollChange); _scrollController.removeListener(_onScrollChanged);
_scrollController.dispose(); _scrollController.dispose();
widget.indexNotifier.removeListener(_onIndexChange); widget.indexNotifier.removeListener(_onIndexChanged);
} }
@override @override
@ -180,7 +180,7 @@ class _ThumbnailScrollerState extends State<ThumbnailScroller> {
} }
} }
void _onScrollChange() { void _onScrollChanged() {
if (!_isAnimating) { if (!_isAnimating) {
final index = scrollOffsetToIndex(_scrollController.offset); final index = scrollOffsetToIndex(_scrollController.offset);
if (indexNotifier.value != index) { if (indexNotifier.value != index) {
@ -190,7 +190,7 @@ class _ThumbnailScrollerState extends State<ThumbnailScroller> {
} }
} }
void _onIndexChange() { void _onIndexChanged() {
if (!_isScrolling && !_isAnimating) { if (!_isScrolling && !_isAnimating) {
final index = indexNotifier.value; final index = indexNotifier.value;
if (index != null) { if (index != null) {

View file

@ -45,13 +45,13 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
void initState() { void initState() {
super.initState(); super.initState();
_patternTextController.text = settings.entryRenamingPattern; _patternTextController.text = settings.entryRenamingPattern;
_patternTextController.addListener(_onUserPatternChange); _patternTextController.addListener(_onUserPatternChanged);
_onUserPatternChange(); _onUserPatternChanged();
} }
@override @override
void dispose() { void dispose() {
_patternTextController.removeListener(_onUserPatternChange); _patternTextController.removeListener(_onUserPatternChanged);
super.dispose(); super.dispose();
} }
@ -196,7 +196,7 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
); );
} }
void _onUserPatternChange() { void _onUserPatternChanged() {
_namingPatternNotifier.value = NamingPattern.from( _namingPatternNotifier.value = NamingPattern.from(
userPattern: _patternTextController.text, userPattern: _patternTextController.text,
entryCount: entryCount, entryCount: entryCount,

View file

@ -44,6 +44,13 @@ class _TagEditorPageState extends State<TagEditorPage> {
_initTopTags(); _initTopTags();
} }
@override
void dispose() {
_newTagTextFocusNode.dispose();
_expandedSectionNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;

View file

@ -38,6 +38,9 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
void dispose() { void dispose() {
_nameController.dispose(); _nameController.dispose();
_nameFieldFocusNode.removeListener(_onFocus); _nameFieldFocusNode.removeListener(_onFocus);
_nameFieldFocusNode.dispose();
_existsNotifier.dispose();
_isValidNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -102,14 +102,16 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
duration: context.read<DurationsData>().iconAnimation, duration: context.read<DurationsData>().iconAnimation,
vsync: this, vsync: this,
); );
_isSelectingNotifier.addListener(_onActivityChange); _isSelectingNotifier.addListener(_onActivityChanged);
WidgetsBinding.instance.addPostFrameCallback((_) => _updateAppBarHeight()); WidgetsBinding.instance.addPostFrameCallback((_) => _updateAppBarHeight());
} }
@override @override
void dispose() { void dispose() {
_queryBarFocusNode.dispose();
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest); _queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
_isSelectingNotifier.removeListener(_onActivityChange); _isSelectingNotifier.removeListener(_onActivityChanged);
_isSelectingNotifier.dispose();
_browseToSelectAnimation.dispose(); _browseToSelectAnimation.dispose();
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
@ -368,7 +370,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
} }
} }
void _onActivityChange() { void _onActivityChanged() {
if (context.read<Selection<FilterGridItem<T>>>().isSelecting) { if (context.read<Selection<FilterGridItem<T>>>().isSelecting) {
_browseToSelectAnimation.forward(); _browseToSelectAnimation.forward();
} else { } else {

View file

@ -365,7 +365,7 @@ class _FilterGridContentState<T extends CollectionFilter> extends State<_FilterG
return AnimatedScale( return AnimatedScale(
scale: focusedItem == gridItem ? 1 : .9, scale: focusedItem == gridItem ? 1 : .9,
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
duration: context.select<DurationsData, Duration>((v) => v.gridTvFocusAnimation), duration: context.select<DurationsData, Duration>((v) => v.tvImageFocusAnimation),
child: child!, child: child!,
); );
}, },

View file

@ -132,12 +132,12 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
parent: _overlayAnimationController, parent: _overlayAnimationController,
curve: Curves.easeOutQuad, curve: Curves.easeOutQuad,
); );
_overlayVisible.addListener(_onOverlayVisibleChange); _overlayVisible.addListener(_onOverlayVisibleChanged);
_subscriptions.add(_mapController.idleUpdates.listen((event) => _onIdle(event.bounds))); _subscriptions.add(_mapController.idleUpdates.listen((event) => _onIdle(event.bounds)));
_subscriptions.add(openingCollection.source.eventBus.on<CatalogMetadataChangedEvent>().listen((e) => _updateRegionCollection())); _subscriptions.add(openingCollection.source.eventBus.on<CatalogMetadataChangedEvent>().listen((e) => _updateRegionCollection()));
_selectedIndexNotifier.addListener(_onThumbnailIndexChange); _selectedIndexNotifier.addListener(_onThumbnailIndexChanged);
Future.delayed(Durations.pageTransitionAnimation * timeDilation + const Duration(seconds: 1), () { Future.delayed(Durations.pageTransitionAnimation * timeDilation + const Duration(seconds: 1), () {
final regionEntries = regionCollection?.sortedEntries ?? []; final regionEntries = regionCollection?.sortedEntries ?? [];
final initialEntry = widget.initialEntry ?? regionEntries.firstOrNull; final initialEntry = widget.initialEntry ?? regionEntries.firstOrNull;
@ -150,7 +150,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
} }
}); });
WidgetsBinding.instance.addPostFrameCallback((_) => _onOverlayVisibleChange(animate: false)); WidgetsBinding.instance.addPostFrameCallback((_) => _onOverlayVisibleChanged(animate: false));
} }
@override @override
@ -161,9 +161,9 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged); _dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
_dotEntryNotifier.removeListener(_onSelectedEntryChanged); _dotEntryNotifier.removeListener(_onSelectedEntryChanged);
_overlayAnimationController.dispose(); _overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange); _overlayVisible.removeListener(_onOverlayVisibleChanged);
_mapController.dispose(); _mapController.dispose();
_selectedIndexNotifier.removeListener(_onThumbnailIndexChange); _selectedIndexNotifier.removeListener(_onThumbnailIndexChanged);
regionCollection?.dispose(); regionCollection?.dispose();
super.dispose(); super.dispose();
} }
@ -380,7 +380,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
: 0; : 0;
_selectedIndexNotifier.value = selectedIndex; _selectedIndexNotifier.value = selectedIndex;
// force update, as the region entries may change without a change of index // force update, as the region entries may change without a change of index
_onThumbnailIndexChange(); _onThumbnailIndexChanged();
} }
AvesEntry? _getRegionEntry(int? index) { AvesEntry? _getRegionEntry(int? index) {
@ -395,7 +395,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
void _onThumbnailTap(int index) => _goToViewer(_getRegionEntry(index)); void _onThumbnailTap(int index) => _goToViewer(_getRegionEntry(index));
void _onThumbnailIndexChange() => _onEntrySelected(_getRegionEntry(_selectedIndexNotifier.value)); void _onThumbnailIndexChanged() => _onEntrySelected(_getRegionEntry(_selectedIndexNotifier.value));
void _onEntrySelected(AvesEntry? selectedEntry) { void _onEntrySelected(AvesEntry? selectedEntry) {
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged); _dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
@ -457,7 +457,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
void _toggleOverlay() => _overlayVisible.value = !_overlayVisible.value; void _toggleOverlay() => _overlayVisible.value = !_overlayVisible.value;
Future<void> _onOverlayVisibleChange({bool animate = true}) async { Future<void> _onOverlayVisibleChanged({bool animate = true}) async {
if (_overlayVisible.value) { if (_overlayVisible.value) {
if (animate) { if (animate) {
await _overlayAnimationController.forward(); await _overlayAnimationController.forward();

View file

@ -69,12 +69,12 @@ class _FloatingNavBarState extends State<FloatingNavBar> with SingleTickerProvid
void _registerWidget(FloatingNavBar widget) { void _registerWidget(FloatingNavBar widget) {
_lastOffset = null; _lastOffset = null;
widget.scrollController?.addListener(_onScrollChange); widget.scrollController?.addListener(_onScrollChanged);
_subscriptions.add(widget.events.listen(_onDraggableScrollBarEvent)); _subscriptions.add(widget.events.listen(_onDraggableScrollBarEvent));
} }
void _unregisterWidget(FloatingNavBar widget) { void _unregisterWidget(FloatingNavBar widget) {
widget.scrollController?.removeListener(_onScrollChange); widget.scrollController?.removeListener(_onScrollChanged);
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
@ -88,7 +88,7 @@ class _FloatingNavBarState extends State<FloatingNavBar> with SingleTickerProvid
); );
} }
void _onScrollChange() { void _onScrollChanged() {
final scrollController = widget.scrollController; final scrollController = widget.scrollController;
if (scrollController == null) return; if (scrollController == null) return;

View file

@ -57,11 +57,11 @@ class _AppBottomNavBarState extends State<AppBottomNavBar> {
} }
void _registerWidget(AppBottomNavBar widget) { void _registerWidget(AppBottomNavBar widget) {
widget.currentCollection?.filterChangeNotifier.addListener(_onCollectionFilterChange); widget.currentCollection?.filterChangeNotifier.addListener(_onCollectionFilterChanged);
} }
void _unregisterWidget(AppBottomNavBar widget) { void _unregisterWidget(AppBottomNavBar widget) {
widget.currentCollection?.filterChangeNotifier.removeListener(_onCollectionFilterChange); widget.currentCollection?.filterChangeNotifier.removeListener(_onCollectionFilterChanged);
} }
@override @override
@ -112,7 +112,7 @@ class _AppBottomNavBarState extends State<AppBottomNavBar> {
); );
} }
void _onCollectionFilterChange() => setState(() {}); void _onCollectionFilterChanged() => setState(() {});
int _getCurrentIndex(BuildContext context, List<AvesBottomNavItem> items) { int _getCurrentIndex(BuildContext context, List<AvesBottomNavItem> items) {
// current route may be null during navigation // current route may be null during navigation

View file

@ -1,3 +1,4 @@
import 'package:aves/model/device.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/common/tiles.dart'; import 'package:aves/widgets/settings/common/tiles.dart';
@ -19,6 +20,7 @@ class ViewerOverlayPage extends StatelessWidget {
body: SafeArea( body: SafeArea(
child: ListView( child: ListView(
children: [ children: [
if (!device.isTelevision)
SettingsSwitchListTile( SettingsSwitchListTile(
selector: (context, s) => s.showOverlayOnOpening, selector: (context, s) => s.showOverlayOnOpening,
onChanged: (v) => settings.showOverlayOnOpening = v, onChanged: (v) => settings.showOverlayOnOpening = v,
@ -54,11 +56,13 @@ class ViewerOverlayPage extends StatelessWidget {
); );
}, },
), ),
if (!device.isTelevision)
SettingsSwitchListTile( SettingsSwitchListTile(
selector: (context, s) => s.showOverlayMinimap, selector: (context, s) => s.showOverlayMinimap,
onChanged: (v) => settings.showOverlayMinimap = v, onChanged: (v) => settings.showOverlayMinimap = v,
title: context.l10n.settingsViewerShowMinimap, title: context.l10n.settingsViewerShowMinimap,
), ),
if (!device.isTelevision)
SettingsSwitchListTile( SettingsSwitchListTile(
selector: (context, s) => s.showOverlayThumbnailPreview, selector: (context, s) => s.showOverlayThumbnailPreview,
onChanged: (v) => settings.showOverlayThumbnailPreview = v, onChanged: (v) => settings.showOverlayThumbnailPreview = v,

View file

@ -92,6 +92,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
case EntryAction.videoCaptureFrame: case EntryAction.videoCaptureFrame:
return !device.isReadOnly && targetEntry.isVideo; return !device.isReadOnly && targetEntry.isVideo;
case EntryAction.videoToggleMute: case EntryAction.videoToggleMute:
return !device.isTelevision && targetEntry.isVideo;
case EntryAction.videoSelectStreams: case EntryAction.videoSelectStreams:
case EntryAction.videoSetSpeed: case EntryAction.videoSetSpeed:
case EntryAction.videoSettings: case EntryAction.videoSettings:
@ -106,8 +107,9 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
return device.canPinShortcut; return device.canPinShortcut;
case EntryAction.edit: case EntryAction.edit:
return !device.isReadOnly; return !device.isReadOnly;
case EntryAction.info:
case EntryAction.copyToClipboard: case EntryAction.copyToClipboard:
return !device.isTelevision;
case EntryAction.info:
case EntryAction.open: case EntryAction.open:
case EntryAction.setAs: case EntryAction.setAs:
case EntryAction.share: case EntryAction.share:
@ -174,7 +176,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
switch (action) { switch (action) {
case EntryAction.info: case EntryAction.info:
ShowInfoNotification().dispatch(context); ShowInfoPageNotification().dispatch(context);
break; break;
case EntryAction.addShortcut: case EntryAction.addShortcut:
_addShortcut(context, targetEntry); _addShortcut(context, targetEntry);

View file

@ -48,18 +48,18 @@ class ViewerController {
}) { }) {
_initialScale = initialScale; _initialScale = initialScale;
_autopilotNotifier = ValueNotifier(autopilot); _autopilotNotifier = ValueNotifier(autopilot);
_autopilotNotifier.addListener(_onAutopilotChange); _autopilotNotifier.addListener(_onAutopilotChanged);
_onAutopilotChange(); _onAutopilotChanged();
} }
void dispose() { void dispose() {
_autopilotNotifier.removeListener(_onAutopilotChange); _autopilotNotifier.removeListener(_onAutopilotChanged);
_clearAutopilotAnimations(); _clearAutopilotAnimations();
_stopPlayTimer(); _stopPlayTimer();
_streamController.close(); _streamController.close();
} }
void _onAutopilotChange() { void _onAutopilotChanged() {
_clearAutopilotAnimations(); _clearAutopilotAnimations();
_stopPlayTimer(); _stopPlayTimer();
if (autopilot && autopilotInterval != null) { if (autopilot && autopilotInterval != null) {

View file

@ -3,6 +3,8 @@ import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -11,6 +13,7 @@ import 'package:aves/widgets/viewer/controller.dart';
import 'package:aves/widgets/viewer/entry_horizontal_pager.dart'; import 'package:aves/widgets/viewer/entry_horizontal_pager.dart';
import 'package:aves/widgets/viewer/info/info_page.dart'; import 'package:aves/widgets/viewer/info/info_page.dart';
import 'package:aves/widgets/viewer/notifications.dart'; import 'package:aves/widgets/viewer/notifications.dart';
import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:aves_magnifier/aves_magnifier.dart'; import 'package:aves_magnifier/aves_magnifier.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -50,6 +53,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
final ValueNotifier<double> _backgroundOpacityNotifier = ValueNotifier(1); final ValueNotifier<double> _backgroundOpacityNotifier = ValueNotifier(1);
final ValueNotifier<bool> _isVerticallyScrollingNotifier = ValueNotifier(false); final ValueNotifier<bool> _isVerticallyScrollingNotifier = ValueNotifier(false);
final ValueNotifier<bool> _isImageFocusedNotifier = ValueNotifier(true);
Timer? _verticalScrollMonitoringTimer; Timer? _verticalScrollMonitoringTimer;
AvesEntry? _oldEntry; AvesEntry? _oldEntry;
Future<double>? _systemBrightness; Future<double>? _systemBrightness;
@ -83,6 +87,9 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
void dispose() { void dispose() {
_unregisterWidget(widget); _unregisterWidget(widget);
_stopScrollMonitoringTimer(); _stopScrollMonitoringTimer();
_backgroundOpacityNotifier.dispose();
_isVerticallyScrollingNotifier.dispose();
_isImageFocusedNotifier.dispose();
super.dispose(); super.dispose();
} }
@ -174,10 +181,19 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
} }
Widget _buildImagePage() { Widget _buildImagePage() {
final isTelevision = device.isTelevision;
Widget? child; Widget? child;
Map<ShortcutActivator, Intent>? shortcuts; Map<ShortcutActivator, Intent>? shortcuts = {
const SingleActivator(LogicalKeyboardKey.arrowUp): isTelevision ? const TvShowLessInfoIntent() : const LeaveIntent(),
const SingleActivator(LogicalKeyboardKey.arrowDown): isTelevision ? const TvShowMoreInfoIntent() : const ShowInfoIntent(),
};
if (hasCollection) { if (hasCollection) {
shortcuts.addAll(const {
SingleActivator(LogicalKeyboardKey.arrowLeft): ShowPreviousIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): ShowNextIntent(),
});
child = MultiEntryScroller( child = MultiEntryScroller(
collection: collection!, collection: collection!,
viewerController: widget.viewerController, viewerController: widget.viewerController,
@ -185,23 +201,28 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
onPageChanged: widget.onHorizontalPageChanged, onPageChanged: widget.onHorizontalPageChanged,
onViewDisposed: widget.onViewDisposed, onViewDisposed: widget.onViewDisposed,
); );
shortcuts = const {
SingleActivator(LogicalKeyboardKey.arrowLeft): ShowPreviousIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): ShowNextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp): LeaveIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): ShowInfoIntent(),
};
} else if (entry != null) { } else if (entry != null) {
child = SingleEntryScroller( child = SingleEntryScroller(
viewerController: widget.viewerController, viewerController: widget.viewerController,
entry: entry!, entry: entry!,
); );
shortcuts = const {
SingleActivator(LogicalKeyboardKey.arrowUp): LeaveIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): ShowInfoIntent(),
};
} }
if (child != null) { if (child != null) {
if (device.isTelevision) {
child = ValueListenableBuilder<bool>(
valueListenable: _isImageFocusedNotifier,
builder: (context, isImageFocused, child) {
return AnimatedScale(
scale: isImageFocused ? 1 : .7,
curve: Curves.fastOutSlowIn,
duration: context.select<DurationsData, Duration>((v) => v.tvImageFocusAnimation),
child: child!,
);
},
child: child,
);
}
return FocusableActionDetector( return FocusableActionDetector(
autofocus: true, autofocus: true,
shortcuts: shortcuts, shortcuts: shortcuts,
@ -209,8 +230,25 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
ShowPreviousIntent: CallbackAction<Intent>(onInvoke: (intent) => _goToHorizontalPage(-1, animate: false)), ShowPreviousIntent: CallbackAction<Intent>(onInvoke: (intent) => _goToHorizontalPage(-1, animate: false)),
ShowNextIntent: CallbackAction<Intent>(onInvoke: (intent) => _goToHorizontalPage(1, animate: false)), ShowNextIntent: CallbackAction<Intent>(onInvoke: (intent) => _goToHorizontalPage(1, animate: false)),
LeaveIntent: CallbackAction<Intent>(onInvoke: (intent) => Navigator.pop(context)), LeaveIntent: CallbackAction<Intent>(onInvoke: (intent) => Navigator.pop(context)),
ShowInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => ShowInfoNotification().dispatch(context)), ShowInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => ShowInfoPageNotification().dispatch(context)),
TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context)),
TvShowMoreInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowMoreInfoNotification().dispatch(context)),
ActivateIntent: CallbackAction<Intent>(onInvoke: (intent) {
if (isTelevision) {
final _entry = entry;
if (_entry != null && _entry.isVideo) {
final controller = context.read<VideoConductor>().getController(_entry);
if (controller != null) {
VideoActionNotification(controller: controller, action: EntryAction.videoTogglePlay).dispatch(context);
}
} else {
const ToggleOverlayNotification().dispatch(context);
}
}
return null;
}),
}, },
onFocusChange: (focused) => _isImageFocusedNotifier.value = focused,
child: child, child: child,
); );
} }
@ -311,3 +349,11 @@ class LeaveIntent extends Intent {
class ShowInfoIntent extends Intent { class ShowInfoIntent extends Intent {
const ShowInfoIntent(); const ShowInfoIntent();
} }
class TvShowLessInfoIntent extends Intent {
const TvShowLessInfoIntent();
}
class TvShowMoreInfoIntent extends Intent {
const TvShowMoreInfoIntent();
}

View file

@ -118,7 +118,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
_currentEntryIndex = max(0, entry != null ? entries.indexOf(entry) : -1); _currentEntryIndex = max(0, entry != null ? entries.indexOf(entry) : -1);
_currentVerticalPage = ValueNotifier(imagePage); _currentVerticalPage = ValueNotifier(imagePage);
_horizontalPager = PageController(initialPage: _currentEntryIndex); _horizontalPager = PageController(initialPage: _currentEntryIndex);
_verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChange); _verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChanged);
_overlayAnimationController = AnimationController( _overlayAnimationController = AnimationController(
duration: context.read<DurationsData>().viewerOverlayAnimation, duration: context.read<DurationsData>().viewerOverlayAnimation,
vsync: this, vsync: this,
@ -142,7 +142,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
curve: Curves.easeOutQuad, curve: Curves.easeOutQuad,
)); ));
_overlayVisible.value = settings.showOverlayOnOpening && !viewerController.autopilot; _overlayVisible.value = settings.showOverlayOnOpening && !viewerController.autopilot;
_overlayVisible.addListener(_onOverlayVisibleChange); _overlayVisible.addListener(_onOverlayVisibleChanged);
_videoActionDelegate = VideoActionDelegate( _videoActionDelegate = VideoActionDelegate(
collection: collection, collection: collection,
); );
@ -164,19 +164,19 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
cleanEntryControllers(entryNotifier.value); cleanEntryControllers(entryNotifier.value);
_videoActionDelegate.dispose(); _videoActionDelegate.dispose();
_overlayAnimationController.dispose(); _overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange); _overlayVisible.removeListener(_onOverlayVisibleChanged);
_verticalPager.removeListener(_onVerticalPageControllerChange); _verticalPager.removeListener(_onVerticalPageControllerChanged);
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
_unregisterWidget(widget); _unregisterWidget(widget);
super.dispose(); super.dispose();
} }
void _registerWidget(EntryViewerStack widget) { void _registerWidget(EntryViewerStack widget) {
widget.collection?.addListener(_onCollectionChange); widget.collection?.addListener(_onCollectionChanged);
} }
void _unregisterWidget(EntryViewerStack widget) { void _unregisterWidget(EntryViewerStack widget) {
widget.collection?.removeListener(_onCollectionChange); widget.collection?.removeListener(_onCollectionChanged);
} }
@override @override
@ -199,13 +199,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
final viewStateConductor = context.read<ViewStateConductor>(); final viewStateConductor = context.read<ViewStateConductor>();
return WillPopScope( return WillPopScope(
onWillPop: () { onWillPop: () {
if (_currentVerticalPage.value == infoPage) { _onWillPop();
// back from info to image
_goToVerticalPage(imagePage);
} else {
if (!_isEntryTracked) _trackEntry();
_popVisual();
}
return SynchronousFuture(false); return SynchronousFuture(false);
}, },
child: ValueListenableProvider<HeroInfo?>.value( child: ValueListenableProvider<HeroInfo?>.value(
@ -243,10 +237,19 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} }
} else if (notification is ToggleOverlayNotification) { } else if (notification is ToggleOverlayNotification) {
_overlayVisible.value = notification.visible ?? !_overlayVisible.value; _overlayVisible.value = notification.visible ?? !_overlayVisible.value;
} else if (notification is ShowInfoNotification) { } else if (notification is TvShowLessInfoNotification) {
if (_overlayVisible.value) {
_overlayVisible.value = false;
} else {
_onWillPop();
}
} else if (notification is TvShowMoreInfoNotification) {
if (!_overlayVisible.value) {
_overlayVisible.value = true;
}
} else if (notification is ShowInfoPageNotification) {
// remove focus, if any, to prevent viewer shortcuts activation from the Info page // remove focus, if any, to prevent viewer shortcuts activation from the Info page
FocusManager.instance.primaryFocus?.unfocus(); _showInfoPage();
_goToVerticalPage(infoPage);
} else if (notification is JumpToPreviousEntryNotification) { } else if (notification is JumpToPreviousEntryNotification) {
_jumpToHorizontalPageByDelta(-1); _jumpToHorizontalPageByDelta(-1);
} else if (notification is JumpToNextEntryNotification) { } else if (notification is JumpToNextEntryNotification) {
@ -458,7 +461,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
builder: (context, mqHeight, child) { builder: (context, mqHeight, child) {
// when orientation change, the `PageController` offset is not updated right away // when orientation change, the `PageController` offset is not updated right away
// and it does not trigger its listeners when it does, so we force a refresh in the next frame // and it does not trigger its listeners when it does, so we force a refresh in the next frame
WidgetsBinding.instance.addPostFrameCallback((_) => _onVerticalPageControllerChange()); WidgetsBinding.instance.addPostFrameCallback((_) => _onVerticalPageControllerChanged());
return AnimatedBuilder( return AnimatedBuilder(
animation: _verticalScrollNotifier, animation: _verticalScrollNotifier,
builder: (context, child) => Positioned( builder: (context, child) => Positioned(
@ -492,7 +495,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} }
} }
void _onVerticalPageControllerChange() { void _onVerticalPageControllerChanged() {
if (!_isEntryTracked && _verticalPager.hasClients && _verticalPager.page?.floor() == transitionPage) { if (!_isEntryTracked && _verticalPager.hasClients && _verticalPager.page?.floor() == transitionPage) {
_trackEntry(); _trackEntry();
} }
@ -520,6 +523,12 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
); );
} }
void _showInfoPage() {
// remove focus, if any, to prevent viewer shortcuts activation from the Info page
FocusManager.instance.primaryFocus?.unfocus();
_goToVerticalPage(infoPage);
}
Future<void> _goToVerticalPage(int page) async { Future<void> _goToVerticalPage(int page) async {
final animationDuration = context.read<DurationsData>().viewerVerticalPageScrollAnimation; final animationDuration = context.read<DurationsData>().viewerVerticalPageScrollAnimation;
if (animationDuration > Duration.zero) { if (animationDuration > Duration.zero) {
@ -574,7 +583,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
_updateEntry(); _updateEntry();
} }
void _onCollectionChange() { void _onCollectionChanged() {
_updateEntry(); _updateEntry();
} }
@ -588,7 +597,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
if (index != -1) { if (index != -1) {
_onHorizontalPageChanged(index); _onHorizontalPageChanged(index);
} }
_onCollectionChange(); _onCollectionChanged();
} }
} }
@ -600,7 +609,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
final collectionEntries = collection!.sortedEntries; final collectionEntries = collection!.sortedEntries;
removedEntries.forEach(collectionEntries.remove); removedEntries.forEach(collectionEntries.remove);
if (collectionEntries.isNotEmpty) { if (collectionEntries.isNotEmpty) {
_onCollectionChange(); _onCollectionChanged();
return; return;
} }
} }
@ -630,6 +639,16 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
await initEntryControllers(newEntry); await initEntryControllers(newEntry);
} }
void _onWillPop() {
if (_currentVerticalPage.value == infoPage) {
// back from info to image
_goToVerticalPage(imagePage);
} else {
if (!_isEntryTracked) _trackEntry();
_popVisual();
}
}
void _popVisual() { void _popVisual() {
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
void pop() { void pop() {
@ -689,11 +708,11 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
// wait for MaterialPageRoute.transitionDuration // wait for MaterialPageRoute.transitionDuration
// to show overlay after hero animation is complete // to show overlay after hero animation is complete
await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation); await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation);
await _onOverlayVisibleChange(); await _onOverlayVisibleChanged();
_overlayInitialized = true; _overlayInitialized = true;
} }
Future<void> _onOverlayVisibleChange({bool animate = true}) async { Future<void> _onOverlayVisibleChanged({bool animate = true}) async {
if (_overlayVisible.value) { if (_overlayVisible.value) {
await AvesApp.showSystemUI(); await AvesApp.showSystemUI();
AvesApp.setSystemUIStyle(context); AvesApp.setSystemUIStyle(context);

View file

@ -61,11 +61,11 @@ class _LocationSectionState extends State<LocationSection> {
} }
void _registerWidget(LocationSection widget) { void _registerWidget(LocationSection widget) {
widget.entry.metadataChangeNotifier.addListener(_onMetadataChange); widget.entry.metadataChangeNotifier.addListener(_onMetadataChanged);
} }
void _unregisterWidget(LocationSection widget) { void _unregisterWidget(LocationSection widget) {
widget.entry.metadataChangeNotifier.removeListener(_onMetadataChange); widget.entry.metadataChangeNotifier.removeListener(_onMetadataChanged);
} }
@override @override
@ -150,7 +150,7 @@ class _LocationSectionState extends State<LocationSection> {
mapCollection.dispose(); mapCollection.dispose();
} }
void _onMetadataChange() { void _onMetadataChanged() {
setState(() {}); setState(() {});
final location = entry.latLng; final location = entry.latLng;

View file

@ -10,7 +10,13 @@ import 'package:flutter/widgets.dart';
class ShowImageNotification extends Notification {} class ShowImageNotification extends Notification {}
@immutable @immutable
class ShowInfoNotification extends Notification {} class ShowInfoPageNotification extends Notification {}
@immutable
class TvShowLessInfoNotification extends Notification {}
@immutable
class TvShowMoreInfoNotification extends Notification {}
@immutable @immutable
class ToggleOverlayNotification extends Notification { class ToggleOverlayNotification extends Notification {

View file

@ -1,17 +1,22 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/app_mode.dart'; import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/extensions/media_query.dart';
import 'package:aves/widgets/viewer/entry_vertical_pager.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart'; import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves/widgets/viewer/notifications.dart';
import 'package:aves/widgets/viewer/overlay/multipage.dart'; import 'package:aves/widgets/viewer/overlay/multipage.dart';
import 'package:aves/widgets/viewer/overlay/thumbnail_preview.dart'; import 'package:aves/widgets/viewer/overlay/thumbnail_preview.dart';
import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart'; import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart';
import 'package:aves/widgets/viewer/overlay/wallpaper_buttons.dart'; import 'package:aves/widgets/viewer/overlay/wallpaper_buttons.dart';
import 'package:aves/widgets/viewer/page_entry_builder.dart'; import 'package:aves/widgets/viewer/page_entry_builder.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -119,11 +124,30 @@ class _BottomOverlayContent extends StatefulWidget {
} }
class _BottomOverlayContentState extends State<_BottomOverlayContent> { class _BottomOverlayContentState extends State<_BottomOverlayContent> {
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
late Animation<double> _buttonScale, _thumbnailOpacity; late Animation<double> _buttonScale, _thumbnailOpacity;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_registerWidget(widget);
}
@override
void didUpdateWidget(covariant _BottomOverlayContent oldWidget) {
super.didUpdateWidget(oldWidget);
_unregisterWidget(oldWidget);
_registerWidget(widget);
}
@override
void dispose() {
_unregisterWidget(widget);
_buttonRowFocusScopeNode.dispose();
super.dispose();
}
void _registerWidget(_BottomOverlayContent widget) {
final animationController = widget.animationController; final animationController = widget.animationController;
_buttonScale = CurvedAnimation( _buttonScale = CurvedAnimation(
parent: animationController, parent: animationController,
@ -134,6 +158,11 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
parent: animationController, parent: animationController,
curve: Curves.easeOutQuad, curve: Curves.easeOutQuad,
); );
animationController.addStatusListener(_onAnimationStatusChanged);
}
void _unregisterWidget(_BottomOverlayContent widget) {
widget.animationController.removeStatusListener(_onAnimationStatusChanged);
} }
@override @override
@ -153,7 +182,11 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
selector: (context, mq) => mq.size.width, selector: (context, mq) => mq.size.width,
builder: (context, mqWidth, child) { builder: (context, mqWidth, child) {
final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero); final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero);
final viewerButtonRow = SafeArea( final viewerButtonRow = FocusableActionDetector(
focusNode: _buttonRowFocusScopeNode,
shortcuts: device.isTelevision ? const {SingleActivator(LogicalKeyboardKey.arrowUp): TvShowLessInfoIntent()} : null,
actions: {TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context))},
child: SafeArea(
top: false, top: false,
bottom: false, bottom: false,
minimum: EdgeInsets.only( minimum: EdgeInsets.only(
@ -171,6 +204,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
collection: widget.collection, collection: widget.collection,
scale: _buttonScale, scale: _buttonScale,
), ),
),
); );
final showMultiPageOverlay = mainEntry.isMultiPage && multiPageController != null; final showMultiPageOverlay = mainEntry.isMultiPage && multiPageController != null;
@ -228,7 +262,14 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
); );
}, },
); );
}); },
);
}
void _onAnimationStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.completed) {
_buttonRowFocusScopeNode.children.firstOrNull?.requestFocus();
}
} }
} }

View file

@ -49,11 +49,11 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
} }
void _registerWidget(MultiPageOverlay widget) { void _registerWidget(MultiPageOverlay widget) {
widget.controller.pageNotifier.addListener(_onPageChange); widget.controller.pageNotifier.addListener(_onPageChanged);
} }
void _unregisterWidget(MultiPageOverlay widget) { void _unregisterWidget(MultiPageOverlay widget) {
widget.controller.pageNotifier.removeListener(_onPageChange); widget.controller.pageNotifier.removeListener(_onPageChanged);
} }
@override @override
@ -74,7 +74,7 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
); );
} }
void _onPageChange() { void _onPageChanged() {
if (_previousPage != null) { if (_previousPage != null) {
final info = controller.info; final info = controller.info;
if (info != null) { if (info != null) {

View file

@ -35,7 +35,7 @@ class _ViewerThumbnailPreviewState extends State<ViewerThumbnailPreview> {
void initState() { void initState() {
super.initState(); super.initState();
_entryIndexNotifier.value = widget.displayedIndex; _entryIndexNotifier.value = widget.displayedIndex;
_entryIndexNotifier.addListener(_onScrollerIndexChange); _entryIndexNotifier.addListener(_onScrollerIndexChanged);
} }
@override @override
@ -49,7 +49,7 @@ class _ViewerThumbnailPreviewState extends State<ViewerThumbnailPreview> {
@override @override
void dispose() { void dispose() {
_entryIndexNotifier.removeListener(_onScrollerIndexChange); _entryIndexNotifier.removeListener(_onScrollerIndexChanged);
super.dispose(); super.dispose();
} }
@ -64,7 +64,7 @@ class _ViewerThumbnailPreviewState extends State<ViewerThumbnailPreview> {
); );
} }
void _onScrollerIndexChange() => _debouncer(() { void _onScrollerIndexChanged() => _debouncer(() {
if (mounted) { if (mounted) {
JumpToEntryNotification(index: _entryIndexNotifier.value).dispatch(context); JumpToEntryNotification(index: _entryIndexNotifier.value).dispatch(context);
} }

View file

@ -59,8 +59,8 @@ class _PlayTogglerState extends State<PlayToggler> with SingleTickerProviderStat
void _registerWidget(PlayToggler widget) { void _registerWidget(PlayToggler widget) {
final controller = widget.controller; final controller = widget.controller;
if (controller != null) { if (controller != null) {
_subscriptions.add(controller.statusStream.listen(_onStatusChange)); _subscriptions.add(controller.statusStream.listen(_onStatusChanged));
_onStatusChange(controller.status); _onStatusChanged(controller.status);
} }
} }
@ -89,7 +89,7 @@ class _PlayTogglerState extends State<PlayToggler> with SingleTickerProviderStat
); );
} }
void _onStatusChange(VideoStatus status) { void _onStatusChanged(VideoStatus status) {
final status = _playPauseAnimation.status; final status = _playPauseAnimation.status;
if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) { if (isPlaying && status != AnimationStatus.forward && status != AnimationStatus.completed) {
_playPauseAnimation.forward(); _playPauseAnimation.forward();

View file

@ -1,4 +1,5 @@
import 'package:aves/model/actions/entry_actions.dart'; import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_lens.dart';
@ -12,6 +13,7 @@ import 'package:aves/widgets/common/app_bar/quick_choosers/tag_button.dart';
import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/basic/popup_menu_button.dart'; import 'package:aves/widgets/common/basic/popup_menu_button.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/common/quick_actions/action_button.dart';
import 'package:aves/widgets/viewer/action/entry_action_delegate.dart'; import 'package:aves/widgets/viewer/action/entry_action_delegate.dart';
import 'package:aves/widgets/viewer/notifications.dart'; import 'package:aves/widgets/viewer/notifications.dart';
import 'package:aves/widgets/viewer/overlay/common.dart'; import 'package:aves/widgets/viewer/overlay/common.dart';
@ -47,9 +49,17 @@ class ViewerButtons extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (device.isTelevision) {
return _TvButtonRowContent(
scale: scale,
mainEntry: mainEntry,
pageEntry: pageEntry,
collection: collection,
);
}
final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection); final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection);
final trashed = mainEntry.trashed; final trashed = mainEntry.trashed;
return SafeArea( return SafeArea(
top: false, top: false,
bottom: false, bottom: false,
@ -82,6 +92,44 @@ class ViewerButtons extends StatelessWidget {
} }
} }
class _TvButtonRowContent extends StatelessWidget {
final Animation<double> scale;
final AvesEntry mainEntry, pageEntry;
final CollectionLens? collection;
const _TvButtonRowContent({
required this.scale,
required this.mainEntry,
required this.pageEntry,
required this.collection,
});
@override
Widget build(BuildContext context) {
final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...EntryActions.topLevel,
...EntryActions.export,
...EntryActions.video,
].where(actionDelegate.isVisible).map((action) {
// TODO TLAD [tv] togglers cf `_buildOverlayButton`
// TODO TLAD [tv] use `scale`
final enabled = actionDelegate.canApply(action);
return ActionButton(
text: action.getText(context),
icon: action.getIcon(),
enabled: enabled,
onPressed: enabled ? () => actionDelegate.onActionSelected(context, action) : null,
);
}).toList(),
);
}
}
class ViewerButtonRowContent extends StatelessWidget { class ViewerButtonRowContent extends StatelessWidget {
final List<EntryAction> quickActions, topLevelActions, exportActions, videoActions; final List<EntryAction> quickActions, topLevelActions, exportActions, videoActions;
final Animation<double> scale; final Animation<double> scale;
@ -342,7 +390,7 @@ class ViewerButtonRowContent extends StatelessWidget {
} }
PopupMenuItem<EntryAction> _buildRotateAndFlipMenuItems(BuildContext context) { PopupMenuItem<EntryAction> _buildRotateAndFlipMenuItems(BuildContext context) {
final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection); final actionDelegate = _entryActionDelegate;
Widget buildDivider() => const SizedBox( Widget buildDivider() => const SizedBox(
height: 16, height: 16,

View file

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart'; import 'package:aves/model/entry_images.dart';
import 'package:aves/model/panorama.dart'; import 'package:aves/model/panorama.dart';
@ -42,13 +43,13 @@ class _PanoramaPageState extends State<PanoramaPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_overlayVisible.addListener(_onOverlayVisibleChange); _overlayVisible.addListener(_onOverlayVisibleChanged);
WidgetsBinding.instance.addPostFrameCallback((_) => _initOverlay()); WidgetsBinding.instance.addPostFrameCallback((_) => _initOverlay());
} }
@override @override
void dispose() { void dispose() {
_overlayVisible.removeListener(_onOverlayVisibleChange); _overlayVisible.removeListener(_onOverlayVisibleChanged);
super.dispose(); super.dispose();
} }
@ -96,7 +97,22 @@ class _PanoramaPageState extends State<PanoramaPage> {
Positioned( Positioned(
right: 0, right: 0,
bottom: 0, bottom: 0,
child: TooltipTheme( child: _buildOverlay(context),
),
const TopGestureAreaProtector(),
const SideGestureAreaProtector(),
const BottomGestureAreaProtector(),
],
),
resizeToAvoidBottomInset: false,
),
);
}
Widget _buildOverlay(BuildContext context) {
if (device.isTelevision) return const SizedBox();
return TooltipTheme(
data: TooltipTheme.of(context).copyWith( data: TooltipTheme.of(context).copyWith(
preferBelow: false, preferBelow: false,
), ),
@ -132,15 +148,6 @@ class _PanoramaPageState extends State<PanoramaPage> {
); );
}, },
), ),
),
),
const TopGestureAreaProtector(),
const SideGestureAreaProtector(),
const BottomGestureAreaProtector(),
],
),
resizeToAvoidBottomInset: false,
),
); );
} }
@ -169,10 +176,10 @@ class _PanoramaPageState extends State<PanoramaPage> {
// wait for MaterialPageRoute.transitionDuration // wait for MaterialPageRoute.transitionDuration
// to show overlay after page animation is complete // to show overlay after page animation is complete
await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation); await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation);
await _onOverlayVisibleChange(); await _onOverlayVisibleChanged();
} }
Future<void> _onOverlayVisibleChange() async { Future<void> _onOverlayVisibleChanged() async {
if (_overlayVisible.value) { if (_overlayVisible.value) {
await AvesApp.showSystemUI(); await AvesApp.showSystemUI();
AvesApp.setSystemUIStyle(context); AvesApp.setSystemUIStyle(context);

View file

@ -499,14 +499,14 @@ extension ExtraFijkPlayer on FijkPlayer {
await setDataSource(uri, autoPlay: false, showCover: false); await setDataSource(uri, autoPlay: false, showCover: false);
final completer = Completer(); final completer = Completer();
void onChange() { void onChanged() {
switch (state) { switch (state) {
case FijkState.prepared: case FijkState.prepared:
removeListener(onChange); removeListener(onChanged);
completer.complete(); completer.complete();
break; break;
case FijkState.error: case FijkState.error:
removeListener(onChange); removeListener(onChanged);
completer.completeError(value.exception); completer.completeError(value.exception);
break; break;
default: default:
@ -514,7 +514,7 @@ extension ExtraFijkPlayer on FijkPlayer {
} }
} }
addListener(onChange); addListener(onChanged);
await prepareAsync(); await prepareAsync();
return completer.future; return completer.future;
} }

View file

@ -32,7 +32,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
if (entry.isMultiPage) { if (entry.isMultiPage) {
await _initMultiPageController(entry); await _initMultiPageController(entry);
} }
void listener() => _onMetadataChange(entry); void listener() => _onMetadataChanged(entry);
_metadataChangeListeners[entry] = listener; _metadataChangeListeners[entry] = listener;
entry.metadataChangeNotifier.addListener(listener); entry.metadataChangeNotifier.addListener(listener);
} }
@ -49,7 +49,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
} }
} }
void _onMetadataChange(AvesEntry entry) { void _onMetadataChanged(AvesEntry entry) {
debugPrint('reinitialize controllers for entry=$entry because metadata changed'); debugPrint('reinitialize controllers for entry=$entry because metadata changed');
cleanEntryControllers(entry); cleanEntryControllers(entry);
initEntryControllers(entry); initEntryControllers(entry);
@ -149,7 +149,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
videoPageEntries.forEach((entry) => videoConductor.getOrCreateController(entry, maxControllerCount: videoPageEntries.length)); videoPageEntries.forEach((entry) => videoConductor.getOrCreateController(entry, maxControllerCount: videoPageEntries.length));
// auto play/pause when changing page // auto play/pause when changing page
Future<void> _onPageChange() async { Future<void> _onPageChanged() async {
await pauseVideoControllers(); await pauseVideoControllers();
if (videoAutoPlayEnabled || (entry.isMotionPhoto && shouldAutoPlayMotionPhoto)) { if (videoAutoPlayEnabled || (entry.isMotionPhoto && shouldAutoPlayMotionPhoto)) {
final page = multiPageController.page; final page = multiPageController.page;
@ -165,9 +165,9 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
} }
} }
_multiPageControllerPageListeners[multiPageController] = _onPageChange; _multiPageControllerPageListeners[multiPageController] = _onPageChanged;
multiPageController.pageNotifier.addListener(_onPageChange); multiPageController.pageNotifier.addListener(_onPageChanged);
await _onPageChange(); await _onPageChanged();
if (entry.isMotionPhoto && shouldAutoPlayMotionPhoto) { if (entry.isMotionPhoto && shouldAutoPlayMotionPhoto) {
await Future.delayed(Durations.motionPhotoAutoPlayDelay); await Future.delayed(Durations.motionPhotoAutoPlayDelay);

View file

@ -103,7 +103,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
// no bounce at the bottom, to avoid video controller displacement // no bounce at the bottom, to avoid video controller displacement
curve: Curves.easeOutQuad, curve: Curves.easeOutQuad,
); );
_overlayVisible.addListener(_onOverlayVisibleChange); _overlayVisible.addListener(_onOverlayVisibleChanged);
_videoActionDelegate = VideoActionDelegate( _videoActionDelegate = VideoActionDelegate(
collection: null, collection: null,
); );
@ -112,7 +112,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
initialScale: const ScaleLevel(ref: ScaleReference.covered), initialScale: const ScaleLevel(ref: ScaleReference.covered),
); );
initEntryControllers(entry); initEntryControllers(entry);
_onOverlayVisibleChange(); _onOverlayVisibleChanged();
} }
@override @override
@ -121,7 +121,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
_viewerController.dispose(); _viewerController.dispose();
_videoActionDelegate.dispose(); _videoActionDelegate.dispose();
_overlayAnimationController.dispose(); _overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange); _overlayVisible.removeListener(_onOverlayVisibleChanged);
super.dispose(); super.dispose();
} }
@ -232,7 +232,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
// overlay // overlay
Future<void> _onOverlayVisibleChange({bool animate = true}) async { Future<void> _onOverlayVisibleChanged({bool animate = true}) async {
if (_overlayVisible.value) { if (_overlayVisible.value) {
await AvesApp.showSystemUI(); await AvesApp.showSystemUI();
AvesApp.setSystemUIStyle(context); AvesApp.setSystemUIStyle(context);