diff --git a/firka_wear/lib/app/app_state.dart b/firka_wear/lib/app/app_state.dart new file mode 100644 index 0000000..389de28 --- /dev/null +++ b/firka_wear/lib/app/app_state.dart @@ -0,0 +1,45 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:isar_community/isar.dart'; +import 'package:logging/logging.dart'; + +import 'package:firka_wear/l10n/app_localizations.dart'; +import 'package:firka_wear/services/wear_sync_store.dart'; + +late final Logger logger; + +final GlobalKey navigatorKey = GlobalKey(); +late WearAppInitialization initData; +bool initDone = false; + +final dio = Dio(); + +class DeviceInfo { + String model; + String versionRelease; + String versionSdkInt; + + DeviceInfo(this.model, this.versionRelease, this.versionSdkInt); + + @override + String toString() { + return "DeviceInfo(model = \"$model\", versionRelease = \"$versionRelease\"" + ", versionSdkInt = \"$versionSdkInt\""; + } +} + +class WearAppInitialization { + final Isar isar; + final WearSyncStore syncStore; + final int tokenCount; + final AppLocalizations l10n; + final DeviceInfo devInfo; + + WearAppInitialization({ + required this.isar, + required this.syncStore, + required this.tokenCount, + required this.l10n, + required this.devInfo, + }); +} diff --git a/firka_wear/lib/app/initialization.dart b/firka_wear/lib/app/initialization.dart index f238eb6..46f0619 100644 --- a/firka_wear/lib/app/initialization.dart +++ b/firka_wear/lib/app/initialization.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:isar_community/isar.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:firka_wear/app/app_state.dart'; import 'package:firka_wear/data/models/generic_cache_model.dart'; import 'package:firka_wear/data/models/homework_cache_model.dart'; import 'package:firka_wear/data/models/timetable_cache_model.dart'; @@ -12,42 +13,9 @@ import 'package:firka_wear/l10n/app_localizations.dart'; import 'package:firka_wear/l10n/app_localizations_de.dart'; import 'package:firka_wear/l10n/app_localizations_en.dart'; import 'package:firka_wear/l10n/app_localizations_hu.dart'; -import 'package:flutter/material.dart'; - import 'package:firka_wear/services/wear_sync_store.dart'; Isar? isarInit; -final GlobalKey navigatorKey = GlobalKey(); - -class DeviceInfo { - String model; - String versionRelease; - String versionSdkInt; - - DeviceInfo(this.model, this.versionRelease, this.versionSdkInt); - - @override - String toString() { - return "DeviceInfo(model = \"$model\", versionRelease = \"$versionRelease\"" - ", versionSdkInt = \"$versionSdkInt\""; - } -} - -class WearAppInitialization { - final Isar isar; - final WearSyncStore syncStore; - final int tokenCount; - final AppLocalizations l10n; - final DeviceInfo devInfo; - - WearAppInitialization({ - required this.isar, - required this.syncStore, - required this.tokenCount, - required this.l10n, - required this.devInfo, - }); -} Future initDB() async { if (isarInit != null) return isarInit!; diff --git a/firka_wear/lib/app/initialization_screen.dart b/firka_wear/lib/app/initialization_screen.dart index 1dada31..8815ac6 100644 --- a/firka_wear/lib/app/initialization_screen.dart +++ b/firka_wear/lib/app/initialization_screen.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:wear_plus/wear_plus.dart'; +import 'package:firka_wear/app/app_state.dart'; import 'package:firka_wear/app/initialization.dart'; +import 'package:firka_wear/core/bloc/wear_sync_cubit.dart'; import 'package:firka_wear/l10n/app_localizations.dart'; import 'package:firka_wear/ui/theme/style.dart'; import 'package:firka_wear/ui/wear/screens/home/home_screen.dart'; @@ -44,37 +47,33 @@ class WearInitializationScreen extends StatelessWidget { ); } - Widget screen; assert(snapshot.data != null); - var data = snapshot.data!; + initData = snapshot.data!; + initDone = true; - if (snapshot.data!.tokenCount == 0) { - screen = WearLoginScreen(data, key: ValueKey('wearLoginScreen')); - } else { - screen = WearHomeScreen(data, key: ValueKey('wearHomeScreen')); - } + final data = initData; + final screen = data.tokenCount == 0 + ? WearLoginScreen(data, key: ValueKey('wearLoginScreen')) + : WearHomeScreen(data, key: ValueKey('wearHomeScreen')); - return MaterialApp( - key: ValueKey('firkaWearApp'), - title: 'Firka', - navigatorKey: navigatorKey, - theme: ThemeData( - primarySwatch: Colors.lightGreen, - visualDensity: VisualDensity.adaptivePlatformDensity, + return BlocProvider( + create: (_) => WearSyncCubit(), + child: MaterialApp( + key: ValueKey('firkaWearApp'), + title: 'Firka', + navigatorKey: navigatorKey, + theme: ThemeData( + primarySwatch: Colors.lightGreen, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + localizationsDelegates: [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + home: screen, ), - localizationsDelegates: [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: AppLocalizations.supportedLocales, - home: screen, - routes: { - '/login': (context) => - WearLoginScreen(data, key: ValueKey('wearLoginScreen')), - '/home': (context) => - WearHomeScreen(data, key: ValueKey('wearHomeScreen')), - }, ); } diff --git a/firka_wear/lib/core/bloc/wear_sync_cubit.dart b/firka_wear/lib/core/bloc/wear_sync_cubit.dart new file mode 100644 index 0000000..20b1c89 --- /dev/null +++ b/firka_wear/lib/core/bloc/wear_sync_cubit.dart @@ -0,0 +1,15 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +class WearSyncState { + final bool isSyncing; + + const WearSyncState({this.isSyncing = false}); +} + +class WearSyncCubit extends Cubit { + WearSyncCubit() : super(const WearSyncState()); + + void setSyncing(bool value) { + emit(WearSyncState(isSyncing: value)); + } +} diff --git a/firka_wear/lib/main.dart b/firka_wear/lib/main.dart index 0672053..6505ae3 100644 --- a/firka_wear/lib/main.dart +++ b/firka_wear/lib/main.dart @@ -1,14 +1,15 @@ import 'dart:io'; -import 'package:dio/dio.dart'; +import 'package:firka_wear/app/app_state.dart'; import 'package:firka_wear/app/initialization_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:logging/logging.dart'; import 'package:permission_handler/permission_handler.dart'; -final dio = Dio(); - void main() async { + logger = Logger('FirkaWear'); + dio.options.connectTimeout = Duration(seconds: 5); dio.options.receiveTimeout = Duration(seconds: 3); dio.options.validateStatus = (status) => status != null && status < 500; diff --git a/firka_wear/lib/services/wear_sync_store.dart b/firka_wear/lib/services/wear_sync_store.dart index 995348c..9ba53d4 100644 --- a/firka_wear/lib/services/wear_sync_store.dart +++ b/firka_wear/lib/services/wear_sync_store.dart @@ -129,8 +129,9 @@ class WearSyncStore { final m = date.month; final d = date.day; return _timetable - .where((l) => - l.start.year == y && l.start.month == m && l.start.day == d) + .where( + (l) => l.start.year == y && l.start.month == m && l.start.day == d, + ) .toList() ..sort((a, b) => a.start.compareTo(b.start)); } diff --git a/firka_wear/lib/ui/wear/screens/home/home_screen.dart b/firka_wear/lib/ui/wear/screens/home/home_screen.dart index e837dde..26d44d9 100644 --- a/firka_wear/lib/ui/wear/screens/home/home_screen.dart +++ b/firka_wear/lib/ui/wear/screens/home/home_screen.dart @@ -5,12 +5,14 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_arc_text/flutter_arc_text.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:kreta_api/kreta_api.dart'; import 'package:watch_connectivity/watch_connectivity.dart'; import 'package:wear_plus/wear_plus.dart'; -import 'package:firka_wear/app/initialization.dart'; +import 'package:firka_wear/app/app_state.dart'; +import 'package:firka_wear/core/bloc/wear_sync_cubit.dart'; import 'package:firka_wear/core/debug_helper.dart'; import 'package:firka_wear/core/extensions.dart'; import 'package:firka_wear/l10n/app_localizations.dart'; @@ -40,10 +42,16 @@ class _WearHomeScreenState extends State { final platform = MethodChannel('firka.app/main'); final watch = WatchConnectivity(); StreamSubscription? _messageSub; - bool _syncing = false; + WearSyncCubit? _syncCubit; bool disposed = false; + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _syncCubit ??= context.read(); + } + @override void initState() { super.initState(); @@ -68,7 +76,7 @@ class _WearHomeScreenState extends State { void _onSyncData(Map msg) async { if (disposed) return; - setState(() => _syncing = true); + _syncCubit?.setSyncing(true); try { final lastSyncAt = msg['lastSyncAt'] != null ? DateTime.parse(msg['lastSyncAt'] as String) @@ -95,7 +103,7 @@ class _WearHomeScreenState extends State { today = data.syncStore.getLessonsForDate(now); }); } finally { - if (!disposed) setState(() => _syncing = false); + if (!disposed) _syncCubit?.setSyncing(false); } } @@ -355,80 +363,84 @@ class _WearHomeScreenState extends State { ); } - return Scaffold( - backgroundColor: mode == WearMode.active - ? wearStyle.colors.background - : wearStyle.colors.backgroundAmoled, - body: Stack( - children: [ - Center(child: titleBar), - Center( - child: Column( - children: [ - WatchShape( - builder: (context, shape, child) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [child!], - ); - }, - child: AmbientMode( - builder: (context, mode, child) { - if (this.mode != mode) { - Timer(Duration(milliseconds: 100), () { - setState(() { - this.mode = mode; - }); - }); - } + return BlocBuilder( + builder: (context, syncState) { + return Scaffold( + backgroundColor: mode == WearMode.active + ? wearStyle.colors.background + : wearStyle.colors.backgroundAmoled, + body: Stack( + children: [ + Center(child: titleBar), + Center( + child: Column( + children: [ + WatchShape( + builder: (context, shape, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [child!], + ); + }, + child: AmbientMode( + builder: (context, mode, child) { + if (this.mode != mode) { + Timer(Duration(milliseconds: 100), () { + setState(() { + this.mode = mode; + }); + }); + } - var (body, padding) = buildBody(context, mode); + var (body, padding) = buildBody(context, mode); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.only(top: padding), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [...body], + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: padding), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [...body], + ), + ), + ], + ); + }, + ), + ), + ], + ), + ), + if (syncState.isSyncing) + Positioned.fill( + child: Container( + color: wearStyle.colors.background.withValues(alpha: 0.8), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator(strokeWidth: 2), + ), + SizedBox(height: 12.h), + Text( + AppLocalizations.of(context)!.wear_syncing, + style: wearStyle.fonts.B_16R.apply( + color: wearStyle.colors.textPrimary, ), ), ], - ); - }, + ), + ), ), ), - ], - ), + ], ), - if (_syncing) - Positioned.fill( - child: Container( - color: wearStyle.colors.background.withValues(alpha: 0.8), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox( - width: 32, - height: 32, - child: CircularProgressIndicator(strokeWidth: 2), - ), - SizedBox(height: 12.h), - Text( - AppLocalizations.of(context)!.wear_syncing, - style: wearStyle.fonts.B_16R.apply( - color: wearStyle.colors.textPrimary, - ), - ), - ], - ), - ), - ), - ), - ], - ), + ); + }, ); } diff --git a/firka_wear/lib/ui/wear/screens/login/login_screen.dart b/firka_wear/lib/ui/wear/screens/login/login_screen.dart index bab2ca7..dbd88c4 100644 --- a/firka_wear/lib/ui/wear/screens/login/login_screen.dart +++ b/firka_wear/lib/ui/wear/screens/login/login_screen.dart @@ -1,20 +1,20 @@ -// ignore_for_file: avoid_print - import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kreta_api/kreta_api.dart'; import 'package:watch_connectivity/watch_connectivity.dart'; import 'package:wear_plus/wear_plus.dart'; -import 'package:firka_wear/app/initialization.dart'; +import 'package:firka_wear/app/app_state.dart' as app_state; +import 'package:firka_wear/core/bloc/wear_sync_cubit.dart'; import 'package:firka_wear/data/models/token_model.dart'; import 'package:firka_wear/ui/theme/style.dart'; import 'package:firka_wear/ui/wear/screens/home/home_screen.dart'; class WearLoginScreen extends StatefulWidget { - final WearAppInitialization data; + final app_state.WearAppInitialization data; const WearLoginScreen(this.data, {super.key}); @override @@ -22,16 +22,22 @@ class WearLoginScreen extends StatefulWidget { } class _WearLoginScreen extends State { - WearAppInitialization get initData => widget.data; + app_state.WearAppInitialization get initData => widget.data; bool init = false; bool isPaired = false; bool isReachable = false; bool isMessageSending = false; bool isMessageSent = false; - bool isSyncing = false; final watch = WatchConnectivity(); late Timer connectionTimer; + WearSyncCubit? _syncCubit; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _syncCubit ??= context.read(); + } @override void initState() { @@ -45,13 +51,13 @@ class _WearLoginScreen extends State { : raw; var id = msg["id"]; - debugPrint("[Phone -> Watch]: $id"); + app_state.logger.fine("[Phone -> Watch]: $id"); switch (id) { case "init_data": () async { if (!mounted) return; - setState(() => isSyncing = true); + _syncCubit?.setSyncing(true); try { final auth = msg["auth"] as Map?; if (auth == null) return; @@ -91,14 +97,22 @@ class _WearLoginScreen extends State { 'data': jsonEncode({'id': 'init_done'}), }); if (!mounted) return; + app_state.initData = app_state.WearAppInitialization( + isar: initData.isar, + syncStore: initData.syncStore, + tokenCount: await initData.isar.tokenModels.count(), + l10n: initData.l10n, + devInfo: initData.devInfo, + ); + if (!mounted) return; Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( - builder: (context) => WearHomeScreen(initData), + builder: (context) => WearHomeScreen(app_state.initData), ), (route) => false, ); } finally { - if (mounted) setState(() => isSyncing = false); + if (mounted) _syncCubit?.setSyncing(false); } }(); break; @@ -112,7 +126,7 @@ class _WearLoginScreen extends State { if (!isMessageSending) { isMessageSending = true; - debugPrint("[Watch -> Phone]: ping"); + app_state.logger.fine("[Watch -> Phone]: ping"); watch.sendMessage({ 'data': jsonEncode({ 'id': 'ping', @@ -129,7 +143,7 @@ class _WearLoginScreen extends State { }); } - (List, double) buildBody(BuildContext context) { + (List, double) buildBody(BuildContext context, bool isSyncing) { if (!init) { return ([], 60); } @@ -196,7 +210,7 @@ class _WearLoginScreen extends State { ), ElevatedButton( onPressed: () async { - debugPrint("[Watch -> Phone]: ping"); + app_state.logger.fine("[Watch -> Phone]: ping"); watch.sendMessage({ 'data': jsonEncode({ 'id': 'ping', @@ -242,7 +256,7 @@ class _WearLoginScreen extends State { ), ElevatedButton( onPressed: () async { - debugPrint("[Watch -> Phone]: ping"); + app_state.logger.fine("[Watch -> Phone]: ping"); watch.sendMessage({ 'data': jsonEncode({ 'id': 'ping', @@ -290,39 +304,42 @@ class _WearLoginScreen extends State { @override Widget build(BuildContext context) { - var (body, offset) = buildBody(context); - - return Scaffold( - backgroundColor: wearStyle.colors.background, - body: Center( - child: Column( - children: [ - WatchShape( - builder: (context, shape, child) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( + return BlocBuilder( + builder: (context, syncState) { + var (body, offset) = buildBody(context, syncState.isSyncing); + return Scaffold( + backgroundColor: wearStyle.colors.background, + body: Center( + child: Column( + children: [ + WatchShape( + builder: (context, shape, child) { + return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - padding: EdgeInsets.only(top: offset), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: body, - ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.only(top: offset), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: body, + ), + ), + ], ), + child!, ], - ), - child!, - ], - ); - }, - child: SizedBox(), + ); + }, + child: SizedBox(), + ), + ], ), - ], - ), - ), + ), + ); + }, ); } diff --git a/firka_wear/pubspec.yaml b/firka_wear/pubspec.yaml index 0da1216..2860066 100644 --- a/firka_wear/pubspec.yaml +++ b/firka_wear/pubspec.yaml @@ -41,7 +41,6 @@ dependencies: dio: ^5.8.0+1 isar_community: 3.3.0 isar_community_flutter_libs: 3.3.0 - build_runner: any path_provider: ^2.1.0 carousel_slider: ^5.0.0 dart_jsonwebtoken: ^3.2.0 @@ -58,8 +57,11 @@ dependencies: flutter_screenutil: ^5.9.3 flutter_arc_text: ^0.6.0 flutter_svg: ^1.1.6 + logging: ^1.3.0 + flutter_bloc: ^9.0.0 dev_dependencies: + build_runner: any flutter_test: sdk: flutter flutter_lints: ^6.0.0