From e681fc45656c5befb5ab6f7d4c447b9d5666b6cb Mon Sep 17 00:00:00 2001 From: ODevStudio Date: Wed, 19 Aug 2026 20:37:44 +0200 Subject: [PATCH 1/2] fix(skins): restore brightness when skin view closes Reset display brightness to OS-managed mode when SkinView is removed so a sleeping skin cannot leave the native UI unreadably dim. --- doc/Skins.md | 1 + lib/main.dart | 4 +++ lib/src/app.dart | 5 +++ lib/src/skin_feature/skin_view.dart | 3 ++ .../skin_view_brightness_test.dart | 33 +++++++++++++++++++ 5 files changed, 46 insertions(+) create mode 100644 test/unit/skin_feature/skin_view_brightness_test.dart diff --git a/doc/Skins.md b/doc/Skins.md index bd7219751..0d6c72f55 100644 --- a/doc/Skins.md +++ b/doc/Skins.md @@ -1444,6 +1444,7 @@ Returns to auto-managed wake-lock behavior based on machine state. **Wake-Lock Auto-Management:** - When no override is active, wake-lock is automatically enabled when the machine is connected and not sleeping, and disabled when the machine sleeps or disconnects. - Brightness is automatically restored to its pre-sleep value when the machine transitions from sleeping to idle. +- When the embedded skin view closes, the host resets brightness to 100 (OS-managed) before returning to native UI. **Low Battery Brightness Cap:** - When the `lowBatteryBrightnessLimit` setting is enabled (via `POST /api/v1/settings`) and battery drops below 30%, screen brightness is capped at 20. diff --git a/lib/main.dart b/lib/main.dart index 53bfb4f77..016981bea 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -610,6 +610,7 @@ void main(List args) async { decentAccountService: decentAccountService, accountTokensController: accountTokensController, batteryController: batteryController, + displayController: displayController, ), ), ); @@ -825,6 +826,7 @@ class AppRoot extends StatefulWidget { final DecentAccountService? decentAccountService; final AccountTokensController? accountTokensController; final BatteryController? batteryController; + final DisplayController displayController; const AppRoot({ super.key, @@ -850,6 +852,7 @@ class AppRoot extends StatefulWidget { this.decentAccountService, this.accountTokensController, this.batteryController, + required this.displayController, }); static void restart(BuildContext context) { @@ -908,6 +911,7 @@ class _AppRootState extends State { decentAccountService: widget.decentAccountService, accountTokensController: widget.accountTokensController, batteryController: widget.batteryController, + displayController: widget.displayController, ), ); diff --git a/lib/src/app.dart b/lib/src/app.dart index 00cef3358..835f63d10 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -8,6 +8,7 @@ import 'package:reaprime/main.dart'; import 'package:reaprime/src/controllers/account_tokens_controller.dart'; import 'package:reaprime/src/controllers/connection_manager.dart'; import 'package:reaprime/src/controllers/de1_state_manager.dart'; +import 'package:reaprime/src/controllers/display_controller.dart'; import 'package:reaprime/src/controllers/persistence_controller.dart'; import 'package:reaprime/src/controllers/presence_controller.dart'; import 'package:reaprime/src/controllers/presence_navigator_observer.dart'; @@ -87,6 +88,7 @@ class MyApp extends StatefulWidget { required this.webUIStorage, required this.webViewLogService, required this.presenceController, + required this.displayController, required this.connectionManager, required this.scanStateGuardian, this.updateCheckService, @@ -111,6 +113,7 @@ class MyApp extends StatefulWidget { final WebUIStorage webUIStorage; final WebViewLogService webViewLogService; final PresenceController presenceController; + final DisplayController displayController; final ConnectionManager connectionManager; final ScanStateGuardian scanStateGuardian; final UpdateCheckService? updateCheckService; @@ -491,6 +494,8 @@ class _MyAppState extends State { settingsController: widget.settingsController, webViewLogService: widget.webViewLogService, deviceIp: widget.webUIService.deviceIp(), + restoreBrightness: () => + widget.displayController.setBrightness(100), ); default: return OnboardingView( diff --git a/lib/src/skin_feature/skin_view.dart b/lib/src/skin_feature/skin_view.dart index 62a3de4d3..c39f588ae 100644 --- a/lib/src/skin_feature/skin_view.dart +++ b/lib/src/skin_feature/skin_view.dart @@ -69,11 +69,13 @@ class SkinView extends StatefulWidget { required this.settingsController, required this.webViewLogService, required this.deviceIp, + required this.restoreBrightness, }); final SettingsController settingsController; final WebViewLogService webViewLogService; final String deviceIp; + final Future Function() restoreBrightness; static const routeName = '/skin'; @@ -113,6 +115,7 @@ class _SkinViewState extends State with WidgetsBindingObserver { @override void dispose() { _log.fine("disposing"); + unawaited(widget.restoreBrightness()); _blankPageTimer?.cancel(); _blankPageTimer = null; final controller = _webViewController; diff --git a/test/unit/skin_feature/skin_view_brightness_test.dart b/test/unit/skin_feature/skin_view_brightness_test.dart new file mode 100644 index 000000000..2a66e664e --- /dev/null +++ b/test/unit/skin_feature/skin_view_brightness_test.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:reaprime/src/services/webview_log_service.dart'; +import 'package:reaprime/src/settings/settings_controller.dart'; +import 'package:reaprime/src/skin_feature/skin_view.dart'; + +import '../../helpers/mock_settings_service.dart'; + +void main() { + testWidgets('removing the skin view restores OS-managed brightness', ( + tester, + ) async { + var restoreCalls = 0; + final logs = WebViewLogService(logDirectoryPath: '.'); + + await tester.pumpWidget( + MaterialApp( + home: SkinView( + settingsController: SettingsController(MockSettingsService()), + webViewLogService: logs, + deviceIp: '127.0.0.1', + restoreBrightness: () async { + restoreCalls++; + }, + ), + ), + ); + await tester.pumpWidget(const SizedBox.shrink()); + + expect(restoreCalls, 1); + logs.dispose(); + }); +} From 02d04af35f8c41e53ef9cdbebdc478412c1a22f0 Mon Sep 17 00:00:00 2001 From: ODevStudio Date: Fri, 21 Aug 2026 09:31:16 +0200 Subject: [PATCH 2/2] fix(skins): define brightness exit authority --- doc/Skins.md | 3 +- lib/src/app.dart | 3 +- lib/src/skin_feature/skin_view.dart | 7 +- .../skin_view_brightness_test.dart | 68 +++++++++++++------ 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/doc/Skins.md b/doc/Skins.md index 0d6c72f55..310c01b84 100644 --- a/doc/Skins.md +++ b/doc/Skins.md @@ -1444,7 +1444,8 @@ Returns to auto-managed wake-lock behavior based on machine state. **Wake-Lock Auto-Management:** - When no override is active, wake-lock is automatically enabled when the machine is connected and not sleeping, and disabled when the machine sleeps or disconnects. - Brightness is automatically restored to its pre-sleep value when the machine transitions from sleeping to idle. -- When the embedded skin view closes, the host resets brightness to 100 (OS-managed) before returning to native UI. +- When the embedded skin view closes, native UI becomes authoritative and the host resets brightness to 100 (OS-managed), overriding any active REST or WebSocket brightness request. +- Clients that still require fixed brightness must request it again after the skin closes. **Low Battery Brightness Cap:** - When the `lowBatteryBrightnessLimit` setting is enabled (via `POST /api/v1/settings`) and battery drops below 30%, screen brightness is capped at 20. diff --git a/lib/src/app.dart b/lib/src/app.dart index 835f63d10..5ab95e6d4 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -494,8 +494,7 @@ class _MyAppState extends State { settingsController: widget.settingsController, webViewLogService: widget.webViewLogService, deviceIp: widget.webUIService.deviceIp(), - restoreBrightness: () => - widget.displayController.setBrightness(100), + displayController: widget.displayController, ); default: return OnboardingView( diff --git a/lib/src/skin_feature/skin_view.dart b/lib/src/skin_feature/skin_view.dart index c39f588ae..5896b2e98 100644 --- a/lib/src/skin_feature/skin_view.dart +++ b/lib/src/skin_feature/skin_view.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:logging/logging.dart'; import 'package:reaprime/build_info.dart'; +import 'package:reaprime/src/controllers/display_controller.dart'; import 'package:reaprime/src/home_feature/widgets/quick_settings_widget.dart'; import 'package:reaprime/src/services/telemetry/boot_timing.dart'; import 'package:reaprime/src/services/webview_compatibility_checker.dart'; @@ -69,13 +70,13 @@ class SkinView extends StatefulWidget { required this.settingsController, required this.webViewLogService, required this.deviceIp, - required this.restoreBrightness, + required this.displayController, }); final SettingsController settingsController; final WebViewLogService webViewLogService; final String deviceIp; - final Future Function() restoreBrightness; + final DisplayController displayController; static const routeName = '/skin'; @@ -115,7 +116,7 @@ class _SkinViewState extends State with WidgetsBindingObserver { @override void dispose() { _log.fine("disposing"); - unawaited(widget.restoreBrightness()); + unawaited(widget.displayController.setBrightness(100)); _blankPageTimer?.cancel(); _blankPageTimer = null; final controller = _webViewController; diff --git a/test/unit/skin_feature/skin_view_brightness_test.dart b/test/unit/skin_feature/skin_view_brightness_test.dart index 2a66e664e..aea05d611 100644 --- a/test/unit/skin_feature/skin_view_brightness_test.dart +++ b/test/unit/skin_feature/skin_view_brightness_test.dart @@ -1,33 +1,61 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:reaprime/src/controllers/device_controller.dart'; +import 'package:reaprime/src/controllers/display_controller.dart'; import 'package:reaprime/src/services/webview_log_service.dart'; import 'package:reaprime/src/settings/settings_controller.dart'; import 'package:reaprime/src/skin_feature/skin_view.dart'; +import '../../helpers/mock_de1_controller.dart'; import '../../helpers/mock_settings_service.dart'; void main() { - testWidgets('removing the skin view restores OS-managed brightness', ( - tester, - ) async { - var restoreCalls = 0; - final logs = WebViewLogService(logDirectoryPath: '.'); + testWidgets( + 'native UI overrides other brightness requests when skin closes', + (tester) async { + final logs = WebViewLogService(logDirectoryPath: '.'); + final de1Controller = MockDe1Controller( + controller: DeviceController(const []), + ); + final settingsController = SettingsController(MockSettingsService()); + var resetCalls = 0; + final displayController = DisplayController( + de1Controller: de1Controller, + settingsController: settingsController, + setBrightness: (_) async {}, + resetBrightness: () async { + resetCalls++; + }, + enableWakeLock: () async {}, + disableWakeLock: () async {}, + platformSupport: const DisplayPlatformSupport( + brightness: true, + wakeLock: false, + ), + ); + addTearDown(() { + displayController.dispose(); + logs.dispose(); + }); + + await displayController.setBrightness(40); + expect(displayController.currentState.requestedBrightness, 40); - await tester.pumpWidget( - MaterialApp( - home: SkinView( - settingsController: SettingsController(MockSettingsService()), - webViewLogService: logs, - deviceIp: '127.0.0.1', - restoreBrightness: () async { - restoreCalls++; - }, + await tester.pumpWidget( + MaterialApp( + home: SkinView( + settingsController: settingsController, + webViewLogService: logs, + deviceIp: '127.0.0.1', + displayController: displayController, + ), ), - ), - ); - await tester.pumpWidget(const SizedBox.shrink()); + ); + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pump(); - expect(restoreCalls, 1); - logs.dispose(); - }); + expect(displayController.currentState.requestedBrightness, 100); + expect(resetCalls, 1); + }, + ); }