forked from firka/firka
firka: migrate state management to Bloc
- Add flutter_bloc dependency - Create ThemeCubit, SettingsCubit, ProfilePictureCubit, ReauthCubit, HomeRefreshCubit - Replace UpdateNotifier/ValueNotifier with Bloc across app - Remove update_notifier.dart and FirkaState globalUpdate listener - Provide cubits via MultiBlocProvider at app root
This commit is contained in:
@@ -13,6 +13,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:isar_community/isar.dart';
|
||||
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/reauth_cubit.dart';
|
||||
import 'package:firka/data/models/token_model.dart';
|
||||
import 'package:firka/data/util.dart';
|
||||
import 'package:firka/core/debug_helper.dart';
|
||||
@@ -60,26 +61,23 @@ class KretaClient {
|
||||
Completer<void>? _tokenMutexCompleter;
|
||||
TokenModel model;
|
||||
Isar isar;
|
||||
final ReauthCubit _reauthCubit;
|
||||
|
||||
static bool needsReauth = false;
|
||||
KretaClient(this.model, this.isar, this._reauthCubit);
|
||||
|
||||
static final ValueNotifier<bool> reauthStateNotifier = ValueNotifier(false);
|
||||
bool get needsReauth => _reauthCubit.state.needsReauth;
|
||||
|
||||
static void clearReauthFlag() {
|
||||
needsReauth = false;
|
||||
reauthStateNotifier.value = false;
|
||||
void clearReauthFlag() {
|
||||
_reauthCubit.clear();
|
||||
debugPrint('[KretaClient] Reauth flag cleared');
|
||||
}
|
||||
|
||||
static Future<void> _setReauthFlag() async {
|
||||
Future<void> _setReauthFlag() async {
|
||||
if (needsReauth) return;
|
||||
needsReauth = true;
|
||||
reauthStateNotifier.value = true;
|
||||
_reauthCubit.setNeedsReauth(true);
|
||||
debugPrint('[KretaClient] Reauth flag set');
|
||||
}
|
||||
|
||||
KretaClient(this.model, this.isar);
|
||||
|
||||
Future<TokenModel> _refreshModelWithCrossDeviceLease(
|
||||
TokenModel sourceToken,
|
||||
) async {
|
||||
|
||||
@@ -3,8 +3,12 @@ import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:firka/api/client/kreta_client.dart';
|
||||
import 'package:firka/core/bloc/home_refresh_cubit.dart';
|
||||
import 'package:firka/core/bloc/profile_picture_cubit.dart';
|
||||
import 'package:firka/core/bloc/reauth_cubit.dart';
|
||||
import 'package:firka/core/bloc/settings_cubit.dart';
|
||||
import 'package:firka/core/bloc/theme_cubit.dart';
|
||||
import 'package:firka/data/models/token_model.dart';
|
||||
import 'package:firka/core/state/update_notifier.dart';
|
||||
import 'package:firka/core/settings.dart';
|
||||
import 'package:firka/l10n/app_localizations.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -24,13 +28,6 @@ GoRouter? appRouter;
|
||||
final dio = Dio();
|
||||
final isBeta = true;
|
||||
|
||||
final ValueNotifier<bool> isLightMode = ValueNotifier<bool>(true);
|
||||
final UpdateNotifier globalUpdate = UpdateNotifier();
|
||||
|
||||
/// Used by home shell screens for pull-to-refresh coordination.
|
||||
final UpdateNotifier homeUpdateNotifier = UpdateNotifier();
|
||||
final UpdateNotifier homeUpdateFinishedNotifier = UpdateNotifier();
|
||||
|
||||
class DeviceInfo {
|
||||
String model;
|
||||
|
||||
@@ -56,8 +53,11 @@ class AppInitialization {
|
||||
bool hasWatchListener = false;
|
||||
Uint8List? profilePicture;
|
||||
SettingsStore settings;
|
||||
UpdateNotifier settingsUpdateNotifier = UpdateNotifier();
|
||||
UpdateNotifier profilePictureUpdateNotifier = UpdateNotifier();
|
||||
ThemeCubit? themeCubit;
|
||||
SettingsCubit? settingsCubit;
|
||||
ProfilePictureCubit? profilePictureCubit;
|
||||
ReauthCubit? reauthCubit;
|
||||
HomeRefreshCubit? homeRefreshCubit;
|
||||
AppLocalizations l10n;
|
||||
final GlobalKey<NavigatorState> navigatorKey;
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/home_refresh_cubit.dart';
|
||||
import 'package:firka/core/bloc/profile_picture_cubit.dart';
|
||||
import 'package:firka/core/bloc/reauth_cubit.dart';
|
||||
import 'package:firka/core/bloc/settings_cubit.dart';
|
||||
import 'package:firka/core/bloc/theme_cubit.dart';
|
||||
import 'package:firka/services/active_account_helper.dart';
|
||||
import 'package:firka/api/client/kreta_client.dart';
|
||||
import 'package:firka/data/models/app_settings_model.dart';
|
||||
@@ -101,6 +106,9 @@ Future<void> initLang(AppInitialization data) async {
|
||||
}
|
||||
|
||||
void initTheme(AppInitialization data) {
|
||||
final themeCubit = data.themeCubit;
|
||||
if (themeCubit == null) return;
|
||||
|
||||
final brightness =
|
||||
SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||
|
||||
@@ -109,24 +117,29 @@ void initTheme(AppInitialization data) {
|
||||
.activeIndex) {
|
||||
case 1:
|
||||
appStyle = lightStyle;
|
||||
isLightMode.value = true;
|
||||
themeCubit.setLightMode(true);
|
||||
break;
|
||||
case 2:
|
||||
appStyle = darkStyle;
|
||||
isLightMode.value = false;
|
||||
themeCubit.setLightMode(false);
|
||||
break;
|
||||
default:
|
||||
if (brightness == Brightness.dark) {
|
||||
appStyle = darkStyle;
|
||||
isLightMode.value = false;
|
||||
themeCubit.setLightMode(false);
|
||||
} else {
|
||||
appStyle = lightStyle;
|
||||
isLightMode.value = true;
|
||||
themeCubit.setLightMode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initData(AppInitialization init) async {
|
||||
init.themeCubit ??= ThemeCubit();
|
||||
init.settingsCubit ??= SettingsCubit();
|
||||
init.profilePictureCubit ??= ProfilePictureCubit();
|
||||
init.reauthCubit ??= ReauthCubit();
|
||||
init.homeRefreshCubit ??= HomeRefreshCubit();
|
||||
await init.settings.load(init.isar.appSettingsModels);
|
||||
await initLang(init);
|
||||
initTheme(init);
|
||||
@@ -136,7 +149,6 @@ Future<void> _initData(AppInitialization init) async {
|
||||
var dispatcher = SchedulerBinding.instance.platformDispatcher;
|
||||
|
||||
dispatcher.onPlatformBrightnessChanged = () {
|
||||
globalUpdate.update();
|
||||
initTheme(init);
|
||||
};
|
||||
|
||||
@@ -158,7 +170,7 @@ Future<void> _initData(AppInitialization init) async {
|
||||
"[Init] System locale changed in auto mode: $previousLocale -> $nextLocale",
|
||||
);
|
||||
}
|
||||
globalUpdate.update();
|
||||
init.themeCubit?.refresh();
|
||||
}());
|
||||
};
|
||||
|
||||
@@ -197,12 +209,12 @@ Future<void> _initData(AppInitialization init) async {
|
||||
return;
|
||||
}
|
||||
logger.fine("Initializing kréta client as: ${token.studentId}");
|
||||
init.client = KretaClient(token, init.isar);
|
||||
init.client = KretaClient(token, init.isar, init.reauthCubit!);
|
||||
|
||||
if (Platform.isIOS) {
|
||||
final expiryDate = token.expiryDate;
|
||||
if (expiryDate != null && expiryDate.isAfter(DateTime.now())) {
|
||||
KretaClient.clearReauthFlag();
|
||||
init.reauthCubit?.clear();
|
||||
}
|
||||
|
||||
unawaited(() async {
|
||||
@@ -287,10 +299,6 @@ Future<AppInitialization> initializeApp() async {
|
||||
|
||||
await _initData(init);
|
||||
|
||||
init.settingsUpdateNotifier.addListener(() {
|
||||
logger.finest("Settings updated");
|
||||
});
|
||||
|
||||
return init;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,15 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/app/initialization.dart';
|
||||
import 'package:firka/core/bloc/home_refresh_cubit.dart';
|
||||
import 'package:firka/core/bloc/profile_picture_cubit.dart';
|
||||
import 'package:firka/core/bloc/reauth_cubit.dart';
|
||||
import 'package:firka/core/bloc/settings_cubit.dart';
|
||||
import 'package:firka/core/bloc/theme_cubit.dart';
|
||||
import 'package:firka/core/firka_bundle.dart';
|
||||
import 'package:firka/routing/app_router.dart';
|
||||
import 'package:firka/services/watch_sync_helper.dart';
|
||||
@@ -100,44 +106,58 @@ class _InitializationScreenState extends State<InitializationScreen> {
|
||||
appRouter = _router;
|
||||
}
|
||||
|
||||
return MaterialApp.router(
|
||||
title: 'Firka',
|
||||
key: const ValueKey('firkaApp'),
|
||||
routerConfig: _router!,
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.lightGreen,
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
),
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
final themeCubit = initData.themeCubit!;
|
||||
final settingsCubit = initData.settingsCubit!;
|
||||
final profilePictureCubit = initData.profilePictureCubit!;
|
||||
final reauthCubit = initData.reauthCubit!;
|
||||
final homeRefreshCubit = initData.homeRefreshCubit!;
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<ThemeCubit>.value(value: themeCubit),
|
||||
BlocProvider<SettingsCubit>.value(value: settingsCubit),
|
||||
BlocProvider<ProfilePictureCubit>.value(value: profilePictureCubit),
|
||||
BlocProvider<ReauthCubit>.value(value: reauthCubit),
|
||||
BlocProvider<HomeRefreshCubit>.value(value: homeRefreshCubit),
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
builder: (context, child) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: isLightMode,
|
||||
builder: (context, isLight, _) {
|
||||
final overlay = SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: isLight
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
statusBarBrightness: isLight
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemStatusBarContrastEnforced: false,
|
||||
);
|
||||
child: MaterialApp.router(
|
||||
title: 'Firka',
|
||||
key: const ValueKey('firkaApp'),
|
||||
routerConfig: _router!,
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.lightGreen,
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
),
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
builder: (context, child) {
|
||||
return BlocBuilder<ThemeCubit, ThemeState>(
|
||||
builder: (context, themeState) {
|
||||
final isLight = themeState.isLightMode;
|
||||
final overlay = SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: isLight
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
statusBarBrightness: isLight
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
systemStatusBarContrastEnforced: false,
|
||||
);
|
||||
|
||||
SystemChrome.setSystemUIOverlayStyle(overlay);
|
||||
SystemChrome.setSystemUIOverlayStyle(overlay);
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: overlay,
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: overlay,
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
19
firka/lib/core/bloc/home_refresh_cubit.dart
Normal file
19
firka/lib/core/bloc/home_refresh_cubit.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class HomeRefreshState {
|
||||
final int refreshTrigger;
|
||||
|
||||
const HomeRefreshState({this.refreshTrigger = 0});
|
||||
}
|
||||
|
||||
class HomeRefreshCubit extends Cubit<HomeRefreshState> {
|
||||
HomeRefreshCubit() : super(const HomeRefreshState());
|
||||
|
||||
void requestRefresh() {
|
||||
emit(HomeRefreshState(refreshTrigger: state.refreshTrigger + 1));
|
||||
}
|
||||
|
||||
void onRefreshComplete() {
|
||||
emit(HomeRefreshState(refreshTrigger: state.refreshTrigger));
|
||||
}
|
||||
}
|
||||
15
firka/lib/core/bloc/profile_picture_cubit.dart
Normal file
15
firka/lib/core/bloc/profile_picture_cubit.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ProfilePictureState {
|
||||
final int version;
|
||||
|
||||
const ProfilePictureState({this.version = 0});
|
||||
}
|
||||
|
||||
class ProfilePictureCubit extends Cubit<ProfilePictureState> {
|
||||
ProfilePictureCubit() : super(const ProfilePictureState());
|
||||
|
||||
void notifyChanged() {
|
||||
emit(ProfilePictureState(version: state.version + 1));
|
||||
}
|
||||
}
|
||||
19
firka/lib/core/bloc/reauth_cubit.dart
Normal file
19
firka/lib/core/bloc/reauth_cubit.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ReauthState {
|
||||
final bool needsReauth;
|
||||
|
||||
const ReauthState({this.needsReauth = false});
|
||||
}
|
||||
|
||||
class ReauthCubit extends Cubit<ReauthState> {
|
||||
ReauthCubit() : super(const ReauthState());
|
||||
|
||||
void setNeedsReauth(bool value) {
|
||||
emit(ReauthState(needsReauth: value));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
emit(const ReauthState(needsReauth: false));
|
||||
}
|
||||
}
|
||||
15
firka/lib/core/bloc/settings_cubit.dart
Normal file
15
firka/lib/core/bloc/settings_cubit.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SettingsState {
|
||||
final int version;
|
||||
|
||||
const SettingsState({this.version = 0});
|
||||
}
|
||||
|
||||
class SettingsCubit extends Cubit<SettingsState> {
|
||||
SettingsCubit() : super(const SettingsState());
|
||||
|
||||
void notifyChanged() {
|
||||
emit(SettingsState(version: state.version + 1));
|
||||
}
|
||||
}
|
||||
20
firka/lib/core/bloc/theme_cubit.dart
Normal file
20
firka/lib/core/bloc/theme_cubit.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ThemeState {
|
||||
final bool isLightMode;
|
||||
|
||||
const ThemeState({required this.isLightMode});
|
||||
}
|
||||
|
||||
class ThemeCubit extends Cubit<ThemeState> {
|
||||
ThemeCubit({bool initialLightMode = true})
|
||||
: super(ThemeState(isLightMode: initialLightMode));
|
||||
|
||||
void setLightMode(bool isLight) {
|
||||
emit(ThemeState(isLightMode: isLight));
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
emit(ThemeState(isLightMode: state.isLightMode));
|
||||
}
|
||||
}
|
||||
@@ -21,5 +21,5 @@ Future<void> pickProfilePicture(
|
||||
await File(p.join(dataDir.path, "profile.webp")).writeAsBytes(bytes);
|
||||
|
||||
data.profilePicture = bytes;
|
||||
data.profilePictureUpdateNotifier.update();
|
||||
data.profilePictureCubit?.notifyChanged();
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ class SettingsStore {
|
||||
initData.settings = SettingsStore(initData.l10n);
|
||||
await initData.settings.load(initData.isar.appSettingsModels);
|
||||
|
||||
globalUpdate.update();
|
||||
initData.themeCubit?.refresh();
|
||||
runApp(InitializationScreen());
|
||||
},
|
||||
),
|
||||
@@ -340,7 +340,6 @@ class SettingsStore {
|
||||
always,
|
||||
() async {
|
||||
initTheme(initData);
|
||||
globalUpdate.update();
|
||||
},
|
||||
),
|
||||
}),
|
||||
@@ -660,7 +659,7 @@ class SettingsStore {
|
||||
await item.save(model);
|
||||
}
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
|
||||
Future<void> load(IsarCollection<AppSettingsModel> model) async {
|
||||
@@ -754,7 +753,7 @@ class SettingsGroup implements SettingsItem {
|
||||
await item.save(model);
|
||||
}
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,7 +795,7 @@ class SettingsSubGroup implements SettingsItem {
|
||||
await item.save(model);
|
||||
}
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,7 +1009,7 @@ class SettingsKretenAccountPicker implements SettingsItem {
|
||||
|
||||
await model.put(v);
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,7 +1077,7 @@ class SettingsAppIconPicker implements SettingsItem {
|
||||
|
||||
await model.put(v);
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1129,7 +1128,7 @@ class SettingsBoolean implements SettingsItem {
|
||||
|
||||
await model.put(v);
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1176,7 +1175,7 @@ class SettingsItemsRadio implements SettingsItem {
|
||||
|
||||
await model.put(v);
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1242,7 +1241,7 @@ class SettingsDouble implements SettingsItem {
|
||||
|
||||
await model.put(v);
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1288,7 +1287,7 @@ class SettingsString implements SettingsItem {
|
||||
|
||||
await model.put(v);
|
||||
|
||||
initData.settingsUpdateNotifier.update();
|
||||
initData.settingsCubit?.notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,3 @@
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
abstract class FirkaState<T extends StatefulWidget> extends State<T> {
|
||||
@override
|
||||
@mustCallSuper
|
||||
void initState() {
|
||||
super.initState();
|
||||
globalUpdate.addListener(_doUpdate);
|
||||
}
|
||||
|
||||
void _doUpdate() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
@mustCallSuper
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
globalUpdate.removeListener(_doUpdate);
|
||||
globalUpdate.addListener(_doUpdate);
|
||||
}
|
||||
|
||||
@override
|
||||
@mustCallSuper
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
||||
globalUpdate.removeListener(_doUpdate);
|
||||
}
|
||||
}
|
||||
abstract class FirkaState<T extends StatefulWidget> extends State<T> {}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UpdateNotifier with ChangeNotifier {
|
||||
void update() {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import 'package:firka/api/model/timetable.dart';
|
||||
import 'package:firka/core/debug_helper.dart';
|
||||
import 'package:firka/data/ios_widget_helper.dart';
|
||||
import 'package:firka/core/settings.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
@@ -149,7 +149,11 @@ class WidgetCacheHelper {
|
||||
theme = 'dark';
|
||||
break;
|
||||
default:
|
||||
theme = isLightMode.value ? 'light' : 'dark';
|
||||
theme =
|
||||
SchedulerBinding.instance.platformDispatcher.platformBrightness ==
|
||||
Brightness.light
|
||||
? 'light'
|
||||
: 'dark';
|
||||
}
|
||||
|
||||
final now = timeNow();
|
||||
|
||||
@@ -111,11 +111,7 @@ GoRouter createAppRouter() {
|
||||
key: state.pageKey,
|
||||
child: DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: HomeMainScreen(
|
||||
initData,
|
||||
homeUpdateNotifier,
|
||||
homeUpdateFinishedNotifier,
|
||||
),
|
||||
child: HomeMainScreen(initData),
|
||||
),
|
||||
),
|
||||
routes: [
|
||||
@@ -126,11 +122,7 @@ GoRouter createAppRouter() {
|
||||
activeSubjectUid = uid;
|
||||
return DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: HomeGradesSubjectScreen(
|
||||
initData,
|
||||
homeUpdateNotifier,
|
||||
homeUpdateFinishedNotifier,
|
||||
),
|
||||
child: HomeGradesSubjectScreen(initData),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -146,11 +138,7 @@ GoRouter createAppRouter() {
|
||||
key: state.pageKey,
|
||||
child: DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: HomeGradesScreen(
|
||||
initData,
|
||||
homeUpdateNotifier,
|
||||
homeUpdateFinishedNotifier,
|
||||
),
|
||||
child: HomeGradesScreen(initData),
|
||||
),
|
||||
),
|
||||
routes: [
|
||||
@@ -161,11 +149,7 @@ GoRouter createAppRouter() {
|
||||
activeSubjectUid = uid;
|
||||
return DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: HomeGradesSubjectScreen(
|
||||
initData,
|
||||
homeUpdateNotifier,
|
||||
homeUpdateFinishedNotifier,
|
||||
),
|
||||
child: HomeGradesSubjectScreen(initData),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -181,11 +165,7 @@ GoRouter createAppRouter() {
|
||||
key: state.pageKey,
|
||||
child: DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: HomeTimetableScreen(
|
||||
initData,
|
||||
homeUpdateNotifier,
|
||||
homeUpdateFinishedNotifier,
|
||||
),
|
||||
child: HomeTimetableScreen(initData),
|
||||
),
|
||||
),
|
||||
routes: [
|
||||
@@ -193,11 +173,7 @@ GoRouter createAppRouter() {
|
||||
path: 'monthly',
|
||||
builder: (context, state) => DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: HomeTimetableMonthlyScreen(
|
||||
initData,
|
||||
homeUpdateNotifier,
|
||||
homeUpdateFinishedNotifier,
|
||||
),
|
||||
child: HomeTimetableMonthlyScreen(initData),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
@@ -207,11 +183,7 @@ GoRouter createAppRouter() {
|
||||
activeSubjectUid = uid;
|
||||
return DefaultAssetBundle(
|
||||
bundle: FirkaBundle(),
|
||||
child: HomeGradesSubjectScreen(
|
||||
initData,
|
||||
homeUpdateNotifier,
|
||||
homeUpdateFinishedNotifier,
|
||||
),
|
||||
child: HomeGradesSubjectScreen(initData),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -290,7 +290,7 @@ class LiveActivityService {
|
||||
);
|
||||
}
|
||||
|
||||
globalUpdate.update();
|
||||
initData.themeCubit?.refresh();
|
||||
} catch (e) {
|
||||
_logger.warning('Error syncing global settings: $e');
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ class WatchSyncHelper {
|
||||
if (initDone) {
|
||||
initData.tokens = [];
|
||||
}
|
||||
KretaClient.clearReauthFlag();
|
||||
if (initDone) initData.reauthCubit?.clear();
|
||||
|
||||
await prefs.setBool(_iosFreshInstallHandledKey, true);
|
||||
return true;
|
||||
@@ -529,7 +529,7 @@ class WatchSyncHelper {
|
||||
);
|
||||
final expiryDate = token?.expiryDate;
|
||||
if (expiryDate != null && expiryDate.isAfter(DateTime.now())) {
|
||||
KretaClient.clearReauthFlag();
|
||||
if (initDone) initData.reauthCubit?.clear();
|
||||
debugPrint(
|
||||
'[WatchSync] Cleared reauth flag after iCloud notification (token is valid)',
|
||||
);
|
||||
@@ -562,7 +562,7 @@ class WatchSyncHelper {
|
||||
return {'error': 'token_incomplete'};
|
||||
}
|
||||
|
||||
if (KretaClient.needsReauth) {
|
||||
if (initData.client.needsReauth) {
|
||||
debugPrint('[WatchSync] iPhone needs reauth');
|
||||
return {'error': 'needsReauth'};
|
||||
}
|
||||
@@ -699,7 +699,7 @@ class WatchSyncHelper {
|
||||
initData.tokens = await initData.isar.tokenModels.where().findAll();
|
||||
if (isForActiveAccount) {
|
||||
initData.client.model = newToken;
|
||||
KretaClient.clearReauthFlag();
|
||||
if (initDone) initData.reauthCubit?.clear();
|
||||
} else {
|
||||
debugPrint(
|
||||
'[WatchSync] Stored token for inactive account ($watchStudentIdNorm), active is $expectedStudentIdNorm',
|
||||
@@ -918,7 +918,7 @@ class WatchSyncHelper {
|
||||
(expectedStudentIdNorm == null ||
|
||||
newToken.studentIdNorm == expectedStudentIdNorm);
|
||||
if (shouldClearReauth) {
|
||||
KretaClient.clearReauthFlag();
|
||||
if (initDone) initData.reauthCubit?.clear();
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
@@ -1008,7 +1008,7 @@ class WatchSyncHelper {
|
||||
currentToken.accessToken != null &&
|
||||
currentToken.refreshToken != null &&
|
||||
currentToken.expiryDate != null &&
|
||||
!KretaClient.needsReauth) {
|
||||
!(initData.reauthCubit?.state.needsReauth ?? false)) {
|
||||
debugPrint('[WatchSync] Sending iPhone token to Watch (no response)');
|
||||
await _sendTokenToWatchInternal(
|
||||
currentToken,
|
||||
@@ -1025,7 +1025,7 @@ class WatchSyncHelper {
|
||||
currentToken.accessToken != null &&
|
||||
currentToken.refreshToken != null &&
|
||||
currentToken.expiryDate != null &&
|
||||
!KretaClient.needsReauth) {
|
||||
!(initData.reauthCubit?.state.needsReauth ?? false)) {
|
||||
debugPrint(
|
||||
'[WatchSync] Sending iPhone token to Watch (Watch has no token)',
|
||||
);
|
||||
@@ -1058,7 +1058,7 @@ class WatchSyncHelper {
|
||||
currentToken.accessToken != null &&
|
||||
currentToken.refreshToken != null &&
|
||||
currentToken.expiryDate != null &&
|
||||
!KretaClient.needsReauth) {
|
||||
!(initData.reauthCubit?.state.needsReauth ?? false)) {
|
||||
await _sendTokenToWatchInternal(
|
||||
currentToken,
|
||||
allowExpiredAccessToken: true,
|
||||
@@ -1080,7 +1080,7 @@ class WatchSyncHelper {
|
||||
currentToken.expiryDate,
|
||||
skew: const Duration(),
|
||||
) &&
|
||||
!KretaClient.needsReauth) {
|
||||
!(initData.reauthCubit?.state.needsReauth ?? false)) {
|
||||
await _sendTokenToWatchInternal(
|
||||
currentToken,
|
||||
allowExpiredAccessToken: true,
|
||||
@@ -1135,7 +1135,7 @@ class WatchSyncHelper {
|
||||
|
||||
if (expectedStudentIdNorm == null ||
|
||||
newToken.studentIdNorm == expectedStudentIdNorm) {
|
||||
KretaClient.clearReauthFlag();
|
||||
if (initDone) initData.reauthCubit?.clear();
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
|
||||
@@ -9,12 +9,14 @@ import 'package:firka/core/settings.dart';
|
||||
import 'package:firka/ui/components/firka_shadow.dart';
|
||||
import 'package:firka/ui/shared/firka_icon.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:majesticons_flutter/majesticons_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/theme_cubit.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
import 'package:firka/ui/phone/pages/home/home_grades.dart';
|
||||
import 'package:firka/ui/phone/widgets/lesson.dart';
|
||||
@@ -859,7 +861,7 @@ Future<void> showHomeworkBottomSheet(
|
||||
shadow: true,
|
||||
child: Card(
|
||||
color: appStyle.colors.card,
|
||||
shadowColor: isLightMode.value
|
||||
shadowColor: context.watch<ThemeCubit>().state.isLightMode
|
||||
? null
|
||||
: Colors.transparent,
|
||||
child: Align(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:firka/ui/components/firka_shadow.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/theme_cubit.dart';
|
||||
import 'package:firka/ui/components/firka_shadow.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
|
||||
enum Attach { none, bottom, top }
|
||||
@@ -35,6 +36,7 @@ class FirkaCard extends StatelessWidget {
|
||||
var attached = this.attached != null ? this.attached! : Attach.none;
|
||||
final defaultRounding = 16.0;
|
||||
final attachedRounding = 8.0;
|
||||
final isLight = context.watch<ThemeCubit>().state.isLightMode;
|
||||
|
||||
if (extra != null) {
|
||||
return SizedBox(
|
||||
@@ -44,7 +46,7 @@ class FirkaCard extends StatelessWidget {
|
||||
shadow: shadow,
|
||||
child: Card(
|
||||
color: color ?? appStyle.colors.card,
|
||||
shadowColor: isLightMode.value && shadow
|
||||
shadowColor: isLight && shadow
|
||||
? null
|
||||
: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -94,7 +96,7 @@ class FirkaCard extends StatelessWidget {
|
||||
shadow: shadow,
|
||||
child: Card(
|
||||
color: color ?? appStyle.colors.card,
|
||||
shadowColor: isLightMode.value && shadow
|
||||
shadowColor: isLight && shadow
|
||||
? null
|
||||
: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:firka/core/bloc/theme_cubit.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
|
||||
class FirkaShadow extends StatelessWidget {
|
||||
@@ -31,7 +32,8 @@ class FirkaShadow extends StatelessWidget {
|
||||
return ClipRRect(borderRadius: borderRadius, child: child);
|
||||
}
|
||||
|
||||
if (isLightMode.value) {
|
||||
final isLight = context.watch<ThemeCubit>().state.isLightMode;
|
||||
if (isLight) {
|
||||
return child;
|
||||
} else {
|
||||
return Container(
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import 'package:firka/data/models/app_settings_model.dart';
|
||||
import 'package:firka/core/settings.dart';
|
||||
import 'package:firka/ui/components/firka_shadow.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
import 'package:firka/ui/shared/firka_icon.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:majesticons_flutter/majesticons_flutter.dart';
|
||||
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/theme_cubit.dart';
|
||||
import 'package:firka/core/settings.dart';
|
||||
import 'package:firka/data/models/app_settings_model.dart';
|
||||
import 'package:firka/ui/components/firka_shadow.dart';
|
||||
import 'package:firka/ui/shared/firka_icon.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
@@ -28,7 +30,9 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
shadow: true,
|
||||
child: Card(
|
||||
color: appStyle.colors.card,
|
||||
shadowColor: isLightMode.value ? null : Colors.transparent,
|
||||
shadowColor: context.watch<ThemeCubit>().state.isLightMode
|
||||
? null
|
||||
: Colors.transparent,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
@@ -130,7 +134,11 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
shadow: true,
|
||||
child: Card(
|
||||
color: appStyle.colors.card,
|
||||
shadowColor: isLightMode.value
|
||||
shadowColor:
|
||||
context
|
||||
.watch<ThemeCubit>()
|
||||
.state
|
||||
.isLightMode
|
||||
? null
|
||||
: Colors.transparent,
|
||||
child: Align(
|
||||
@@ -179,7 +187,11 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
shadow: true,
|
||||
child: Card(
|
||||
color: appStyle.colors.card,
|
||||
shadowColor: isLightMode.value
|
||||
shadowColor:
|
||||
context
|
||||
.watch<ThemeCubit>()
|
||||
.state
|
||||
.isLightMode
|
||||
? null
|
||||
: Colors.transparent,
|
||||
child: Align(
|
||||
@@ -238,6 +250,8 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
onTap: () async {
|
||||
if (isDebug()) return;
|
||||
if (debugCounter == 10) {
|
||||
final navigator = Navigator.of(context);
|
||||
final router = GoRouter.of(context);
|
||||
data.settings
|
||||
.group("settings")
|
||||
.setBoolean(
|
||||
@@ -257,8 +271,8 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) {
|
||||
.group("settings")["developer_enabled"]!
|
||||
.postUpdate();
|
||||
|
||||
context.pop();
|
||||
context.go('/home');
|
||||
navigator.pop();
|
||||
router.go('/home');
|
||||
} else if (debugCounter < 10) {
|
||||
debugCounter++;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:firka/ui/components/grade_helpers.dart';
|
||||
import 'package:firka/ui/phone/widgets/grade_chart.dart';
|
||||
import 'package:firka/ui/shared/grade_small_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:firka/api/consts.dart';
|
||||
@@ -15,22 +16,15 @@ import 'package:firka/api/model/subject.dart';
|
||||
import 'package:firka/api/model/timetable.dart';
|
||||
import 'package:firka/core/debug_helper.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/core/state/update_notifier.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/home_refresh_cubit.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
import 'package:firka/ui/shared/delayed_spinner.dart';
|
||||
|
||||
class HomeGradesScreen extends StatefulWidget {
|
||||
final AppInitialization data;
|
||||
final UpdateNotifier updateNotifier;
|
||||
final UpdateNotifier finishNotifier;
|
||||
|
||||
const HomeGradesScreen(
|
||||
this.data,
|
||||
this.updateNotifier,
|
||||
this.finishNotifier, {
|
||||
super.key,
|
||||
});
|
||||
const HomeGradesScreen(this.data, {super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _HomeGradesScreen();
|
||||
@@ -48,15 +42,8 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
|
||||
ApiResponse<List<ClassGroup>>? classGroups;
|
||||
ApiResponse<List<SubjectAverage>>? lessons;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(HomeGradesScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
}
|
||||
|
||||
void updateListener() async {
|
||||
void _onRefreshRequested(BuildContext context) async {
|
||||
final cubit = context.read<HomeRefreshCubit>();
|
||||
var now = timeNow();
|
||||
var start = now.subtract(Duration(days: now.weekday - 1));
|
||||
var end = start.add(Duration(days: 6));
|
||||
@@ -72,16 +59,16 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
|
||||
);
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
widget.finishNotifier.update();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
cubit.onRefreshComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
|
||||
(() async {
|
||||
var now = timeNow();
|
||||
var start = now.subtract(Duration(days: now.weekday - 1));
|
||||
@@ -100,13 +87,18 @@ class _HomeGradesScreen extends FirkaState<HomeGradesScreen> {
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<HomeRefreshCubit, HomeRefreshState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.refreshTrigger != previous.refreshTrigger,
|
||||
listener: (context, state) {
|
||||
_onRefreshRequested(context);
|
||||
},
|
||||
child: _buildContent(context),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget _buildContent(BuildContext context) {
|
||||
if (grades == null || week == null) {
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 1.35,
|
||||
|
||||
@@ -7,26 +7,20 @@ import 'package:firka/ui/phone/pages/home/home_grades.dart';
|
||||
import 'package:firka/ui/shared/class_icon.dart';
|
||||
import 'package:firka/ui/shared/firka_icon.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:majesticons_flutter/majesticons_flutter.dart';
|
||||
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/core/state/update_notifier.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/home_refresh_cubit.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
|
||||
class HomeGradesSubjectScreen extends StatefulWidget {
|
||||
final AppInitialization data;
|
||||
final UpdateNotifier updateNotifier;
|
||||
final UpdateNotifier finishNotifier;
|
||||
|
||||
const HomeGradesSubjectScreen(
|
||||
this.data,
|
||||
this.updateNotifier,
|
||||
this.finishNotifier, {
|
||||
super.key,
|
||||
});
|
||||
const HomeGradesSubjectScreen(this.data, {super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _HomeGradesSubjectScreen();
|
||||
@@ -35,30 +29,22 @@ class HomeGradesSubjectScreen extends StatefulWidget {
|
||||
class _HomeGradesSubjectScreen extends FirkaState<HomeGradesSubjectScreen> {
|
||||
Iterable<Grade>? grades;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(HomeGradesSubjectScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
}
|
||||
|
||||
void updateListener() async {
|
||||
void _onRefreshRequested(BuildContext context) async {
|
||||
final cubit = context.read<HomeRefreshCubit>();
|
||||
grades = (await widget.data.client.getGrades(forceCache: false)).response!
|
||||
.where((grade) => grade.subject.uid == activeSubjectUid)
|
||||
.where((grade) => grade.type.name != "felevi_jegy_ertekeles");
|
||||
|
||||
if (mounted) setState(() {});
|
||||
|
||||
widget.finishNotifier.update();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
cubit.onRefreshComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
|
||||
(() async {
|
||||
grades = (await widget.data.client.getGrades()).response!
|
||||
.where((grade) => grade.subject.uid == activeSubjectUid)
|
||||
@@ -69,13 +55,18 @@ class _HomeGradesSubjectScreen extends FirkaState<HomeGradesSubjectScreen> {
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<HomeRefreshCubit, HomeRefreshState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.refreshTrigger != previous.refreshTrigger,
|
||||
listener: (context, state) {
|
||||
_onRefreshRequested(context);
|
||||
},
|
||||
child: _buildContent(context),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget _buildContent(BuildContext context) {
|
||||
if (grades != null && grades!.isNotEmpty && activeSubjectUid != "") {
|
||||
var aGrade = grades!.first;
|
||||
var groups = grades!.groupList((grade) => grade.recordDate);
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:firka/ui/phone/widgets/info_board_item.dart';
|
||||
import 'package:firka/ui/phone/widgets/lesson_small.dart';
|
||||
import 'package:firka/ui/shared/delayed_spinner.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:majesticons_flutter/majesticons_flutter.dart';
|
||||
|
||||
@@ -22,8 +23,8 @@ import 'package:firka/core/debug_helper.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/ui/components/firka_card.dart';
|
||||
import 'package:firka/ui/components/grade.dart';
|
||||
import 'package:firka/core/state/update_notifier.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/home_refresh_cubit.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
import 'package:firka/ui/shared/firka_icon.dart';
|
||||
import '../../widgets/home_main_welcome.dart';
|
||||
@@ -31,15 +32,8 @@ import '../../widgets/lesson_big.dart';
|
||||
|
||||
class HomeMainScreen extends StatefulWidget {
|
||||
final AppInitialization data;
|
||||
final UpdateNotifier updateNotifier;
|
||||
final UpdateNotifier finishNotifier;
|
||||
|
||||
const HomeMainScreen(
|
||||
this.data,
|
||||
this.updateNotifier,
|
||||
this.finishNotifier, {
|
||||
super.key,
|
||||
});
|
||||
const HomeMainScreen(this.data, {super.key});
|
||||
|
||||
@override
|
||||
State<HomeMainScreen> createState() => _HomeMainScreen();
|
||||
@@ -58,18 +52,12 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
|
||||
Student? student;
|
||||
Timer? timer;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(HomeMainScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
}
|
||||
|
||||
void updateListener() async {
|
||||
void _onRefreshRequested(BuildContext context) async {
|
||||
final cubit = context.read<HomeRefreshCubit>();
|
||||
await fetchData(cacheOnly: false);
|
||||
|
||||
widget.finishNotifier.update();
|
||||
if (mounted) {
|
||||
cubit.onRefreshComplete();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchData({bool cacheOnly = true}) async {
|
||||
@@ -190,8 +178,6 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
|
||||
now = timeNow();
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -210,14 +196,22 @@ class _HomeMainScreen extends FirkaState<HomeMainScreen> {
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
|
||||
timer?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<HomeRefreshCubit, HomeRefreshState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.refreshTrigger != previous.refreshTrigger,
|
||||
listener: (context, state) {
|
||||
_onRefreshRequested(context);
|
||||
},
|
||||
child: _buildContent(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
Widget welcomeWidget = SizedBox();
|
||||
Widget nextClass = SizedBox();
|
||||
Widget? nextTest;
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:firka/ui/phone/screens/settings/settings_screen.dart';
|
||||
import 'package:firka/ui/phone/widgets/bubble_test.dart';
|
||||
import 'package:firka/ui/shared/delayed_spinner.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:majesticons_flutter/majesticons_flutter.dart';
|
||||
@@ -20,23 +21,17 @@ import 'package:transparent_pointer/transparent_pointer.dart';
|
||||
|
||||
import 'package:firka/api/consts.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/core/state/update_notifier.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/home_refresh_cubit.dart';
|
||||
import 'package:firka/core/bloc/settings_cubit.dart';
|
||||
import 'package:firka/ui/shared/firka_icon.dart';
|
||||
import '../../widgets/bottom_tt_icon.dart';
|
||||
import '../../widgets/tt_day.dart';
|
||||
|
||||
class HomeTimetableScreen extends StatefulWidget {
|
||||
final AppInitialization data;
|
||||
final UpdateNotifier updateNotifier;
|
||||
final UpdateNotifier finishNotifier;
|
||||
|
||||
const HomeTimetableScreen(
|
||||
this.data,
|
||||
this.updateNotifier,
|
||||
this.finishNotifier, {
|
||||
super.key,
|
||||
});
|
||||
const HomeTimetableScreen(this.data, {super.key});
|
||||
|
||||
@override
|
||||
State<HomeTimetableScreen> createState() => _HomeTimetableScreen();
|
||||
@@ -70,9 +65,6 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
widget.data.settingsUpdateNotifier.addListener(settingsUpdateListener);
|
||||
|
||||
now = timeNow();
|
||||
initForWeek(now!);
|
||||
|
||||
@@ -252,27 +244,15 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
|
||||
}
|
||||
}
|
||||
|
||||
void updateListener() async {
|
||||
void _onRefreshRequested(BuildContext context) async {
|
||||
final cubit = context.read<HomeRefreshCubit>();
|
||||
if (now != null) {
|
||||
await initForWeek(now!, forceCache: false);
|
||||
if (mounted) setState(() {});
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
cubit.onRefreshComplete();
|
||||
}
|
||||
}
|
||||
widget.finishNotifier.update();
|
||||
}
|
||||
|
||||
void settingsUpdateListener() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(HomeTimetableScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
|
||||
widget.data.settingsUpdateNotifier.removeListener(settingsUpdateListener);
|
||||
widget.data.settingsUpdateNotifier.addListener(settingsUpdateListener);
|
||||
}
|
||||
|
||||
bool animating = false;
|
||||
@@ -371,6 +351,22 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<SettingsCubit, SettingsState>(
|
||||
listener: (context, state) {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
child: BlocListener<HomeRefreshCubit, HomeRefreshState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.refreshTrigger != previous.refreshTrigger,
|
||||
listener: (context, state) {
|
||||
_onRefreshRequested(context);
|
||||
},
|
||||
child: _buildContent(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
if (lessons != null && tests != null && events != null && dates != null) {
|
||||
List<Widget> ttWidgets = [];
|
||||
List<Widget> ttDays = [];
|
||||
@@ -773,12 +769,4 @@ class _HomeTimetableScreen extends FirkaState<HomeTimetableScreen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
widget.data.settingsUpdateNotifier.removeListener(settingsUpdateListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,29 +7,23 @@ import 'package:firka/core/settings.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
import 'package:firka/ui/shared/delayed_spinner.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:majesticons_flutter/majesticons_flutter.dart';
|
||||
import 'package:transparent_pointer/transparent_pointer.dart';
|
||||
|
||||
import 'package:firka/api/model/test.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/core/state/update_notifier.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/core/bloc/home_refresh_cubit.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/ui/shared/firka_icon.dart';
|
||||
import '../../screens/settings/settings_screen.dart';
|
||||
|
||||
class HomeTimetableMonthlyScreen extends StatefulWidget {
|
||||
final AppInitialization data;
|
||||
final UpdateNotifier updateNotifier;
|
||||
final UpdateNotifier finishNotifier;
|
||||
|
||||
const HomeTimetableMonthlyScreen(
|
||||
this.data,
|
||||
this.updateNotifier,
|
||||
this.finishNotifier, {
|
||||
super.key,
|
||||
});
|
||||
const HomeTimetableMonthlyScreen(this.data, {super.key});
|
||||
|
||||
@override
|
||||
State<HomeTimetableMonthlyScreen> createState() =>
|
||||
@@ -95,41 +89,38 @@ class _HomeTimetableMonthlyScreen
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(HomeTimetableMonthlyScreen oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
}
|
||||
|
||||
void updateListener() async {
|
||||
void _onRefreshRequested(BuildContext context) async {
|
||||
final cubit = context.read<HomeRefreshCubit>();
|
||||
if (now != null) {
|
||||
await initForMonth(now!, forceCache: false);
|
||||
setState(() {});
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
cubit.onRefreshComplete();
|
||||
}
|
||||
}
|
||||
widget.finishNotifier.update();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.updateNotifier.addListener(updateListener);
|
||||
|
||||
now = timeNow();
|
||||
initForMonth(now!);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
||||
widget.updateNotifier.removeListener(updateListener);
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<HomeRefreshCubit, HomeRefreshState>(
|
||||
listenWhen: (previous, current) =>
|
||||
current.refreshTrigger != previous.refreshTrigger,
|
||||
listener: (context, state) {
|
||||
_onRefreshRequested(context);
|
||||
},
|
||||
child: _buildContent(context),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget _buildContent(BuildContext context) {
|
||||
if (lessons != null &&
|
||||
omissions != null &&
|
||||
tests != null &&
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:firka/api/client/kreta_client.dart';
|
||||
import 'package:firka/api/client/kreta_stream.dart';
|
||||
import 'package:firka/api/exceptions/token.dart';
|
||||
import 'package:firka/core/extensions.dart';
|
||||
@@ -14,12 +13,16 @@ import 'package:firka/ui/theme/style.dart';
|
||||
import 'package:firka/ui/phone/pages/extras/reauth_toast.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:majesticons_flutter/majesticons_flutter.dart';
|
||||
|
||||
import 'package:firka/data/widget.dart';
|
||||
import 'package:firka/core/debug_helper.dart';
|
||||
import 'package:firka/core/bloc/profile_picture_cubit.dart';
|
||||
import 'package:firka/core/bloc/reauth_cubit.dart';
|
||||
import 'package:firka/core/bloc/settings_cubit.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/core/image_preloader.dart';
|
||||
import 'package:firka/ui/shared/delayed_spinner.dart';
|
||||
@@ -118,7 +121,7 @@ class _HomeScreenState extends FirkaState<HomeScreen>
|
||||
|
||||
final now = DateTime.now();
|
||||
final shouldRunRecovery =
|
||||
KretaClient.needsReauth ||
|
||||
initData.client.needsReauth ||
|
||||
activeToken == null ||
|
||||
activeToken.expiryDate == null ||
|
||||
activeToken.expiryDate!.isBefore(now.add(const Duration(seconds: 60)));
|
||||
@@ -153,7 +156,7 @@ class _HomeScreenState extends FirkaState<HomeScreen>
|
||||
if (selectedToken != null) {
|
||||
initData.client.model = selectedToken;
|
||||
}
|
||||
KretaClient.clearReauthFlag();
|
||||
initData.reauthCubit?.clear();
|
||||
logger.info('[Home] Secondary iCloud recovery applied a fresher token');
|
||||
} catch (e) {
|
||||
logger.warning('[Home] Secondary iCloud recovery failed: $e');
|
||||
@@ -223,7 +226,7 @@ class _HomeScreenState extends FirkaState<HomeScreen>
|
||||
}
|
||||
|
||||
if (!_disposed &&
|
||||
(LiveActivityService.isTokenExpired || KretaClient.needsReauth)) {
|
||||
(LiveActivityService.isTokenExpired || initData.client.needsReauth)) {
|
||||
activeToast = ActiveToastType.reauth;
|
||||
setState(() {
|
||||
toast = buildReauthToast(context, initData, () {
|
||||
@@ -394,10 +397,6 @@ class _HomeScreenState extends FirkaState<HomeScreen>
|
||||
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
initData.settingsUpdateNotifier.addListener(settingsUpdateListener);
|
||||
initData.profilePictureUpdateNotifier.addListener(_onProfilePictureUpdated);
|
||||
KretaClient.reauthStateNotifier.addListener(_onReauthStateChanged);
|
||||
|
||||
_setupNotificationListener();
|
||||
_setupWidgetDeepLinkListener();
|
||||
|
||||
@@ -412,24 +411,6 @@ class _HomeScreenState extends FirkaState<HomeScreen>
|
||||
}
|
||||
}
|
||||
|
||||
void _onReauthStateChanged() {
|
||||
if (!mounted || _disposed) return;
|
||||
if (!KretaClient.needsReauth && activeToast == ActiveToastType.reauth) {
|
||||
setState(() {
|
||||
activeToast = ActiveToastType.none;
|
||||
toast = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void settingsUpdateListener() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
void _onProfilePictureUpdated() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _preloadImages() async {
|
||||
final imagePaths = initData.settings.appIcons.keys
|
||||
.map((icon) => "assets/images/icons/$icon.webp")
|
||||
@@ -508,12 +489,35 @@ class _HomeScreenState extends FirkaState<HomeScreen>
|
||||
}
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
return Scaffold(
|
||||
backgroundColor: appStyle.colors.background,
|
||||
body: SafeArea(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Stack(children: [widget.child, toast ?? SizedBox.shrink()]),
|
||||
return BlocListener<SettingsCubit, SettingsState>(
|
||||
listener: (context, state) {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
child: BlocListener<ProfilePictureCubit, ProfilePictureState>(
|
||||
listener: (context, state) {
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
child: BlocListener<ReauthCubit, ReauthState>(
|
||||
listener: (context, state) {
|
||||
if (!mounted || _disposed) return;
|
||||
if (!state.needsReauth && activeToast == ActiveToastType.reauth) {
|
||||
setState(() {
|
||||
activeToast = ActiveToastType.none;
|
||||
toast = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: appStyle.colors.background,
|
||||
body: SafeArea(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Stack(
|
||||
children: [widget.child, toast ?? SizedBox.shrink()],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -557,11 +561,6 @@ class _HomeScreenState extends FirkaState<HomeScreen>
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
initData.settingsUpdateNotifier.removeListener(settingsUpdateListener);
|
||||
initData.profilePictureUpdateNotifier.removeListener(
|
||||
_onProfilePictureUpdated,
|
||||
);
|
||||
KretaClient.reauthStateNotifier.removeListener(_onReauthStateChanged);
|
||||
|
||||
_disposed = true;
|
||||
_fetching = false;
|
||||
|
||||
@@ -5,9 +5,11 @@ import 'package:firka/core/firka_bundle.dart';
|
||||
import 'package:firka/app/app_state.dart';
|
||||
import 'package:firka/ui/phone/widgets/login_webview.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'package:firka/core/bloc/theme_cubit.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/core/image_preloader.dart';
|
||||
import 'package:firka/ui/theme/style.dart';
|
||||
@@ -107,7 +109,9 @@ class _LoginScreenState extends FirkaState<LoginScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
final carousel = isLightMode.value ? "carousel" : "carousel_dark";
|
||||
final carousel = context.watch<ThemeCubit>().state.isLightMode
|
||||
? "carousel"
|
||||
: "carousel_dark";
|
||||
|
||||
final paddingWidthHorizontal =
|
||||
MediaQuery.of(context).size.width -
|
||||
|
||||
@@ -24,7 +24,6 @@ import 'package:firka/data/widget.dart';
|
||||
import 'package:firka/core/firka_bundle.dart';
|
||||
import 'package:firka/app/initialization_screen.dart';
|
||||
import 'package:firka/core/state/firka_state.dart';
|
||||
import 'package:firka/api/client/kreta_client.dart';
|
||||
import 'package:firka/core/settings.dart';
|
||||
import 'package:firka/services/live_activity_service.dart';
|
||||
import 'package:firka/services/watch_sync_helper.dart';
|
||||
@@ -984,7 +983,7 @@ class _SettingsScreenState extends FirkaState<SettingsScreen> {
|
||||
'[Settings] Failed to clear iCloud token: $e',
|
||||
);
|
||||
}
|
||||
KretaClient.clearReauthFlag();
|
||||
initData.client.clearReauthFlag();
|
||||
}
|
||||
if (!mounted) return;
|
||||
context.go('/login');
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:isar_community/isar.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
import 'package:firka/api/client/kreta_client.dart';
|
||||
import 'package:firka/services/watch_sync_helper.dart';
|
||||
import 'package:firka/api/consts.dart';
|
||||
import 'package:firka/api/token_grant.dart';
|
||||
@@ -148,7 +147,7 @@ class _LoginWebviewWidgetState extends FirkaState<LoginWebviewWidget>
|
||||
|
||||
if (!mounted) return NavigationDecision.prevent;
|
||||
|
||||
KretaClient.clearReauthFlag();
|
||||
widget.data.reauthCubit?.clear();
|
||||
if (Platform.isIOS) {
|
||||
LiveActivityService.clearTokenExpiration();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ dependencies:
|
||||
fl_chart: ^1.1.1
|
||||
flutter_native_splash: ^2.4.7
|
||||
go_router: ^17.1.0
|
||||
flutter_bloc: ^9.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user