11import 'dart:async' ;
2+ import 'dart:math' as math;
23
34import 'package:flutter/material.dart' ;
45import 'package:flutter_bloc/flutter_bloc.dart' ;
56import 'package:flutter_local_notifications/flutter_local_notifications.dart' ;
7+ import 'package:flutter_markdown/flutter_markdown.dart' ;
68import 'package:go_router/go_router.dart' ;
79import 'package:responsive_framework/responsive_framework.dart' ;
810import 'package:tsdm_client/constants/layout.dart' ;
11+ import 'package:tsdm_client/extensions/string.dart' ;
912import 'package:tsdm_client/features/authentication/repository/authentication_repository.dart' ;
1013import 'package:tsdm_client/features/home/cubit/home_cubit.dart' ;
1114import 'package:tsdm_client/features/home/cubit/init_cubit.dart' ;
@@ -14,16 +17,24 @@ import 'package:tsdm_client/features/local_notice/keys.dart';
1417import 'package:tsdm_client/features/local_notice/stream.dart' ;
1518import 'package:tsdm_client/features/notification/models/models.dart' ;
1619import 'package:tsdm_client/features/root/bloc/root_location_cubit.dart' ;
20+ import 'package:tsdm_client/features/root/view/root_page.dart' ;
21+ import 'package:tsdm_client/features/update/cubit/update_cubit.dart' ;
1722import 'package:tsdm_client/i18n/strings.g.dart' ;
1823import 'package:tsdm_client/instance.dart' ;
24+ import 'package:tsdm_client/routes/app_routes.dart' ;
1925import 'package:tsdm_client/routes/screen_paths.dart' ;
26+ import 'package:tsdm_client/utils/git_info.dart' ;
2027import 'package:tsdm_client/utils/logger.dart' ;
2128import 'package:tsdm_client/utils/platform.dart' ;
29+ import 'package:tsdm_client/utils/show_toast.dart' ;
30+ import 'package:tsdm_client/widgets/custom_alert_dialog.dart' ;
2231import 'package:tsdm_client/widgets/indicator.dart' ;
2332
2433const _drawerWidth = 250.0 ;
2534
2635/// Page of the homepage of the app.
36+ ///
37+ // Partial global singleton page, provides global functionalities.
2738class HomePage extends StatefulWidget {
2839 /// Constructor.
2940 const HomePage ({required this .showNavigationBar, required this .child, super .key});
@@ -146,30 +157,100 @@ class _HomePageState extends State<HomePage> with LoggerMixin {
146157
147158 return MultiBlocProvider (
148159 providers: [BlocProvider (create: (_) => HomeCubit ())],
149- child: BlocBuilder <InitCubit , InitState >(
150- builder: (context, state) {
151- if (state.clearingOutdatedImageCache) {
152- return const CenteredCircularIndicator ();
153- }
154-
155- if (ResponsiveBreakpoints .of (context).largerThan (WindowSize .expanded.name)) {
156- return _buildDrawerBody (context);
157- } else if (ResponsiveBreakpoints .of (context).largerThan (WindowSize .compact.name)) {
158- return Scaffold (
159- body: Row (
160- children: [
161- if (widget.showNavigationBar) const HomeNavigationRail (),
162- Expanded (child: widget.child),
163- ],
164- ),
165- );
166- } else {
167- return Scaffold (
168- body: widget.child,
169- bottomNavigationBar: widget.showNavigationBar ? const HomeNavigationBar () : null ,
170- );
171- }
172- },
160+ child: MultiBlocListener (
161+ listeners: [
162+ BlocListener <UpdateCubit , UpdateCubitState >(
163+ listenWhen: (prev, curr) => ! curr.loading && prev.loading,
164+ listener: (context, state) async {
165+ final info = state.latestVersionInfo;
166+ final tr = context.t.updatePage;
167+ if (info == null ) {
168+ error ('failed to check update state' );
169+ if (state.notice) {
170+ showSnackBar (context: context, message: tr.failed);
171+ }
172+ return ;
173+ }
174+
175+ final inUpdatePage = context.read <RootLocationCubit >().isIn (ScreenPaths .update);
176+
177+ if (info.versionCode <= appVersion.split ('+' ).last.parseToInt ()! ) {
178+ // Only show the already latest message in update page.
179+ if (inUpdatePage) {
180+ showSnackBar (context: context, message: tr.alreadyLatest);
181+ }
182+ } else {
183+ final gotoUpdatePage = await showDialog <bool >(
184+ context: context,
185+ builder: (context) {
186+ final size = MediaQuery .sizeOf (context);
187+ return RootPage (
188+ DialogPaths .updateNotice,
189+ CustomAlertDialog .sync (
190+ title: Text (tr.availableDialog.title),
191+ content: SizedBox (
192+ width: math.min (size.width * 0.8 , 800 ),
193+ height: math.min (size.height * 0.6 , 600 ),
194+ child: Column (
195+ crossAxisAlignment: CrossAxisAlignment .start,
196+ children: [
197+ Text (
198+ tr.availableDialog.version (version: info.version),
199+ style: Theme .of (
200+ context,
201+ ).textTheme.labelMedium? .copyWith (color: Theme .of (context).colorScheme.primary),
202+ ),
203+ sizedBoxW8H8,
204+ Expanded (child: Markdown (data: info.changelog)),
205+ ],
206+ ),
207+ ),
208+ actions: [
209+ TextButton (
210+ child: Text (context.t.general.cancel),
211+ onPressed: () => context.pop (false ),
212+ ),
213+ TextButton (
214+ child: Text (context.t.settingsPage.othersSection.update),
215+ onPressed: () => context.pop (true ),
216+ ),
217+ ],
218+ ),
219+ );
220+ },
221+ );
222+ if (true == gotoUpdatePage && context.mounted && ! inUpdatePage) {
223+ await router.pushNamed (ScreenPaths .update);
224+ }
225+ }
226+ },
227+ ),
228+ ],
229+ child: BlocBuilder <InitCubit , InitState >(
230+ builder: (context, state) {
231+ if (state.clearingOutdatedImageCache) {
232+ return const CenteredCircularIndicator ();
233+ }
234+
235+ if (ResponsiveBreakpoints .of (context).largerThan (WindowSize .expanded.name)) {
236+ return _buildDrawerBody (context);
237+ } else if (ResponsiveBreakpoints .of (context).largerThan (WindowSize .compact.name)) {
238+ return Scaffold (
239+ body: Row (
240+ children: [
241+ if (widget.showNavigationBar) const HomeNavigationRail (),
242+ Expanded (child: widget.child),
243+ ],
244+ ),
245+ );
246+ } else {
247+ return Scaffold (
248+ body: widget.child,
249+ bottomNavigationBar: widget.showNavigationBar ? const HomeNavigationBar () : null ,
250+ );
251+ }
252+ },
253+ ),
173254 ),
174255 );
175256 }
0 commit comments