#1032 screen saver: black background

This commit is contained in:
Thibault Deckers 2024-05-30 01:34:18 +02:00
parent 171258ba2b
commit ece28db3f8
10 changed files with 75 additions and 31 deletions

View file

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Changed ### Changed
- Screen saver: black background, consistent with slideshow
- upgraded Flutter to stable v3.22.1 - upgraded Flutter to stable v3.22.1
### Removed ### Removed

View file

@ -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.ServiceWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler import deckers.thibault.aves.channel.calls.window.WindowHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler 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.channel.streams.MediaStoreStreamHandler
import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.LogUtils
import io.flutter.FlutterInjector 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.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
// for FlutterView-level integration, cf https://docs.flutter.dev/development/add-to-app/android/add-flutter-view // for FlutterView-level integration, cf https://docs.flutter.dev/development/add-to-app/android/add-flutter-view
class ScreenSaverService : DreamService() { class ScreenSaverService : DreamService() {
private var flutterEngine: FlutterEngine? = null private var flutterEngine: FlutterEngine? = null
private var flutterView: FlutterView? = null private var flutterView: FlutterView? = null
private lateinit var mediaSessionHandler: MediaSessionHandler
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
Log.i(LOG_TAG, "onAttachedToWindow") Log.i(LOG_TAG, "onAttachedToWindow")
@ -77,6 +80,7 @@ class ScreenSaverService : DreamService() {
private fun release() { private fun release() {
destroyView() destroyView()
mediaSessionHandler.dispose()
flutterEngine = null flutterEngine = null
flutterView = null flutterView = null
} }
@ -96,12 +100,19 @@ class ScreenSaverService : DreamService() {
private fun initChannels() { private fun initChannels() {
val messenger = flutterEngine!!.dartExecutor val messenger = flutterEngine!!.dartExecutor
// notification: platform -> dart
val mediaCommandStreamHandler = MediaCommandStreamHandler().apply {
EventChannel(messenger, MediaCommandStreamHandler.CHANNEL).setStreamHandler(this)
}
// dart -> platform -> dart // dart -> platform -> dart
// - need Context // - need Context
mediaSessionHandler = MediaSessionHandler(this, mediaCommandStreamHandler)
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this)) MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this)) MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this)) MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this)) MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
MethodChannel(messenger, MediaSessionHandler.CHANNEL).setMethodCallHandler(mediaSessionHandler)
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this)) MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this)) MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this)) MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))

View file

@ -1,4 +1,5 @@
enum AppMode { enum AppMode {
initialization,
main, main,
pickCollectionFiltersExternal, pickCollectionFiltersExternal,
pickSingleMediaExternal, pickSingleMediaExternal,

View file

@ -86,6 +86,8 @@ class AvesApp extends StatefulWidget {
// so that we can react to fullscreen `PageRoute`s only // so that we can react to fullscreen `PageRoute`s only
static final RouteObserver<PageRoute> pageRouteObserver = RouteObserver<PageRoute>(); static final RouteObserver<PageRoute> pageRouteObserver = RouteObserver<PageRoute>();
static ScreenBrightness? get screenBrightness => _AvesAppState._screenBrightness;
const AvesApp({ const AvesApp({
super.key, super.key,
required this.flavor, required this.flavor,
@ -159,7 +161,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
final ValueNotifier<PageTransitionsBuilder> _pageTransitionsBuilderNotifier = ValueNotifier(defaultPageTransitionsBuilder); final ValueNotifier<PageTransitionsBuilder> _pageTransitionsBuilderNotifier = ValueNotifier(defaultPageTransitionsBuilder);
final ValueNotifier<TvMediaQueryModifier?> _tvMediaQueryModifierNotifier = ValueNotifier(null); final ValueNotifier<TvMediaQueryModifier?> _tvMediaQueryModifierNotifier = ValueNotifier(null);
final ValueNotifier<AppMode> _appModeNotifier = ValueNotifier(AppMode.main); final ValueNotifier<AppMode> _appModeNotifier = ValueNotifier(AppMode.initialization);
// observers are not registered when using the same list object with different items // observers are not registered when using the same list object with different items
// the list itself needs to be reassigned // the list itself needs to be reassigned
@ -175,6 +177,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
// - `ZoomPageTransitionsBuilder` on Android 10 / API 29 and above (default in Flutter v3.0.0) // - `ZoomPageTransitionsBuilder` on Android 10 / API 29 and above (default in Flutter v3.0.0)
static const defaultPageTransitionsBuilder = FadeUpwardsPageTransitionsBuilder(); static const defaultPageTransitionsBuilder = FadeUpwardsPageTransitionsBuilder();
static final GlobalKey<NavigatorState> _navigatorKey = GlobalKey(debugLabel: 'app-navigator'); static final GlobalKey<NavigatorState> _navigatorKey = GlobalKey(debugLabel: 'app-navigator');
static ScreenBrightness? _screenBrightness;
@override @override
void initState() { void initState() {
@ -187,6 +190,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
_subscriptions.add(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion())); _subscriptions.add(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()));
_subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?))); _subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?)));
_updateCutoutInsets(); _updateCutoutInsets();
_appModeNotifier.addListener(_onAppModeChanged);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
} }
@ -539,9 +543,9 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
switch (settings.maxBrightness) { switch (settings.maxBrightness) {
case MaxBrightness.never: case MaxBrightness.never:
case MaxBrightness.viewerOnly: case MaxBrightness.viewerOnly:
ScreenBrightness().resetScreenBrightness(); AvesApp.screenBrightness?.resetScreenBrightness();
case MaxBrightness.always: case MaxBrightness.always:
ScreenBrightness().setScreenBrightness(1); AvesApp.screenBrightness?.setScreenBrightness(1);
} }
} }
@ -627,6 +631,20 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
} }
void _onError(String? error) => reportService.recordError(error, null); 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 { class AvesScrollBehavior extends MaterialScrollBehavior {

View file

@ -49,13 +49,7 @@ class InteractiveTile extends StatelessWidget {
case AppMode.pickFilteredMediaInternal: case AppMode.pickFilteredMediaInternal:
case AppMode.pickUnfilteredMediaInternal: case AppMode.pickUnfilteredMediaInternal:
Navigator.maybeOf(context)?.pop(entry); Navigator.maybeOf(context)?.pop(entry);
case AppMode.pickCollectionFiltersExternal: default:
case AppMode.pickFilterInternal:
case AppMode.screenSaver:
case AppMode.setWallpaper:
case AppMode.slideshow:
case AppMode.view:
case AppMode.edit:
break; break;
} }
}, },

