From ece28db3f89a1cad01d3e755e258073cfa3be698 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 30 May 2024 01:34:18 +0200 Subject: [PATCH] #1032 screen saver: black background --- CHANGELOG.md | 1 + .../thibault/aves/ScreenSaverService.kt | 11 +++++ lib/app_mode.dart | 1 + lib/widgets/aves_app.dart | 24 ++++++++-- lib/widgets/collection/grid/tile.dart | 8 +--- lib/widgets/viewer/entry_vertical_pager.dart | 6 +-- lib/widgets/viewer/entry_viewer_stack.dart | 45 +++++++++++++------ .../viewer/visual/entry_page_view.dart | 1 + .../viewer/visual/video/swipe_action.dart | 6 +-- lib/widgets/wallpaper_page.dart | 3 +- 10 files changed, 75 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 336ca9b9f..43245ecb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ### Changed +- Screen saver: black background, consistent with slideshow - upgraded Flutter to stable v3.22.1 ### Removed diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt b/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt index be332d36f..931fe9560 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/ScreenSaverService.kt @@ -9,6 +9,7 @@ import deckers.thibault.aves.channel.calls.* import deckers.thibault.aves.channel.calls.window.ServiceWindowHandler import deckers.thibault.aves.channel.calls.window.WindowHandler import deckers.thibault.aves.channel.streams.ImageByteStreamHandler +import deckers.thibault.aves.channel.streams.MediaCommandStreamHandler import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler import deckers.thibault.aves.utils.LogUtils import io.flutter.FlutterInjector @@ -18,12 +19,14 @@ import io.flutter.embedding.android.FlutterView import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodChannel // for FlutterView-level integration, cf https://docs.flutter.dev/development/add-to-app/android/add-flutter-view class ScreenSaverService : DreamService() { private var flutterEngine: FlutterEngine? = null private var flutterView: FlutterView? = null + private lateinit var mediaSessionHandler: MediaSessionHandler override fun onAttachedToWindow() { Log.i(LOG_TAG, "onAttachedToWindow") @@ -77,6 +80,7 @@ class ScreenSaverService : DreamService() { private fun release() { destroyView() + mediaSessionHandler.dispose() flutterEngine = null flutterView = null } @@ -96,12 +100,19 @@ class ScreenSaverService : DreamService() { private fun initChannels() { val messenger = flutterEngine!!.dartExecutor + // notification: platform -> dart + val mediaCommandStreamHandler = MediaCommandStreamHandler().apply { + EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this) + } + // dart -> platform -> dart // - need Context + mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler) MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this)) MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this)) MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this)) MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this)) + MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler) MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this)) MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this)) MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) diff --git a/lib/app_mode.dart b/lib/app_mode.dart index 478afd8b9..950458bc2 100644 --- a/lib/app_mode.dart +++ b/lib/app_mode.dart @@ -1,4 +1,5 @@ enum AppMode { + initialization, main, pickCollectionFiltersExternal, pickSingleMediaExternal, diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart index 1a853f8e1..f274ae169 100644 --- a/lib/widgets/aves_app.dart +++ b/lib/widgets/aves_app.dart @@ -86,6 +86,8 @@ class AvesApp extends StatefulWidget { // so that we can react to fullscreen `PageRoute`s only static final RouteObserver pageRouteObserver = RouteObserver(); + static ScreenBrightness? get screenBrightness => _AvesAppState._screenBrightness; + const AvesApp({ super.key, required this.flavor, @@ -159,7 +161,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { final ValueNotifier _pageTransitionsBuilderNotifier = ValueNotifier(defaultPageTransitionsBuilder); final ValueNotifier _tvMediaQueryModifierNotifier = ValueNotifier(null); - final ValueNotifier _appModeNotifier = ValueNotifier(AppMode.main); + final ValueNotifier _appModeNotifier = ValueNotifier(AppMode.initialization); // observers are not registered when using the same list object with different items // the list itself needs to be reassigned @@ -175,6 +177,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { // - `ZoomPageTransitionsBuilder` on Android 10 / API 29 and above (default in Flutter v3.0.0) static const defaultPageTransitionsBuilder = FadeUpwardsPageTransitionsBuilder(); static final GlobalKey _navigatorKey = GlobalKey(debugLabel: 'app-navigator'); + static ScreenBrightness? _screenBrightness; @override void initState() { @@ -187,6 +190,7 @@ class _AvesAppState extends State with WidgetsBindingObserver { _subscriptions.add(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion())); _subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?))); _updateCutoutInsets(); + _appModeNotifier.addListener(_onAppModeChanged); WidgetsBinding.instance.addObserver(this); } @@ -539,9 +543,9 @@ class _AvesAppState extends State with WidgetsBindingObserver { switch (settings.maxBrightness) { case MaxBrightness.never: case MaxBrightness.viewerOnly: - ScreenBrightness().resetScreenBrightness(); + AvesApp.screenBrightness?.resetScreenBrightness(); case MaxBrightness.always: - ScreenBrightness().setScreenBrightness(1); + AvesApp.screenBrightness?.setScreenBrightness(1); } } @@ -627,6 +631,20 @@ class _AvesAppState extends State with WidgetsBindingObserver { } void _onError(String? error) => reportService.recordError(error, null); + + void _onAppModeChanged() { + final appMode = _appModeNotifier.value; + debugPrint('App mode set to $appMode'); + switch (appMode) { + case AppMode.screenSaver: + // we cannot modify brightness without access to the activity + _screenBrightness = null; + break; + default: + _screenBrightness = ScreenBrightness(); + break; + } + } } class AvesScrollBehavior extends MaterialScrollBehavior { diff --git a/lib/widgets/collection/grid/tile.dart b/lib/widgets/collection/grid/tile.dart index 554c9cb9a..4e8824d0a 100644 --- a/lib/widgets/collection/grid/tile.dart +++ b/lib/widgets/collection/grid/tile.dart @@ -49,13 +49,7 @@ class InteractiveTile extends StatelessWidget { case AppMode.pickFilteredMediaInternal: case AppMode.pickUnfilteredMediaInternal: Navigator.maybeOf(context)?.pop(entry); - case AppMode.pickCollectionFiltersExternal: - case AppMode.pickFilterInternal: - case AppMode.screenSaver: - case AppMode.setWallpaper: - case AppMode.slideshow: - case AppMode.view: - case AppMode.edit: + default: break; } }, diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 5d890dce2..83292311a 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -10,6 +10,7 @@ import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/common/behaviour/springy_scroll_physics.dart'; import 'package:aves/widgets/common/extensions/theme.dart'; import 'package:aves/widgets/viewer/action/entry_action_delegate.dart'; @@ -26,7 +27,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -import 'package:screen_brightness/screen_brightness.dart'; class ViewerVerticalPageView extends StatefulWidget { final CollectionLens? collection; @@ -86,7 +86,7 @@ class _ViewerVerticalPageViewState extends State { _registerWidget(widget); if (settings.maxBrightness == MaxBrightness.viewerOnly) { - _systemBrightness = ScreenBrightness().system; + _systemBrightness = AvesApp.screenBrightness?.system; } } @@ -328,7 +328,7 @@ class _ViewerVerticalPageViewState extends State { if (settings.maxBrightness == MaxBrightness.viewerOnly) { _systemBrightness?.then((system) { final value = lerpDouble(maximumBrightness, system, ((1 - page).abs() * 2).clamp(0, 1))!; - ScreenBrightness().setScreenBrightness(value); + AvesApp.screenBrightness?.setScreenBrightness(value); }); } diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index 1e73c1e8b..17eef5b6c 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -45,7 +45,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -import 'package:screen_brightness/screen_brightness.dart'; class EntryViewerStack extends StatefulWidget { final CollectionLens? collection; @@ -106,7 +105,7 @@ class _EntryViewerStackState extends State with EntryViewContr void initState() { super.initState(); if (settings.maxBrightness == MaxBrightness.viewerOnly) { - ScreenBrightness().setScreenBrightness(1); + AvesApp.screenBrightness?.setScreenBrightness(1); } if (settings.keepScreenOn == KeepScreenOn.viewerOnly) { windowService.keepScreenOn(true); @@ -901,15 +900,15 @@ class _EntryViewerStackState extends State with EntryViewContr switch (settings.maxBrightness) { case MaxBrightness.never: case MaxBrightness.viewerOnly: - await ScreenBrightness().resetScreenBrightness(); + await AvesApp.screenBrightness?.resetScreenBrightness(); case MaxBrightness.always: - await ScreenBrightness().setScreenBrightness(1); + await AvesApp.screenBrightness?.setScreenBrightness(1); } if (settings.keepScreenOn == KeepScreenOn.viewerOnly) { await windowService.keepScreenOn(false); } await mediaSessionService.release(); - await AvesApp.showSystemUI(); + await _showSystemUI(context, true); AvesApp.setSystemUIStyle(theme); if (!settings.useTvLayout) { await windowService.requestOrientation(); @@ -950,10 +949,16 @@ class _EntryViewerStackState extends State with EntryViewContr // overlay Future _initOverlay() async { - // wait for MaterialPageRoute.transitionDuration - // to show overlay after hero animation is complete - await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation); - await _onOverlayVisibleChanged(); + final appMode = context.read>().value; + if (appMode == AppMode.screenSaver) { + _overlayVisible.value = false; + await _onOverlayVisibleChanged(animate: false); + } else { + // wait for MaterialPageRoute.transitionDuration + // to show overlay after hero animation is complete + await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation); + await _onOverlayVisibleChanged(); + } _overlayInitialized = true; } @@ -963,7 +968,7 @@ class _EntryViewerStackState extends State with EntryViewContr if (_viewLocked.value) { await _startOverlayHidingTimer(); } else { - await AvesApp.showSystemUI(); + await _showSystemUI(context, true); AvesApp.setSystemUIStyle(Theme.of(context)); } if (animate) { @@ -978,7 +983,7 @@ class _EntryViewerStackState extends State with EntryViewContr _frozenViewInsets = mediaQuery.viewInsets; _frozenViewPadding = mediaQuery.viewPadding; }); - await AvesApp.hideSystemUI(); + await _showSystemUI(context, false); if (animate) { await _overlayAnimationController.reverse(); } else { @@ -993,10 +998,10 @@ class _EntryViewerStackState extends State with EntryViewContr Future _onViewLockedChanged() async { if (_viewLocked.value) { - await AvesApp.hideSystemUI(); + await _showSystemUI(context, false); await _startOverlayHidingTimer(); } else { - await AvesApp.showSystemUI(); + await _showSystemUI(context, true); AvesApp.setSystemUIStyle(Theme.of(context)); _stopOverlayHidingTimer(); _overlayVisible.value = true; @@ -1010,4 +1015,18 @@ class _EntryViewerStackState extends State with EntryViewContr } void _stopOverlayHidingTimer() => _overlayHidingTimer?.cancel(); + + Future _showSystemUI(BuildContext context, bool show) async { + final appMode = context.read>().value; + if (appMode == AppMode.screenSaver) { + // as of Flutter v3.22.1, calls to `SystemChrome.setEnabledSystemUIMode` hang when app is used as a screen saver + return; + } + + if (show) { + await AvesApp.showSystemUI(); + } else { + await AvesApp.hideSystemUI(); + } + } } diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart index eb8f122d4..32a34248e 100644 --- a/lib/widgets/viewer/visual/entry_page_view.dart +++ b/lib/widgets/viewer/visual/entry_page_view.dart @@ -488,6 +488,7 @@ class _EntryPageViewState extends State with TickerProviderStateM } void _onViewStateChanged(MagnifierState v) { + if (!mounted) return; _viewStateNotifier.value = _viewStateNotifier.value.copyWith( position: v.position, scale: v.scale, diff --git a/lib/widgets/viewer/visual/video/swipe_action.dart b/lib/widgets/viewer/visual/video/swipe_action.dart index 30b049672..435f3da34 100644 --- a/lib/widgets/viewer/visual/video/swipe_action.dart +++ b/lib/widgets/viewer/visual/video/swipe_action.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/aves_app.dart'; import 'package:decorated_icon/decorated_icon.dart'; import 'package:flutter/material.dart'; -import 'package:screen_brightness/screen_brightness.dart'; import 'package:volume_controller/volume_controller.dart'; enum SwipeAction { brightness, volume } @@ -12,7 +12,7 @@ extension ExtraSwipeAction on SwipeAction { Future get() { switch (this) { case SwipeAction.brightness: - return ScreenBrightness().current; + return AvesApp.screenBrightness?.current ?? Future.value(1); case SwipeAction.volume: return VolumeController().getVolume(); } @@ -21,7 +21,7 @@ extension ExtraSwipeAction on SwipeAction { Future set(double value) async { switch (this) { case SwipeAction.brightness: - await ScreenBrightness().setScreenBrightness(value); + await AvesApp.screenBrightness?.setScreenBrightness(value); case SwipeAction.volume: VolumeController().setVolume(value, showSystemUI: false); } diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart index 7313af32b..56b51a35b 100644 --- a/lib/widgets/wallpaper_page.dart +++ b/lib/widgets/wallpaper_page.dart @@ -24,7 +24,6 @@ import 'package:aves_model/aves_model.dart'; import 'package:aves_video/aves_video.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:screen_brightness/screen_brightness.dart'; class WallpaperPage extends StatelessWidget { static const routeName = '/set_wallpaper'; @@ -89,7 +88,7 @@ class _EntryEditorState extends State with EntryViewControllerMixin void initState() { super.initState(); if (settings.maxBrightness == MaxBrightness.viewerOnly) { - ScreenBrightness().setScreenBrightness(1); + AvesApp.screenBrightness?.setScreenBrightness(1); } if (settings.keepScreenOn == KeepScreenOn.viewerOnly) { windowService.keepScreenOn(true);