#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();
_appliedLocale = null;
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,
RecentlyAddedFilter.instance,
];
showOverlayOnOpening = false;
showOverlayMinimap = false;
showOverlayThumbnailPreview = false;
viewerGestureSideTapNext = false;
viewerUseCutout = true;
viewerMaxBrightness = false;
videoControls = VideoControls.playSeek;
videoControls = VideoControls.none;
videoGestureDoubleTapTogglePlay = false;
videoGestureSideDoubleTapSeek = false;
enableBin = false;
@ -886,7 +889,7 @@ class Settings extends ChangeNotifier {
// platform settings
void _onPlatformSettingsChange(Map? fields) {
void _onPlatformSettingsChanged(Map? fields) {
fields?.forEach((key, value) {
switch (key) {
case platformAccelerometerRotationKey:

View file

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

View file

@ -154,7 +154,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
_screenSize = _getScreenSize();
_shouldUseBoldFontLoader = AccessibilityService.shouldUseBoldFont();
_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?));
_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion());
_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?));
@ -451,7 +451,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
_mediaStoreSource.updateDerivedFilters();
}
void _onMediaStoreChange(String? uri) {
void _onMediaStoreChanged(String? uri) {
if (uri != null) _changedUris.add(uri);
if (_changedUris.isNotEmpty) {
_mediaStoreChangeDebouncer(() async {
@ -460,7 +460,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
final tempUris = await _mediaStoreSource.refreshUris(todo);
if (tempUris.isNotEmpty) {
_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,
vsync: this,
);
_isSelectingNotifier.addListener(_onActivityChange);
_isSelectingNotifier.addListener(_onActivityChanged);
_registerWidget(widget);
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -121,8 +121,10 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
@override
void dispose() {
_unregisterWidget(widget);
_queryBarFocusNode.dispose();
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
_isSelectingNotifier.removeListener(_onActivityChange);
_isSelectingNotifier.removeListener(_onActivityChanged);
_isSelectingNotifier.dispose();
_browseToSelectAnimation.dispose();
_subscriptions
..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) {
_browseToSelectAnimation.forward();
} else {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -132,12 +132,12 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
parent: _overlayAnimationController,
curve: Curves.easeOutQuad,
);
_overlayVisible.addListener(_onOverlayVisibleChange);
_overlayVisible.addListener(_onOverlayVisibleChanged);
_subscriptions.add(_mapController.idleUpdates.listen((event) => _onIdle(event.bounds)));
_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), () {
final regionEntries = regionCollection?.sortedEntries ?? [];
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
@ -161,9 +161,9 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
_dotEntryNotifier.removeListener(_onSelectedEntryChanged);
_overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange);
_overlayVisible.removeListener(_onOverlayVisibleChanged);
_mapController.dispose();
_selectedIndexNotifier.removeListener(_onThumbnailIndexChange);
_selectedIndexNotifier.removeListener(_onThumbnailIndexChanged);
regionCollection?.dispose();
super.dispose();
}
@ -380,7 +380,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
: 0;
_selectedIndexNotifier.value = selectedIndex;
// force update, as the region entries may change without a change of index
_onThumbnailIndexChange();
_onThumbnailIndexChanged();
}
AvesEntry? _getRegionEntry(int? index) {
@ -395,7 +395,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
void _onThumbnailTap(int index) => _goToViewer(_getRegionEntry(index));
void _onThumbnailIndexChange() => _onEntrySelected(_getRegionEntry(_selectedIndexNotifier.value));
void _onThumbnailIndexChanged() => _onEntrySelected(_getRegionEntry(_selectedIndexNotifier.value));
void _onEntrySelected(AvesEntry? selectedEntry) {
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
@ -457,7 +457,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
void _toggleOverlay() => _overlayVisible.value = !_overlayVisible.value;
Future<void> _onOverlayVisibleChange({bool animate = true}) async {
Future<void> _onOverlayVisibleChanged({bool animate = true}) async {
if (_overlayVisible.value) {
if (animate) {
await _overlayAnimationController.forward();

View file

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

View file

@ -57,11 +57,11 @@ class _AppBottomNavBarState extends State<AppBottomNavBar> {
}
void _registerWidget(AppBottomNavBar widget) {
widget.currentCollection?.filterChangeNotifier.addListener(_onCollectionFilterChange);
widget.currentCollection?.filterChangeNotifier.addListener(_onCollectionFilterChanged);
}
void _unregisterWidget(AppBottomNavBar widget) {
widget.currentCollection?.filterChangeNotifier.removeListener(_onCollectionFilterChange);
widget.currentCollection?.filterChangeNotifier.removeListener(_onCollectionFilterChanged);
}
@override
@ -112,7 +112,7 @@ class _AppBottomNavBarState extends State<AppBottomNavBar> {
);
}
void _onCollectionFilterChange() => setState(() {});
void _onCollectionFilterChanged() => setState(() {});
int _getCurrentIndex(BuildContext context, List<AvesBottomNavItem> items) {
// 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/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/common/tiles.dart';
@ -19,6 +20,7 @@ class ViewerOverlayPage extends StatelessWidget {
body: SafeArea(
child: ListView(
children: [
if (!device.isTelevision)
SettingsSwitchListTile(
selector: (context, s) => s.showOverlayOnOpening,
onChanged: (v) => settings.showOverlayOnOpening = v,
@ -54,11 +56,13 @@ class ViewerOverlayPage extends StatelessWidget {
);
},
),
if (!device.isTelevision)
SettingsSwitchListTile(
selector: (context, s) => s.showOverlayMinimap,
onChanged: (v) => settings.showOverlayMinimap = v,
title: context.l10n.settingsViewerShowMinimap,
),
if (!device.isTelevision)
SettingsSwitchListTile(
selector: (context, s) => s.showOverlayThumbnailPreview,
onChanged: (v) => settings.showOverlayThumbnailPreview = v,

View file

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

View file

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

View file

@ -3,6 +3,8 @@ import 'dart:math';
import 'dart:ui';
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/settings/settings.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/info/info_page.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:flutter/gestures.dart';
import 'package:flutter/material.dart';
@ -50,6 +53,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
final List<StreamSubscription> _subscriptions = [];
final ValueNotifier<double> _backgroundOpacityNotifier = ValueNotifier(1);
final ValueNotifier<bool> _isVerticallyScrollingNotifier = ValueNotifier(false);
final ValueNotifier<bool> _isImageFocusedNotifier = ValueNotifier(true);
Timer? _verticalScrollMonitoringTimer;
AvesEntry? _oldEntry;
Future<double>? _systemBrightness;
@ -83,6 +87,9 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
void dispose() {
_unregisterWidget(widget);
_stopScrollMonitoringTimer();
_backgroundOpacityNotifier.dispose();
_isVerticallyScrollingNotifier.dispose();
_isImageFocusedNotifier.dispose();
super.dispose();
}
@ -174,10 +181,19 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
}
Widget _buildImagePage() {
final isTelevision = device.isTelevision;
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) {
shortcuts.addAll(const {
SingleActivator(LogicalKeyboardKey.arrowLeft): ShowPreviousIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight): ShowNextIntent(),
});
child = MultiEntryScroller(
collection: collection!,
viewerController: widget.viewerController,
@ -185,23 +201,28 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
onPageChanged: widget.onHorizontalPageChanged,
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) {
child = SingleEntryScroller(
viewerController: widget.viewerController,
entry: entry!,
);
shortcuts = const {
SingleActivator(LogicalKeyboardKey.arrowUp): LeaveIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): ShowInfoIntent(),
};
}
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(
autofocus: true,
shortcuts: shortcuts,
@ -209,8 +230,25 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
ShowPreviousIntent: 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)),
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,
);
}
@ -311,3 +349,11 @@ class LeaveIntent extends Intent {
class ShowInfoIntent extends Intent {
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);
_currentVerticalPage = ValueNotifier(imagePage);
_horizontalPager = PageController(initialPage: _currentEntryIndex);
_verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChange);
_verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChanged);
_overlayAnimationController = AnimationController(
duration: context.read<DurationsData>().viewerOverlayAnimation,
vsync: this,
@ -142,7 +142,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
curve: Curves.easeOutQuad,
));
_overlayVisible.value = settings.showOverlayOnOpening && !viewerController.autopilot;
_overlayVisible.addListener(_onOverlayVisibleChange);
_overlayVisible.addListener(_onOverlayVisibleChanged);
_videoActionDelegate = VideoActionDelegate(
collection: collection,
);
@ -164,19 +164,19 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
cleanEntryControllers(entryNotifier.value);
_videoActionDelegate.dispose();
_overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange);
_verticalPager.removeListener(_onVerticalPageControllerChange);
_overlayVisible.removeListener(_onOverlayVisibleChanged);
_verticalPager.removeListener(_onVerticalPageControllerChanged);
WidgetsBinding.instance.removeObserver(this);
_unregisterWidget(widget);
super.dispose();
}
void _registerWidget(EntryViewerStack widget) {
widget.collection?.addListener(_onCollectionChange);
widget.collection?.addListener(_onCollectionChanged);
}
void _unregisterWidget(EntryViewerStack widget) {
widget.collection?.removeListener(_onCollectionChange);
widget.collection?.removeListener(_onCollectionChanged);
}
@override
@ -199,13 +199,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
final viewStateConductor = context.read<ViewStateConductor>();
return WillPopScope(
onWillPop: () {
if (_currentVerticalPage.value == infoPage) {
// back from info to image
_goToVerticalPage(imagePage);
} else {
if (!_isEntryTracked) _trackEntry();
_popVisual();
}
_onWillPop();
return SynchronousFuture(false);
},
child: ValueListenableProvider<HeroInfo?>.value(
@ -243,10 +237,19 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
}
} else if (notification is ToggleOverlayNotification) {
_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
FocusManager.instance.primaryFocus?.unfocus();
_goToVerticalPage(infoPage);
_showInfoPage();
} else if (notification is JumpToPreviousEntryNotification) {
_jumpToHorizontalPageByDelta(-1);
} else if (notification is JumpToNextEntryNotification) {
@ -458,7 +461,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
builder: (context, mqHeight, child) {
// 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
WidgetsBinding.instance.addPostFrameCallback((_) => _onVerticalPageControllerChange());
WidgetsBinding.instance.addPostFrameCallback((_) => _onVerticalPageControllerChanged());
return AnimatedBuilder(
animation: _verticalScrollNotifier,
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) {
_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 {
final animationDuration = context.read<DurationsData>().viewerVerticalPageScrollAnimation;
if (animationDuration > Duration.zero) {
@ -574,7 +583,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
_updateEntry();
}
void _onCollectionChange() {
void _onCollectionChanged() {
_updateEntry();
}
@ -588,7 +597,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
if (index != -1) {
_onHorizontalPageChanged(index);
}
_onCollectionChange();
_onCollectionChanged();
}
}
@ -600,7 +609,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
final collectionEntries = collection!.sortedEntries;
removedEntries.forEach(collectionEntries.remove);
if (collectionEntries.isNotEmpty) {
_onCollectionChange();
_onCollectionChanged();
return;
}
}
@ -630,6 +639,16 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
await initEntryControllers(newEntry);
}
void _onWillPop() {
if (_currentVerticalPage.value == infoPage) {
// back from info to image
_goToVerticalPage(imagePage);
} else {
if (!_isEntryTracked) _trackEntry();
_popVisual();
}
}
void _popVisual() {
if (Navigator.canPop(context)) {
void pop() {
@ -689,11 +708,11 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
// wait for MaterialPageRoute.transitionDuration
// to show overlay after hero animation is complete
await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation);
await _onOverlayVisibleChange();
await _onOverlayVisibleChanged();
_overlayInitialized = true;
}
Future<void> _onOverlayVisibleChange({bool animate = true}) async {
Future<void> _onOverlayVisibleChanged({bool animate = true}) async {
if (_overlayVisible.value) {
await AvesApp.showSystemUI();
AvesApp.setSystemUIStyle(context);

View file

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

View file

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

View file

@ -1,17 +1,22 @@
import 'dart:math';
import 'package:aves/app_mode.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/source/collection_lens.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/notifications.dart';
import 'package:aves/widgets/viewer/overlay/multipage.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/wallpaper_buttons.dart';
import 'package:aves/widgets/viewer/page_entry_builder.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
@ -119,11 +124,30 @@ class _BottomOverlayContent extends StatefulWidget {
}
class _BottomOverlayContentState extends State<_BottomOverlayContent> {
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
late Animation<double> _buttonScale, _thumbnailOpacity;
@override
void 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;
_buttonScale = CurvedAnimation(
parent: animationController,
@ -134,6 +158,11 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
parent: animationController,
curve: Curves.easeOutQuad,
);
animationController.addStatusListener(_onAnimationStatusChanged);
}
void _unregisterWidget(_BottomOverlayContent widget) {
widget.animationController.removeStatusListener(_onAnimationStatusChanged);
}
@override
@ -153,7 +182,11 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
selector: (context, mq) => mq.size.width,
builder: (context, mqWidth, child) {
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,
bottom: false,
minimum: EdgeInsets.only(
@ -171,6 +204,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
collection: widget.collection,
scale: _buttonScale,
),
),
);
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) {
widget.controller.pageNotifier.addListener(_onPageChange);
widget.controller.pageNotifier.addListener(_onPageChanged);
}
void _unregisterWidget(MultiPageOverlay widget) {
widget.controller.pageNotifier.removeListener(_onPageChange);
widget.controller.pageNotifier.removeListener(_onPageChanged);
}
@override
@ -74,7 +74,7 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
);
}
void _onPageChange() {
void _onPageChanged() {
if (_previousPage != null) {
final info = controller.info;
if (info != null) {

View file

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

View file

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

View file

@ -1,4 +1,5 @@
import 'package:aves/model/actions/entry_actions.dart';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/settings.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/popup_menu_button.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/notifications.dart';
import 'package:aves/widgets/viewer/overlay/common.dart';
@ -47,9 +49,17 @@ class ViewerButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (device.isTelevision) {
return _TvButtonRowContent(
scale: scale,
mainEntry: mainEntry,
pageEntry: pageEntry,
collection: collection,
);
}
final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection);
final trashed = mainEntry.trashed;
return SafeArea(
top: 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 {
final List<EntryAction> quickActions, topLevelActions, exportActions, videoActions;
final Animation<double> scale;
@ -342,7 +390,7 @@ class ViewerButtonRowContent extends StatelessWidget {
}
PopupMenuItem<EntryAction> _buildRotateAndFlipMenuItems(BuildContext context) {
final actionDelegate = EntryActionDelegate(mainEntry, pageEntry, collection);
final actionDelegate = _entryActionDelegate;
Widget buildDivider() => const SizedBox(
height: 16,

View file

@ -1,5 +1,6 @@
import 'dart:math';
import 'package:aves/model/device.dart';
import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart';
import 'package:aves/model/panorama.dart';
@ -42,13 +43,13 @@ class _PanoramaPageState extends State<PanoramaPage> {
@override
void initState() {
super.initState();
_overlayVisible.addListener(_onOverlayVisibleChange);
_overlayVisible.addListener(_onOverlayVisibleChanged);
WidgetsBinding.instance.addPostFrameCallback((_) => _initOverlay());
}
@override
void dispose() {
_overlayVisible.removeListener(_onOverlayVisibleChange);
_overlayVisible.removeListener(_onOverlayVisibleChanged);
super.dispose();
}
@ -96,7 +97,22 @@ class _PanoramaPageState extends State<PanoramaPage> {
Positioned(
right: 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(
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
// to show overlay after page animation is complete
await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation);
await _onOverlayVisibleChange();
await _onOverlayVisibleChanged();
}
Future<void> _onOverlayVisibleChange() async {
Future<void> _onOverlayVisibleChanged() async {
if (_overlayVisible.value) {
await AvesApp.showSystemUI();
AvesApp.setSystemUIStyle(context);

View file

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

View file

@ -32,7 +32,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
if (entry.isMultiPage) {
await _initMultiPageController(entry);
}
void listener() => _onMetadataChange(entry);
void listener() => _onMetadataChanged(entry);
_metadataChangeListeners[entry] = 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');
cleanEntryControllers(entry);
initEntryControllers(entry);
@ -149,7 +149,7 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
videoPageEntries.forEach((entry) => videoConductor.getOrCreateController(entry, maxControllerCount: videoPageEntries.length));
// auto play/pause when changing page
Future<void> _onPageChange() async {
Future<void> _onPageChanged() async {
await pauseVideoControllers();
if (videoAutoPlayEnabled || (entry.isMotionPhoto && shouldAutoPlayMotionPhoto)) {
final page = multiPageController.page;
@ -165,9 +165,9 @@ mixin EntryViewControllerMixin<T extends StatefulWidget> on State<T> {
}
}
_multiPageControllerPageListeners[multiPageController] = _onPageChange;
multiPageController.pageNotifier.addListener(_onPageChange);
await _onPageChange();
_multiPageControllerPageListeners[multiPageController] = _onPageChanged;
multiPageController.pageNotifier.addListener(_onPageChanged);
await _onPageChanged();
if (entry.isMotionPhoto && shouldAutoPlayMotionPhoto) {
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
curve: Curves.easeOutQuad,
);
_overlayVisible.addListener(_onOverlayVisibleChange);
_overlayVisible.addListener(_onOverlayVisibleChanged);
_videoActionDelegate = VideoActionDelegate(
collection: null,
);
@ -112,7 +112,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
initialScale: const ScaleLevel(ref: ScaleReference.covered),
);
initEntryControllers(entry);
_onOverlayVisibleChange();
_onOverlayVisibleChanged();
}
@override
@ -121,7 +121,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
_viewerController.dispose();
_videoActionDelegate.dispose();
_overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChange);
_overlayVisible.removeListener(_onOverlayVisibleChanged);
super.dispose();
}
@ -232,7 +232,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
// overlay
Future<void> _onOverlayVisibleChange({bool animate = true}) async {
Future<void> _onOverlayVisibleChanged({bool animate = true}) async {
if (_overlayVisible.value) {
await AvesApp.showSystemUI();
AvesApp.setSystemUIStyle(context);