View file

@ -10,6 +10,7 @@ import 'package:aves/model/entry/extensions/props.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/theme/durations.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/behaviour/springy_scroll_physics.dart';
import 'package:aves/widgets/common/extensions/theme.dart'; import 'package:aves/widgets/common/extensions/theme.dart';
import 'package:aves/widgets/viewer/action/entry_action_delegate.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/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:screen_brightness/screen_brightness.dart';
class ViewerVerticalPageView extends StatefulWidget { class ViewerVerticalPageView extends StatefulWidget {
final CollectionLens? collection; final CollectionLens? collection;
@ -86,7 +86,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
_registerWidget(widget); _registerWidget(widget);
if (settings.maxBrightness == MaxBrightness.viewerOnly) { if (settings.maxBrightness == MaxBrightness.viewerOnly) {
_systemBrightness = ScreenBrightness().system; _systemBrightness = AvesApp.screenBrightness?.system;
} }
} }
@ -328,7 +328,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
if (settings.maxBrightness == MaxBrightness.viewerOnly) { if (settings.maxBrightness == MaxBrightness.viewerOnly) {
_systemBrightness?.then((system) { _systemBrightness?.then((system) {
final value = lerpDouble(maximumBrightness, system, ((1 - page).abs() * 2).clamp(0, 1))!; final value = lerpDouble(maximumBrightness, system, ((1 - page).abs() * 2).clamp(0, 1))!;
ScreenBrightness().setScreenBrightness(value); AvesApp.screenBrightness?.setScreenBrightness(value);
}); });
} }

View file

