@@ -7,6 +7,7 @@ import 'package:flutter/scheduler.dart';
77import '../generated/l10n/zulip_localizations.dart' ;
88import '../log.dart' ;
99import '../model/actions.dart' ;
10+ import '../model/binding.dart' ;
1011import '../model/localizations.dart' ;
1112import '../model/store.dart' ;
1213import '../notifications/display.dart' ;
@@ -151,10 +152,13 @@ class ZulipApp extends StatefulWidget {
151152}
152153
153154class _ZulipAppState extends State <ZulipApp > with WidgetsBindingObserver {
155+ late final Future <GlobalStore > _globalStoreFuture;
156+
154157 @override
155158 void initState () {
156159 super .initState ();
157160 WidgetsBinding .instance.addObserver (this );
161+ _globalStoreFuture = ZulipBinding .instance.getGlobalStoreUniquely ();
158162 }
159163
160164 @override
@@ -212,44 +216,101 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
212216
213217 @override
214218 Widget build (BuildContext context) {
215- return GlobalStoreWidget (
216- child: Builder (builder: (context) {
217- return MaterialApp (
218- onGenerateTitle: (BuildContext context) {
219- return ZulipLocalizations .of (context).zulipAppTitle;
220- },
221- localizationsDelegates: ZulipLocalizations .localizationsDelegates,
222- supportedLocales: ZulipLocalizations .supportedLocales,
223- // The context has to be taken from the [Builder] because
224- // [zulipThemeData] requires access to [GlobalStoreWidget] in the tree.
225- theme: zulipThemeData (context),
226-
227- navigatorKey: ZulipApp .navigatorKey,
228- navigatorObservers: [
229- if (widget.navigatorObservers != null )
230- ...widget.navigatorObservers! ,
231- _PreventEmptyStack (),
232- ],
233- builder: (BuildContext context, Widget ? child) {
234- if (! ZulipApp .ready.value) {
235- SchedulerBinding .instance.addPostFrameCallback (
236- (_) => widget._declareReady ());
237- }
238- GlobalLocalizations .zulipLocalizations = ZulipLocalizations .of (context);
239- return child! ;
240- },
219+ return DeferredBuilderWidget (
220+ future: _globalStoreFuture,
221+ builder: (context, store) {
222+ return GlobalStoreWidget (
223+ store: store,
224+ child: Builder (builder: (context) {
225+ return MaterialApp (
226+ onGenerateTitle: (BuildContext context) {
227+ return ZulipLocalizations .of (context).zulipAppTitle;
228+ },
229+ localizationsDelegates: ZulipLocalizations .localizationsDelegates,
230+ supportedLocales: ZulipLocalizations .supportedLocales,
231+ // The context has to be taken from the [Builder] because
232+ // [zulipThemeData] requires access to [GlobalStoreWidget] in the tree.
233+ theme: zulipThemeData (context),
234+
235+ navigatorKey: ZulipApp .navigatorKey,
236+ navigatorObservers: [
237+ if (widget.navigatorObservers != null )
238+ ...widget.navigatorObservers! ,
239+ _PreventEmptyStack (),
240+ ],
241+ builder: (BuildContext context, Widget ? child) {
242+ if (! ZulipApp .ready.value) {
243+ SchedulerBinding .instance.addPostFrameCallback (
244+ (_) => widget._declareReady ());
245+ }
246+ GlobalLocalizations .zulipLocalizations = ZulipLocalizations .of (context);
247+ return child! ;
248+ },
249+
250+ // We use onGenerateInitialRoutes for the real work of specifying the
251+ // initial nav state. To do that we need [MaterialApp] to decide to
252+ // build a [Navigator]... which means specifying either `home`, `routes`,
253+ // `onGenerateRoute`, or `onUnknownRoute`. Make it `onGenerateRoute`.
254+ // It never actually gets called, though: `onGenerateInitialRoutes`
255+ // handles startup, and then we always push whole routes with methods
256+ // like [Navigator.push], never mere names as with [Navigator.pushNamed].
257+ onGenerateRoute: (_) => null ,
258+
259+ onGenerateInitialRoutes: _handleGenerateInitialRoutes);
260+ }));
261+ });
262+ }
263+ }
264+
265+ /// A widget that defers the builder until the provided [future] completes.
266+ ///
267+ /// It shows a placeholder widget while it waits for the [future]
268+ /// to complete.
269+ class DeferredBuilderWidget <T > extends StatefulWidget {
270+ const DeferredBuilderWidget ({
271+ super .key,
272+ required this .future,
273+ required this .builder,
274+ this .placeholderBuilder = _defaultPlaceHolderBuilder,
275+ });
276+
277+ final Future <T > future;
278+
279+ /// The widget to build when [future] completes, with it's result
280+ /// passed as `result` .
281+ final Widget Function (BuildContext context, T result) builder;
282+
283+ /// The placeholder widget to build while waiting for the [future]
284+ /// to complete.
285+ ///
286+ /// By default, it will build the [LoadingPlaceholder] .
287+ final Widget Function (BuildContext context) placeholderBuilder;
241288
242- // We use onGenerateInitialRoutes for the real work of specifying the
243- // initial nav state. To do that we need [MaterialApp] to decide to
244- // build a [Navigator]... which means specifying either `home`, `routes`,
245- // `onGenerateRoute`, or `onUnknownRoute`. Make it `onGenerateRoute`.
246- // It never actually gets called, though: `onGenerateInitialRoutes`
247- // handles startup, and then we always push whole routes with methods
248- // like [Navigator.push], never mere names as with [Navigator.pushNamed].
249- onGenerateRoute: (_) => null ,
250-
251- onGenerateInitialRoutes: _handleGenerateInitialRoutes);
252- }));
289+ static Widget _defaultPlaceHolderBuilder (BuildContext context) {
290+ return const LoadingPlaceholder ();
291+ }
292+
293+ @override
294+ State <DeferredBuilderWidget <T >> createState () => _DeferredBuilderWidgetState <T >();
295+ }
296+
297+ class _DeferredBuilderWidgetState <T > extends State <DeferredBuilderWidget <T >> {
298+ T ? _result;
299+
300+ @override
301+ void initState () {
302+ super .initState ();
303+ () async {
304+ _result = await widget.future;
305+ if (mounted) setState (() {});
306+ }();
307+ }
308+
309+ @override
310+ Widget build (BuildContext context) {
311+ final result = _result;
312+ if (result == null ) return widget.placeholderBuilder (context);
313+ return widget.builder (context, result);
253314 }
254315}
255316
0 commit comments