Skip to content

Commit 4e9fd2b

Browse files
committed
refactor(app): drive host config by value, not streams
AppConfigSource now carries plain values (ThemeSettings/ThemeMode/FeatureAccess) instead of streams. The app renders them directly through the widget tree the same way it renders everything else - App reads the theme from configSource (or its AppBloc), and RootApp provides the FeatureAccess value (or the normal StreamProvider). A host re-supplies a fresh AppConfigSource on each edit, so reactivity comes from the normal rebuild; no stream subscriptions, no AppThemeSettingsChanged/AppThemeModeChanged plumbing, no initial-seed field.
1 parent ee98d88 commit 4e9fd2b

3 files changed

Lines changed: 31 additions & 37 deletions

File tree

lib/app/app_config_source.dart

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,22 @@ import 'package:webtrit_phone/data/feature_access.dart';
44
import 'package:webtrit_phone/theme/theme.dart';
55

66
/// Live application configuration supplied by an external host (the theme
7-
/// configurator's realtime preview).
7+
/// configurator's realtime preview), passed down the widget tree like any other
8+
/// config.
89
///
9-
/// Every field is optional: when a source is provided the app follows it,
10-
/// otherwise it uses its own bootstrap-built defaults. This is a plain config
11-
/// source — it carries no notion of "embedding", so the app stays unaware of
12-
/// where it runs.
10+
/// Every field is optional: when present the app renders it, otherwise it uses
11+
/// its bootstrap-built defaults. These are plain values, not streams — the host
12+
/// re-supplies a new instance on each edit, so reactivity comes from the normal
13+
/// widget rebuild. The app carries no notion of "embedding".
1314
class AppConfigSource {
14-
const AppConfigSource({this.themeSettings, this.themeMode, this.featureAccess, this.featureAccessInitial});
15+
const AppConfigSource({this.themeSettings, this.themeMode, this.featureAccess});
1516

16-
/// Live theme appearance applied via `AppThemeSettingsChanged` (not persisted).
17-
final Stream<ThemeSettings>? themeSettings;
17+
/// Theme appearance to render instead of the app's own.
18+
final ThemeSettings? themeSettings;
1819

19-
/// Live theme mode applied via `AppThemeModeChanged`, paired with [themeSettings].
20-
final Stream<ThemeMode>? themeMode;
20+
/// Theme mode (light/dark) to apply, paired with [themeSettings].
21+
final ThemeMode? themeMode;
2122

22-
/// Reactive [FeatureAccess] that replaces the bootstrap-built configuration.
23-
final Stream<FeatureAccess>? featureAccess;
24-
25-
/// First-frame [FeatureAccess] seed paired with [featureAccess].
26-
final FeatureAccess? featureAccessInitial;
23+
/// [FeatureAccess] to use instead of the bootstrap-built configuration.
24+
final FeatureAccess? featureAccess;
2725
}

lib/app/view/app.dart

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import 'dart:async';
2-
31
import 'package:flutter/material.dart';
42

53
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -27,7 +25,7 @@ class App extends StatefulWidget {
2725
const App({super.key, this.configSource});
2826

2927
/// Optional live config supplied by an external host (the configurator's
30-
/// realtime preview); when present, the app follows its theme streams.
28+
/// realtime preview); when present, the app renders its theme and feature config.
3129
final AppConfigSource? configSource;
3230

3331
@override
@@ -38,9 +36,6 @@ class _AppState extends State<App> {
3836
late final AppBloc appBloc;
3937
late final AppRouter appRouter;
4038

41-
StreamSubscription<ThemeSettings>? _themeSettingsSubscription;
42-
StreamSubscription<ThemeMode>? _themeModeSubscription;
43-
4439
@override
4540
void initState() {
4641
super.initState();
@@ -93,13 +88,6 @@ class _AppState extends State<App> {
9388
initialTabResolver,
9489
featureAccess.checker,
9590
);
96-
97-
_themeSettingsSubscription = widget.configSource?.themeSettings?.listen(
98-
(settings) => appBloc.add(AppThemeSettingsChanged(settings)),
99-
);
100-
_themeModeSubscription = widget.configSource?.themeMode?.listen(
101-
(themeMode) => appBloc.add(AppThemeModeChanged(themeMode)),
102-
);
10391
}
10492

10593
@override
@@ -125,8 +113,6 @@ class _AppState extends State<App> {
125113

126114
@override
127115
void dispose() {
128-
_themeSettingsSubscription?.cancel();
129-
_themeModeSubscription?.cancel();
130116
appBloc.close();
131117
super.dispose();
132118
}
@@ -137,11 +123,15 @@ class _AppState extends State<App> {
137123

138124
final featureAccess = context.watch<FeatureAccess>();
139125

126+
// A host (the configurator's realtime preview) renders its own theme; the
127+
// app falls back to its AppBloc theme otherwise.
128+
final configSource = widget.configSource;
129+
140130
final materialApp = BlocBuilder<AppBloc, AppState>(
141131
buildWhen: (previous, current) => previous.themeSettings != current.themeSettings,
142132
builder: (context, state) {
143133
return ThemeProvider(
144-
settings: state.themeSettings,
134+
settings: configSource?.themeSettings ?? state.themeSettings,
145135
lightDynamic: null,
146136
darkDynamic: null,
147137
child: BlocBuilder<AppBloc, AppState>(
@@ -151,7 +141,9 @@ class _AppState extends State<App> {
151141
builder: (context, state) {
152142
final themeProvider = ThemeProvider.of(context);
153143
final forcedMode = featureAccess.supportedConfig.themeMode;
154-
final finalThemeMode = forcedMode == ThemeMode.system ? state.effectiveThemeMode : forcedMode;
144+
final finalThemeMode = forcedMode == ThemeMode.system
145+
? (configSource?.themeMode ?? state.effectiveThemeMode)
146+
: forcedMode;
155147

156148
return MaterialApp.router(
157149
locale: state.effectiveLocale,

lib/main.dart

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,15 @@ class RootApp extends StatelessWidget {
9090
// Provides reactive [FeatureAccess] configuration synchronized with [SystemInfoRepository] and [RemoteConfigService].
9191
//
9292
// Initializes with bootstrap data and updates whenever system information or remote configuration changes.
93-
StreamProvider<FeatureAccess>(
94-
initialData: configSource?.featureAccessInitial ?? instanceRegistry.get<FeatureAccess>(),
95-
create: (_) => configSource?.featureAccess ?? instanceRegistry.get<FeatureAccessStreamFactory>().create(),
96-
updateShouldNotify: (previous, next) => previous != next,
97-
),
93+
// A host (the configurator's realtime preview) overrides it with a plain value it re-supplies on edits.
94+
if (configSource?.featureAccess != null)
95+
Provider<FeatureAccess>.value(value: configSource!.featureAccess!)
96+
else
97+
StreamProvider<FeatureAccess>(
98+
initialData: instanceRegistry.get<FeatureAccess>(),
99+
create: (_) => instanceRegistry.get<FeatureAccessStreamFactory>().create(),
100+
updateShouldNotify: (previous, next) => previous != next,
101+
),
98102
Provider<SecureStorage>(create: (_) => instanceRegistry.get()),
99103
Provider<AppPermissions>(create: (_) => instanceRegistry.get()),
100104
Provider<AppLogger>(create: (_) => instanceRegistry.get()),

0 commit comments

Comments
 (0)