@ -45,7 +45,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:screen_brightness/screen_brightness.dart';
class EntryViewerStack extends StatefulWidget { class EntryViewerStack extends StatefulWidget {
final CollectionLens? collection; final CollectionLens? collection;
@ -106,7 +105,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
void initState() { void initState() {
super.initState(); super.initState();
if (settings.maxBrightness == MaxBrightness.viewerOnly) { if (settings.maxBrightness == MaxBrightness.viewerOnly) {
ScreenBrightness().setScreenBrightness(1); AvesApp.screenBrightness?.setScreenBrightness(1);
} }
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) { if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
windowService.keepScreenOn(true); windowService.keepScreenOn(true);
@ -901,15 +900,15 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
switch (settings.maxBrightness) { switch (settings.maxBrightness) {
case MaxBrightness.never: case MaxBrightness.never:
case MaxBrightness.viewerOnly: case MaxBrightness.viewerOnly:
await ScreenBrightness().resetScreenBrightness(); await AvesApp.screenBrightness?.resetScreenBrightness();
case MaxBrightness.always: case MaxBrightness.always:
await ScreenBrightness().setScreenBrightness(1); await AvesApp.screenBrightness?.setScreenBrightness(1);
} }
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) { if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
await windowService.keepScreenOn(false); await windowService.keepScreenOn(false);
} }
await mediaSessionService.release(); await mediaSessionService.release();
await AvesApp.showSystemUI(); await _showSystemUI(context, true);
AvesApp.setSystemUIStyle(theme); AvesApp.setSystemUIStyle(theme);
if (!settings.useTvLayout) { if (!settings.useTvLayout) {
await windowService.requestOrientation(); await windowService.requestOrientation();
@ -950,10 +949,16 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
// overlay // overlay
Future<void> _initOverlay() async { Future<void> _initOverlay() async {
// wait for MaterialPageRoute.transitionDuration final appMode = context.read<ValueNotifier<AppMode>>().value;
// to show overlay after hero animation is complete if (appMode == AppMode.screenSaver) {
await Future.delayed(ModalRoute.of(context)!.transitionDuration * timeDilation); _overlayVisible.value = false;
await _onOverlayVisibleChanged(); 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; _overlayInitialized = true;
} }
@ -963,7 +968,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
if (_viewLocked.value) { if (_viewLocked.value) {
await _startOverlayHidingTimer(); await _startOverlayHidingTimer();
} else { } else {
await AvesApp.showSystemUI(); await _showSystemUI(context, true);
AvesApp.setSystemUIStyle(Theme.of(context)); AvesApp.setSystemUIStyle(Theme.of(context));
} }
if (animate) { if (animate) {
@ -978,7 +983,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
_frozenViewInsets = mediaQuery.viewInsets; _frozenViewInsets = mediaQuery.viewInsets;
_frozenViewPadding = mediaQuery.viewPadding; _frozenViewPadding = mediaQuery.viewPadding;
}); });
await AvesApp.hideSystemUI(); await _showSystemUI(context, false);
if (animate) { if (animate) {
await _overlayAnimationController.reverse(); await _overlayAnimationController.reverse();
} else { } else {
@ -993,10 +998,10 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
Future<void> _onViewLockedChanged() async { Future<void> _onViewLockedChanged() async {
if (_viewLocked.value) { if (_viewLocked.value) {
await AvesApp.hideSystemUI(); await _showSystemUI(context, false);
await _startOverlayHidingTimer(); await _startOverlayHidingTimer();
} else { } else {
await AvesApp.showSystemUI(); await _showSystemUI(context, true);
AvesApp.setSystemUIStyle(Theme.of(context)); AvesApp.setSystemUIStyle(Theme.of(context));
_stopOverlayHidingTimer(); _stopOverlayHidingTimer();
_overlayVisible.value = true; _overlayVisible.value = true;
@ -1010,4 +1015,18 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} }
void _stopOverlayHidingTimer() => _overlayHidingTimer?.cancel(); void _stopOverlayHidingTimer() => _overlayHidingTimer?.cancel();
Future<void> _showSystemUI(BuildContext context, bool show) async {
final appMode = context.read<ValueNotifier<AppMode>>().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();
}
}
} }

View file

@ -488,6 +488,7 @@ class _EntryPageViewState extends State<EntryPageView> with TickerProviderStateM
} }
void _onViewStateChanged(MagnifierState v) { void _onViewStateChanged(MagnifierState v) {
if (!mounted) return;
_viewStateNotifier.value = _viewStateNotifier.value.copyWith( _viewStateNotifier.value = _viewStateNotifier.value.copyWith(
position: v.position, position: v.position,
scale: v.scale, scale: v.scale,

View file

@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/aves_app.dart';
import 'package:decorated_icon/decorated_icon.dart'; import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'package:volume_controller/volume_controller.dart'; import 'package:volume_controller/volume_controller.dart';
enum SwipeAction { brightness, volume } enum SwipeAction { brightness, volume }
@ -12,7 +12,7 @@ extension ExtraSwipeAction on SwipeAction {
Future<double> get() { Future<double> get() {
switch (this) { switch (this) {
case SwipeAction.brightness: case SwipeAction.brightness:
return ScreenBrightness().current; return AvesApp.screenBrightness?.current ?? Future.value(1);
case SwipeAction.volume: case SwipeAction.volume:
return VolumeController().getVolume(); return VolumeController().getVolume();
} }
@ -21,7 +21,7 @@ extension ExtraSwipeAction on SwipeAction {
Future<void> set(double value) async { Future<void> set(double value) async {
switch (this) { switch (this) {
case SwipeAction.brightness: case SwipeAction.brightness:
await ScreenBrightness().setScreenBrightness(value); await AvesApp.screenBrightness?.setScreenBrightness(value);
case SwipeAction.volume: case SwipeAction.volume:
VolumeController().setVolume(value, showSystemUI: false); VolumeController().setVolume(value, showSystemUI: false);
} }

View file

@ -24,7 +24,6 @@ import 'package:aves_model/aves_model.dart';
import 'package:aves_video/aves_video.dart'; import 'package:aves_video/aves_video.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:screen_brightness/screen_brightness.dart';
class WallpaperPage extends StatelessWidget { class WallpaperPage extends StatelessWidget {
static const routeName = '/set_wallpaper'; static const routeName = '/set_wallpaper';
@ -89,7 +88,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
void initState() { void initState() {
super.initState(); super.initState();
if (settings.maxBrightness == MaxBrightness.viewerOnly) { if (settings.maxBrightness == MaxBrightness.viewerOnly) {
ScreenBrightness().setScreenBrightness(1); AvesApp.screenBrightness?.setScreenBrightness(1);
} }
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) { if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
windowService.keepScreenOn(true); windowService.keepScreenOn(true);