Dark/bright: Auto (Android's dark theme), Dark or Bright, kept across restarts
Settings has a segmented Auto / Dark / Bright. Auto follows platformBrightness, so Android's schedule too. The Dash's quick button holds the other variant; a long press is auto again. The mode is kept with shared_preferences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,12 @@ chosen readouts.
|
|||||||
|
|
||||||
## 5. Dark/bright: automatic or fixed, the Android way
|
## 5. Dark/bright: automatic or fixed, the Android way
|
||||||
|
|
||||||
|
**Done (2026-10-15):** Settings has Auto / Dark / Bright (a segmented control, kept across
|
||||||
|
restarts). Auto follows Android's dark theme (`platformBrightness`, so its schedule too). The
|
||||||
|
Dash's button holds the other variant, and a long press returns it to auto. Not done: the
|
||||||
|
ambient light sensor as an auto source, and moving the button into the banner (item 1).
|
||||||
|
What was asked:
|
||||||
|
|
||||||
Today the variant is a switch (the Dash's top-right button, and Settings). Wanted:
|
Today the variant is a switch (the Dash's top-right button, and Settings). Wanted:
|
||||||
|
|
||||||
- **Auto or fixed**, chosen in Settings. Auto could follow Android's own dark mode
|
- **Auto or fixed**, chosen in Settings. Auto could follow Android's own dark mode
|
||||||
|
|||||||
+4
-1
@@ -16,7 +16,7 @@ pirate-station-aide's (main views, then the rarely used ones grouped as Tools).
|
|||||||
| `pages/` | One file per view: `dash`, `signals` (values and raw traffic), `checklist` (Captures), `camera`, `link_test`, `settings` |
|
| `pages/` | One file per view: `dash`, `signals` (values and raw traffic), `checklist` (Captures), `camera`, `link_test`, `settings` |
|
||||||
| `actions/` | Things done from anywhere, as dialogs and sheets: `marks` (free-form mark), `orin_power` (power off / reboot), `server_url` (gateway address) |
|
| `actions/` | Things done from anywhere, as dialogs and sheets: `marks` (free-form mark), `orin_power` (power off / reboot), `server_url` (gateway address) |
|
||||||
| `link/` | Talking to the Orin: `boundary` (the protocol client), `net` (binding to the USB link), `reading` (one signal's state as a display needs it) |
|
| `link/` | Talking to the Orin: `boundary` (the protocol client), `net` (binding to the USB link), `reading` (one signal's state as a display needs it) |
|
||||||
| `theme/` | `palette.dart`: the palette roles and `hudFont` |
|
| `theme/` | `palette.dart`: the palette roles, `hudFont`, and `Themes` (which variant shows: auto or held, kept across restarts) |
|
||||||
| `widgets/` | Reusable display widgets, one per file, each fed values, never the boundary (`link_status` takes it whole: it shows the link) |
|
| `widgets/` | Reusable display widgets, one per file, each fed values, never the boundary (`link_status` takes it whole: it shows the link) |
|
||||||
|
|
||||||
## Widgets
|
## Widgets
|
||||||
@@ -40,6 +40,9 @@ pirate-station-aide's (main views, then the rarely used ones grouped as Tools).
|
|||||||
and the state gradient `good` → `caution` → `alarm`; plus the derived `surface` (raised bars) and
|
and the state gradient `good` → `caution` → `alarm`; plus the derived `surface` (raised bars) and
|
||||||
`onFill(colour)` (text on a fill). No widget names a colour. Themes are JSON in `assets/themes/`, `rei` by
|
`onFill(colour)` (text on a fill). No widget names a colour. Themes are JSON in `assets/themes/`, `rei` by
|
||||||
default; smart-serow's four-key files still load (their `highlight` is `alarm`).
|
default; smart-serow's four-key files still load (their `highlight` is `alarm`).
|
||||||
|
- **Dark or bright.** `Themes.mode` is `auto` (Android's dark theme, schedule included), `dark` or
|
||||||
|
`bright`, chosen in Settings and kept across restarts. The Dash's quick button holds the other variant;
|
||||||
|
a long press there is auto again.
|
||||||
- **Cosmetic fonts are opt-in.** A widget that wants the HUD face names `fontFamily: hudFont` in its own
|
- **Cosmetic fonts are opt-in.** A widget that wants the HUD face names `fontFamily: hudFont` in its own
|
||||||
`TextStyle`; everything else keeps the basic font, which covers CJK where DIN 1451 does not.
|
`TextStyle`; everything else keeps the basic font, which covers CJK where DIN 1451 does not.
|
||||||
- **Missing is a state** (app guideline 2): a widget given no value draws "—" or subdued, never an error.
|
- **Missing is a state** (app guideline 2): a widget given no value draws "—" or subdued, never an error.
|
||||||
|
|||||||
+13
-7
@@ -164,7 +164,7 @@ class DashPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The dash's own thin top line: the menu, link state, and the dark/bright switch.
|
/// The dash's own thin top line: the menu, link state, and the dark/bright quick switch.
|
||||||
class _DashBar extends StatelessWidget {
|
class _DashBar extends StatelessWidget {
|
||||||
const _DashBar({required this.boundary, required this.themes});
|
const _DashBar({required this.boundary, required this.themes});
|
||||||
final Boundary boundary;
|
final Boundary boundary;
|
||||||
@@ -185,12 +185,18 @@ class _DashBar extends StatelessWidget {
|
|||||||
MenuButton(color: p.subdued),
|
MenuButton(color: p.subdued),
|
||||||
Text(text, style: TextStyle(color: color, letterSpacing: 2)),
|
Text(text, style: TextStyle(color: color, letterSpacing: 2)),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
// Tap: hold the other variant. Long press: back to auto (Settings has both).
|
||||||
tooltip: themes.isDark ? 'Bright' : 'Dark',
|
GestureDetector(
|
||||||
onPressed: themes.toggle,
|
onLongPress: () => themes.mode = VariantMode.auto,
|
||||||
icon: Icon(
|
child: IconButton(
|
||||||
themes.isDark ? Icons.light_mode : Icons.dark_mode,
|
tooltip: themes.isDark ? 'Bright' : 'Dark',
|
||||||
color: p.subdued,
|
onPressed: themes.toggle,
|
||||||
|
icon: Icon(
|
||||||
|
themes.mode == VariantMode.auto
|
||||||
|
? Icons.brightness_auto
|
||||||
|
: (themes.isDark ? Icons.light_mode : Icons.dark_mode),
|
||||||
|
color: p.subdued,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
+31
-7
@@ -52,14 +52,38 @@ class SettingsPage extends StatelessWidget {
|
|||||||
onTap: () => editUrl(context, boundary, usb),
|
onTap: () => editUrl(context, boundary, usb),
|
||||||
),
|
),
|
||||||
section('Display'),
|
section('Display'),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: Icon(
|
leading: Icon(themes.isDark ? Icons.dark_mode : Icons.light_mode),
|
||||||
themes.isDark ? Icons.dark_mode : Icons.light_mode,
|
title: const Text('Dark or bright'),
|
||||||
|
subtitle: const Text(
|
||||||
|
"Auto follows Android's dark theme and its schedule. "
|
||||||
|
"The Dash's button holds one; a long press there is auto again.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(72, 0, 16, 8),
|
||||||
|
child: SegmentedButton<VariantMode>(
|
||||||
|
segments: const [
|
||||||
|
ButtonSegment(
|
||||||
|
value: VariantMode.auto,
|
||||||
|
icon: Icon(Icons.brightness_auto),
|
||||||
|
label: Text('Auto'),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: VariantMode.dark,
|
||||||
|
icon: Icon(Icons.dark_mode),
|
||||||
|
label: Text('Dark'),
|
||||||
|
),
|
||||||
|
ButtonSegment(
|
||||||
|
value: VariantMode.bright,
|
||||||
|
icon: Icon(Icons.light_mode),
|
||||||
|
label: Text('Bright'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: {themes.mode},
|
||||||
|
showSelectedIcon: false,
|
||||||
|
onSelectionChanged: (s) => themes.mode = s.single,
|
||||||
),
|
),
|
||||||
title: const Text('Dark variant'),
|
|
||||||
subtitle: const Text('Bright for daylight, dark for night'),
|
|
||||||
value: themes.isDark,
|
|
||||||
onChanged: (v) => themes.isDark = v,
|
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.palette),
|
leading: const Icon(Icons.palette),
|
||||||
|
|||||||
+60
-12
@@ -20,6 +20,7 @@ import 'dart:math' show max, min;
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
/// The HUD face (smart-serow's DIN 1451). Opt-in: a widget names it in its own
|
/// The HUD face (smart-serow's DIN 1451). Opt-in: a widget names it in its own
|
||||||
/// TextStyle; everything else keeps the basic font, which covers CJK where DIN does not.
|
/// TextStyle; everything else keeps the basic font, which covers CJK where DIN does not.
|
||||||
@@ -116,29 +117,70 @@ class Palette {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A theme: both variants, and which one is showing. Listen to it to follow a switch.
|
/// Which variant shows: Android's own dark theme (auto), or one held fixed.
|
||||||
class Themes extends ChangeNotifier {
|
enum VariantMode { auto, dark, bright }
|
||||||
Themes(this.name, this.dark, this.bright);
|
|
||||||
|
/// A theme: both variants, and which one is showing. Listen to it to follow a switch,
|
||||||
|
/// and, in auto, Android's dark theme turning on or off (by hand or on its schedule).
|
||||||
|
/// The mode is kept across restarts.
|
||||||
|
class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
||||||
|
Themes(this.name, this.dark, this.bright, [this._prefs]) {
|
||||||
|
_mode =
|
||||||
|
VariantMode.values.asNameMap()[_prefs?.getString(_key)] ??
|
||||||
|
VariantMode.auto;
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _key = 'variant_mode';
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final Palette dark;
|
final Palette dark;
|
||||||
final Palette bright;
|
final Palette bright;
|
||||||
bool _isDark = true;
|
final SharedPreferences? _prefs;
|
||||||
|
late VariantMode _mode;
|
||||||
|
|
||||||
bool get isDark => _isDark;
|
VariantMode get mode => _mode;
|
||||||
Palette get current => _isDark ? dark : bright;
|
|
||||||
|
|
||||||
set isDark(bool v) {
|
set mode(VariantMode v) {
|
||||||
if (v == _isDark) return;
|
if (v == _mode) return;
|
||||||
_isDark = v;
|
_mode = v;
|
||||||
|
_prefs?.setString(_key, v.name);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggle() => isDark = !_isDark;
|
bool get isDark => switch (_mode) {
|
||||||
|
VariantMode.auto =>
|
||||||
|
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||||
|
Brightness.dark,
|
||||||
|
VariantMode.dark => true,
|
||||||
|
VariantMode.bright => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Palette get current => isDark ? dark : bright;
|
||||||
|
|
||||||
|
/// The quick switch: hold the other variant fixed.
|
||||||
|
void toggle() => mode = isDark ? VariantMode.bright : VariantMode.dark;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangePlatformBrightness() {
|
||||||
|
if (_mode == VariantMode.auto) notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/// [themeName], falling back to `default`, then to the hard-coded palette
|
/// [themeName], falling back to `default`, then to the hard-coded palette
|
||||||
/// (smart-serow's chain).
|
/// (smart-serow's chain).
|
||||||
static Future<Themes> load([String name = themeName]) async {
|
static Future<Themes> load([String name = themeName]) async {
|
||||||
|
SharedPreferences? prefs;
|
||||||
|
try {
|
||||||
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
} catch (_) {
|
||||||
|
// No storage: the mode starts at auto each time.
|
||||||
|
}
|
||||||
for (final n in {name, 'default'}) {
|
for (final n in {name, 'default'}) {
|
||||||
try {
|
try {
|
||||||
final j = jsonDecode(
|
final j = jsonDecode(
|
||||||
@@ -148,19 +190,25 @@ class Themes extends ChangeNotifier {
|
|||||||
n,
|
n,
|
||||||
Palette._fromJson(j['dark'] as Map<String, dynamic>),
|
Palette._fromJson(j['dark'] as Map<String, dynamic>),
|
||||||
Palette._fromJson(j['bright'] as Map<String, dynamic>),
|
Palette._fromJson(j['bright'] as Map<String, dynamic>),
|
||||||
|
prefs,
|
||||||
);
|
);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Missing or malformed: try the next one.
|
// Missing or malformed: try the next one.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Themes('fallback', Palette.fallbackDark, Palette.fallbackBright);
|
return Themes(
|
||||||
|
'fallback',
|
||||||
|
Palette.fallbackDark,
|
||||||
|
Palette.fallbackBright,
|
||||||
|
prefs,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Material's theme for the rest of the app, from the same roles: buttons and
|
/// Material's theme for the rest of the app, from the same roles: buttons and
|
||||||
/// selection in accent, errors in alarm, raised surfaces in surface.
|
/// selection in accent, errors in alarm, raised surfaces in surface.
|
||||||
ThemeData material() {
|
ThemeData material() {
|
||||||
final p = current;
|
final p = current;
|
||||||
final brightness = _isDark ? Brightness.dark : Brightness.light;
|
final brightness = isDark ? Brightness.dark : Brightness.light;
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
|
|||||||
+134
-1
@@ -57,6 +57,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.3"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -75,6 +91,11 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -139,6 +160,102 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: "1e85e0dccf5b4a016077fea7dfbb4a7cb39c759ac1596fcf60c06deadb7da91f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.6"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.28"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.7"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.3"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -208,6 +325,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.3.0"
|
version: "15.3.0"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.13.1 <4.0.0"
|
dart: ">=3.13.1 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.44.0"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
|
shared_preferences: ^2.5.6
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:display/theme/palette.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final dispatcher = TestWidgetsFlutterBinding.instance.platformDispatcher;
|
||||||
|
tearDown(dispatcher.clearPlatformBrightnessTestValue);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'auto follows Android, the quick switch holds one, and it is kept',
|
||||||
|
() async {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
Themes make() =>
|
||||||
|
Themes('t', Palette.fallbackDark, Palette.fallbackBright, prefs);
|
||||||
|
final t = make();
|
||||||
|
var notified = 0;
|
||||||
|
t.addListener(() => notified++);
|
||||||
|
|
||||||
|
expect(t.mode, VariantMode.auto);
|
||||||
|
dispatcher.platformBrightnessTestValue = Brightness.light;
|
||||||
|
expect(t.isDark, isFalse);
|
||||||
|
expect(notified, 1); // the binding tells its observers
|
||||||
|
|
||||||
|
t.toggle(); // bright showing: hold dark
|
||||||
|
expect((t.mode, t.isDark), (VariantMode.dark, true));
|
||||||
|
dispatcher.platformBrightnessTestValue = Brightness.dark;
|
||||||
|
dispatcher.platformBrightnessTestValue = Brightness.light;
|
||||||
|
expect(notified, 2); // fixed: Android's changes are ignored
|
||||||
|
|
||||||
|
expect(make().mode, VariantMode.dark); // kept for the next start
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user