refactor(settings): share preference binding lifecycle
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
|
import 'dart:async' show unawaited;
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||||
import '../models/shader_preset.dart';
|
import '../models/shader_preset.dart';
|
||||||
|
import '../services/settings_binding_owner.dart';
|
||||||
import '../services/settings_service.dart';
|
import '../services/settings_service.dart';
|
||||||
import '../services/shader_asset_loader.dart';
|
import '../services/shader_asset_loader.dart';
|
||||||
|
|
||||||
class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||||
SettingsService? _settingsService;
|
late final SettingsBindingOwner _settingsBinding;
|
||||||
ValueNotifier<String>? _savedPresetListenable;
|
|
||||||
ValueNotifier<List<Map<String, dynamic>>>? _customPresetsListenable;
|
|
||||||
|
|
||||||
ShaderPreset _savedPreset = ShaderPreset.none;
|
ShaderPreset _savedPreset = ShaderPreset.none;
|
||||||
ShaderPreset _currentPreset = ShaderPreset.none;
|
ShaderPreset _currentPreset = ShaderPreset.none;
|
||||||
@@ -17,30 +18,14 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
|||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
|
|
||||||
ShaderProvider() {
|
ShaderProvider() {
|
||||||
_initialize();
|
_settingsBinding = SettingsBindingOwner(
|
||||||
|
prefs: [SettingsService.globalShaderPreset, SettingsService.customShaderPresets],
|
||||||
|
onRefresh: _syncFromSettings,
|
||||||
|
);
|
||||||
|
unawaited(_settingsBinding.bind());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initialize() async {
|
void _syncFromSettings(SettingsService service) {
|
||||||
final service = await SettingsService.getInstance();
|
|
||||||
if (_settingsService == service && _savedPresetListenable != null && _customPresetsListenable != null) {
|
|
||||||
_syncFromSettings();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_savedPresetListenable?.removeListener(_onSettingsChanged);
|
|
||||||
_customPresetsListenable?.removeListener(_onSettingsChanged);
|
|
||||||
_settingsService = service;
|
|
||||||
_savedPresetListenable = service.listenable(SettingsService.globalShaderPreset)..addListener(_onSettingsChanged);
|
|
||||||
_customPresetsListenable = service.listenable(SettingsService.customShaderPresets)..addListener(_onSettingsChanged);
|
|
||||||
_syncFromSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onSettingsChanged() => _syncFromSettings();
|
|
||||||
|
|
||||||
void _syncFromSettings() {
|
|
||||||
final service = _settingsService;
|
|
||||||
if (service == null) return;
|
|
||||||
|
|
||||||
final customData = service.read(SettingsService.customShaderPresets);
|
final customData = service.read(SettingsService.customShaderPresets);
|
||||||
final customPresets = customData.map((json) => ShaderPreset.fromJson(json)).toList();
|
final customPresets = customData.map((json) => ShaderPreset.fromJson(json)).toList();
|
||||||
_customPresets = customPresets;
|
_customPresets = customPresets;
|
||||||
@@ -55,8 +40,7 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_savedPresetListenable?.removeListener(_onSettingsChanged);
|
_settingsBinding.dispose();
|
||||||
_customPresetsListenable?.removeListener(_onSettingsChanged);
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +56,12 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setPreset(ShaderPreset preset) async {
|
Future<void> setPreset(ShaderPreset preset) async {
|
||||||
final service = _settingsService ?? await SettingsService.getInstance();
|
final service = _settingsBinding.settings ?? await SettingsService.getInstance();
|
||||||
await service.write(SettingsService.globalShaderPreset, preset.id);
|
await service.write(SettingsService.globalShaderPreset, preset.id);
|
||||||
final changed = _savedPreset.id != preset.id || _currentPreset.id != preset.id;
|
final changed = _savedPreset.id != preset.id || _currentPreset.id != preset.id;
|
||||||
_savedPreset = preset;
|
_savedPreset = preset;
|
||||||
_currentPreset = preset;
|
_currentPreset = preset;
|
||||||
if (changed || _savedPresetListenable == null) {
|
if (changed || _settingsBinding.settings == null) {
|
||||||
safeNotifyListeners();
|
safeNotifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +103,7 @@ class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveCustomPresets() async {
|
Future<void> _saveCustomPresets() async {
|
||||||
final service = _settingsService ?? await SettingsService.getInstance();
|
final service = _settingsBinding.settings ?? await SettingsService.getInstance();
|
||||||
final data = _customPresets.map((p) => p.toJson()).toList();
|
final data = _customPresets.map((p) => p.toJson()).toList();
|
||||||
await service.write(SettingsService.customShaderPresets, data);
|
await service.write(SettingsService.customShaderPresets, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
|
import 'dart:async' show unawaited;
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||||
|
import '../services/settings_binding_owner.dart';
|
||||||
import '../services/settings_service.dart' as settings;
|
import '../services/settings_service.dart' as settings;
|
||||||
import '../theme/mono_theme.dart';
|
import '../theme/mono_theme.dart';
|
||||||
|
|
||||||
class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||||
settings.SettingsService? _settingsService;
|
late final SettingsBindingOwner _settingsBinding;
|
||||||
ValueNotifier<settings.ThemeMode>? _themeModeListenable;
|
|
||||||
settings.ThemeMode _themeMode = settings.ThemeMode.system;
|
settings.ThemeMode _themeMode = settings.ThemeMode.system;
|
||||||
late Brightness _systemBrightness;
|
late Brightness _systemBrightness;
|
||||||
|
|
||||||
@@ -19,7 +20,11 @@ class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
|||||||
// async path below lands a microtask too late for the first build.
|
// async path below lands a microtask too late for the first build.
|
||||||
final loaded = settings.SettingsService.instanceOrNull;
|
final loaded = settings.SettingsService.instanceOrNull;
|
||||||
if (loaded != null) _themeMode = loaded.read(settings.SettingsService.themeMode);
|
if (loaded != null) _themeMode = loaded.read(settings.SettingsService.themeMode);
|
||||||
_initializeSettings();
|
_settingsBinding = SettingsBindingOwner(
|
||||||
|
prefs: const [settings.SettingsService.themeMode],
|
||||||
|
onRefresh: (service) => _syncThemeMode(service.read(settings.SettingsService.themeMode)),
|
||||||
|
);
|
||||||
|
unawaited(_settingsBinding.bind());
|
||||||
WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged = _onBrightnessChanged;
|
WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged = _onBrightnessChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,33 +37,13 @@ class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_themeModeListenable?.removeListener(_onThemeModeSettingChanged);
|
_settingsBinding.dispose();
|
||||||
if (WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged == _onBrightnessChanged) {
|
if (WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged == _onBrightnessChanged) {
|
||||||
WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged = null;
|
WidgetsBinding.instance.platformDispatcher.onPlatformBrightnessChanged = null;
|
||||||
}
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initializeSettings() async {
|
|
||||||
final service = await settings.SettingsService.getInstance();
|
|
||||||
if (_settingsService == service && _themeModeListenable != null) {
|
|
||||||
_syncThemeMode(service.read(settings.SettingsService.themeMode));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_themeModeListenable?.removeListener(_onThemeModeSettingChanged);
|
|
||||||
_settingsService = service;
|
|
||||||
_themeModeListenable = service.listenable(settings.SettingsService.themeMode)
|
|
||||||
..addListener(_onThemeModeSettingChanged);
|
|
||||||
_syncThemeMode(_themeModeListenable!.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onThemeModeSettingChanged() {
|
|
||||||
final listenable = _themeModeListenable;
|
|
||||||
if (listenable == null) return;
|
|
||||||
_syncThemeMode(listenable.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncThemeMode(settings.ThemeMode mode, {bool forceNotify = false}) {
|
void _syncThemeMode(settings.ThemeMode mode, {bool forceNotify = false}) {
|
||||||
final changed = _themeMode != mode;
|
final changed = _themeMode != mode;
|
||||||
_themeMode = mode;
|
_themeMode = mode;
|
||||||
@@ -106,14 +91,14 @@ class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
|||||||
|
|
||||||
Future<void> setThemeMode(settings.ThemeMode mode) async {
|
Future<void> setThemeMode(settings.ThemeMode mode) async {
|
||||||
if (_themeMode == mode) return;
|
if (_themeMode == mode) return;
|
||||||
final service = _settingsService ?? await settings.SettingsService.getInstance();
|
final service = _settingsBinding.settings ?? await settings.SettingsService.getInstance();
|
||||||
await service.write(settings.SettingsService.themeMode, mode);
|
await service.write(settings.SettingsService.themeMode, mode);
|
||||||
if (_themeModeListenable == null) _syncThemeMode(mode);
|
if (_settingsBinding.settings == null) _syncThemeMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reload() async {
|
Future<void> reload() async {
|
||||||
await _initializeSettings();
|
await _settingsBinding.bind();
|
||||||
final service = _settingsService;
|
final service = _settingsBinding.settings;
|
||||||
if (service != null) _syncThemeMode(service.read(settings.SettingsService.themeMode), forceNotify: true);
|
if (service != null) _syncThemeMode(service.read(settings.SettingsService.themeMode), forceNotify: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
|
|||||||
import '../models/hotkey_model.dart';
|
import '../models/hotkey_model.dart';
|
||||||
import '../i18n/strings.g.dart';
|
import '../i18n/strings.g.dart';
|
||||||
import '../mpv/mpv.dart';
|
import '../mpv/mpv.dart';
|
||||||
|
import 'settings_binding_owner.dart';
|
||||||
import 'settings_service.dart';
|
import 'settings_service.dart';
|
||||||
import '../utils/platform_detector.dart';
|
import '../utils/platform_detector.dart';
|
||||||
import '../utils/player_utils.dart';
|
import '../utils/player_utils.dart';
|
||||||
@@ -14,14 +15,26 @@ class KeyboardShortcutsService extends ChangeNotifier {
|
|||||||
static const Set<String> _repeatableVideoActions = {'zoom_in', 'zoom_out'};
|
static const Set<String> _repeatableVideoActions = {'zoom_in', 'zoom_out'};
|
||||||
|
|
||||||
static KeyboardShortcutsService? _instance;
|
static KeyboardShortcutsService? _instance;
|
||||||
late SettingsService _settingsService;
|
late final SettingsBindingOwner _settingsBinding;
|
||||||
final List<VoidCallback> _settingsDisposers = [];
|
|
||||||
Map<String, HotKey> _hotkeys = {};
|
Map<String, HotKey> _hotkeys = {};
|
||||||
int _seekTimeSmall = 10; // Default, loaded from settings
|
int _seekTimeSmall = 10; // Default, loaded from settings
|
||||||
int _seekTimeLarge = 30; // Default, loaded from settings
|
int _seekTimeLarge = 30; // Default, loaded from settings
|
||||||
int _maxVolume = 100; // Default, loaded from settings (100-300%)
|
int _maxVolume = 100; // Default, loaded from settings (100-300%)
|
||||||
|
bool _settingsInitialized = false;
|
||||||
|
|
||||||
KeyboardShortcutsService._();
|
KeyboardShortcutsService._() {
|
||||||
|
_settingsBinding = SettingsBindingOwner(
|
||||||
|
prefs: [
|
||||||
|
SettingsService.keyboardHotkeys,
|
||||||
|
SettingsService.seekTimeSmall,
|
||||||
|
SettingsService.seekTimeLarge,
|
||||||
|
SettingsService.maxVolume,
|
||||||
|
],
|
||||||
|
onRefresh: _syncFromSettings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsService get _settingsService => _settingsBinding.settings!;
|
||||||
|
|
||||||
static Future<KeyboardShortcutsService> getInstance() async {
|
static Future<KeyboardShortcutsService> getInstance() async {
|
||||||
if (_instance == null) {
|
if (_instance == null) {
|
||||||
@@ -37,32 +50,14 @@ class KeyboardShortcutsService extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _init() async {
|
Future<void> _init() async {
|
||||||
_settingsService = await SettingsService.getInstance();
|
await _settingsBinding.bind();
|
||||||
_bindSettings();
|
|
||||||
_syncFromSettings(notify: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _bindSettings() {
|
void _syncFromSettings(SettingsService service) {
|
||||||
if (_settingsDisposers.isNotEmpty) return;
|
final hotkeys = service.read(SettingsService.keyboardHotkeys);
|
||||||
void bind<T>(Pref<T> pref) {
|
final seekTimeSmall = service.read(SettingsService.seekTimeSmall);
|
||||||
final notifier = _settingsService.listenable(pref);
|
final seekTimeLarge = service.read(SettingsService.seekTimeLarge);
|
||||||
notifier.addListener(_onSettingsChanged);
|
final maxVolume = service.read(SettingsService.maxVolume);
|
||||||
_settingsDisposers.add(() => notifier.removeListener(_onSettingsChanged));
|
|
||||||
}
|
|
||||||
|
|
||||||
bind(SettingsService.keyboardHotkeys);
|
|
||||||
bind(SettingsService.seekTimeSmall);
|
|
||||||
bind(SettingsService.seekTimeLarge);
|
|
||||||
bind(SettingsService.maxVolume);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onSettingsChanged() => _syncFromSettings();
|
|
||||||
|
|
||||||
void _syncFromSettings({bool notify = true}) {
|
|
||||||
final hotkeys = _settingsService.read(SettingsService.keyboardHotkeys);
|
|
||||||
final seekTimeSmall = _settingsService.read(SettingsService.seekTimeSmall);
|
|
||||||
final seekTimeLarge = _settingsService.read(SettingsService.seekTimeLarge);
|
|
||||||
final maxVolume = _settingsService.read(SettingsService.maxVolume);
|
|
||||||
|
|
||||||
final changed =
|
final changed =
|
||||||
!_hotkeyMapsEqual(_hotkeys, hotkeys) ||
|
!_hotkeyMapsEqual(_hotkeys, hotkeys) ||
|
||||||
@@ -75,6 +70,8 @@ class KeyboardShortcutsService extends ChangeNotifier {
|
|||||||
_seekTimeLarge = seekTimeLarge;
|
_seekTimeLarge = seekTimeLarge;
|
||||||
_maxVolume = maxVolume;
|
_maxVolume = maxVolume;
|
||||||
|
|
||||||
|
final notify = _settingsInitialized;
|
||||||
|
_settingsInitialized = true;
|
||||||
if (notify && changed) notifyListeners();
|
if (notify && changed) notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +96,7 @@ class KeyboardShortcutsService extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refreshFromStorage() async {
|
Future<void> refreshFromStorage() async {
|
||||||
_syncFromSettings();
|
_settingsBinding.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetToDefaults() async {
|
Future<void> resetToDefaults() async {
|
||||||
@@ -109,10 +106,7 @@ class KeyboardShortcutsService extends ChangeNotifier {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
for (final dispose in _settingsDisposers) {
|
_settingsBinding.dispose();
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
_settingsDisposers.clear();
|
|
||||||
if (identical(_instance, this)) _instance = null;
|
if (identical(_instance, this)) _instance = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'settings_service.dart';
|
||||||
|
|
||||||
|
typedef SettingsServiceAcquirer = Future<SettingsService> Function();
|
||||||
|
|
||||||
|
/// Owns a group of preference listeners backed by one [SettingsService].
|
||||||
|
class SettingsBindingOwner {
|
||||||
|
factory SettingsBindingOwner({
|
||||||
|
required Iterable<Pref<Object?>> prefs,
|
||||||
|
required void Function(SettingsService service) onRefresh,
|
||||||
|
SettingsServiceAcquirer? acquireSettings,
|
||||||
|
}) => SettingsBindingOwner._(List.unmodifiable(prefs), onRefresh, acquireSettings);
|
||||||
|
|
||||||
|
SettingsBindingOwner._(this._prefs, this._onRefresh, this._acquireSettings);
|
||||||
|
|
||||||
|
final List<Pref<Object?>> _prefs;
|
||||||
|
final void Function(SettingsService service) _onRefresh;
|
||||||
|
final SettingsServiceAcquirer? _acquireSettings;
|
||||||
|
final List<Listenable> _listenables = [];
|
||||||
|
|
||||||
|
SettingsService? _settings;
|
||||||
|
Future<void>? _bindingFuture;
|
||||||
|
int _generation = 0;
|
||||||
|
bool _disposed = false;
|
||||||
|
|
||||||
|
SettingsService? get settings => _settings;
|
||||||
|
bool get isBound => !_disposed && _settings != null;
|
||||||
|
|
||||||
|
Future<void> bind() {
|
||||||
|
if (_disposed) return Future.value();
|
||||||
|
|
||||||
|
final pending = _bindingFuture;
|
||||||
|
if (pending != null) return pending;
|
||||||
|
|
||||||
|
final generation = ++_generation;
|
||||||
|
late final Future<void> future;
|
||||||
|
future = _bind(generation).whenComplete(() {
|
||||||
|
if (identical(_bindingFuture, future)) _bindingFuture = null;
|
||||||
|
});
|
||||||
|
_bindingFuture = future;
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _bind(int generation) async {
|
||||||
|
final acquirer = _acquireSettings;
|
||||||
|
final service = await (acquirer?.call() ?? SettingsService.getInstance());
|
||||||
|
if (_disposed || generation != _generation) return;
|
||||||
|
|
||||||
|
if (!identical(_settings, service)) {
|
||||||
|
_removeListeners();
|
||||||
|
_settings = service;
|
||||||
|
for (final pref in _prefs) {
|
||||||
|
final listenable = service.listenableOf(pref)..addListener(_handlePreferenceChanged);
|
||||||
|
_listenables.add(listenable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRefresh(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
final service = _settings;
|
||||||
|
if (_disposed || service == null) return;
|
||||||
|
_onRefresh(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handlePreferenceChanged() {
|
||||||
|
final service = _settings;
|
||||||
|
if (_disposed || service == null) return;
|
||||||
|
_onRefresh(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeListeners() {
|
||||||
|
for (final listenable in _listenables) {
|
||||||
|
listenable.removeListener(_handlePreferenceChanged);
|
||||||
|
}
|
||||||
|
_listenables.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
_generation++;
|
||||||
|
_removeListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:plezy/services/settings_binding_owner.dart';
|
||||||
|
import 'package:plezy/services/settings_service.dart';
|
||||||
|
|
||||||
|
import '../test_helpers/prefs.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
resetSharedPreferencesForTest();
|
||||||
|
SettingsService.resetForTesting();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('refreshes when a bound preference changes', () async {
|
||||||
|
final settings = await SettingsService.getInstance();
|
||||||
|
final values = <int>[];
|
||||||
|
final binding = SettingsBindingOwner(
|
||||||
|
prefs: [SettingsService.maxVolume],
|
||||||
|
onRefresh: (service) => values.add(service.read(SettingsService.maxVolume)),
|
||||||
|
);
|
||||||
|
addTearDown(binding.dispose);
|
||||||
|
|
||||||
|
await binding.bind();
|
||||||
|
await settings.write(SettingsService.maxVolume, 175);
|
||||||
|
|
||||||
|
expect(values, [100, 175]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disposal before initialization ignores the stale completion', () async {
|
||||||
|
final settings = await SettingsService.getInstance();
|
||||||
|
final acquired = Completer<SettingsService>();
|
||||||
|
var refreshes = 0;
|
||||||
|
final binding = SettingsBindingOwner(
|
||||||
|
prefs: [SettingsService.maxVolume],
|
||||||
|
onRefresh: (_) => refreshes++,
|
||||||
|
acquireSettings: () => acquired.future,
|
||||||
|
);
|
||||||
|
|
||||||
|
final initialized = binding.bind();
|
||||||
|
binding.dispose();
|
||||||
|
acquired.complete(settings);
|
||||||
|
await initialized;
|
||||||
|
await settings.write(SettingsService.maxVolume, 150);
|
||||||
|
|
||||||
|
expect(refreshes, 0);
|
||||||
|
expect(binding.isBound, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('duplicate binding shares initialization and registers one listener', () async {
|
||||||
|
final settings = await SettingsService.getInstance();
|
||||||
|
final acquired = Completer<SettingsService>();
|
||||||
|
var acquisitions = 0;
|
||||||
|
var refreshes = 0;
|
||||||
|
final binding = SettingsBindingOwner(
|
||||||
|
prefs: [SettingsService.maxVolume],
|
||||||
|
onRefresh: (_) => refreshes++,
|
||||||
|
acquireSettings: () {
|
||||||
|
acquisitions++;
|
||||||
|
return acquired.future;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
addTearDown(binding.dispose);
|
||||||
|
|
||||||
|
final first = binding.bind();
|
||||||
|
final second = binding.bind();
|
||||||
|
acquired.complete(settings);
|
||||||
|
await Future.wait([first, second]);
|
||||||
|
await binding.bind();
|
||||||
|
await settings.write(SettingsService.maxVolume, 125);
|
||||||
|
|
||||||
|
expect(acquisitions, 2);
|
||||||
|
expect(refreshes, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not refresh after disposal', () async {
|
||||||
|
final settings = await SettingsService.getInstance();
|
||||||
|
var refreshes = 0;
|
||||||
|
final binding = SettingsBindingOwner(prefs: [SettingsService.maxVolume], onRefresh: (_) => refreshes++);
|
||||||
|
|
||||||
|
await binding.bind();
|
||||||
|
binding.dispose();
|
||||||
|
await settings.write(SettingsService.maxVolume, 200);
|
||||||
|
binding.refresh();
|
||||||
|
|
||||||
|
expect(refreshes, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user