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:
2026-02-28 10:25:13 +01:00
parent 4abf995fde
commit 299a769f74
32 changed files with 403 additions and 374 deletions

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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(),
);
},
);
},
),
);
}

View 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));
}
}

View 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));
}
}

View 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));
}
}

View 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));
}
}

View 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));
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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> {}

View File

@@ -1,7 +0,0 @@
import 'package:flutter/material.dart';
class UpdateNotifier with ChangeNotifier {
void update() {
notifyListeners();
}
}

View File

@@ -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();

View File

@@ -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),
);
},
),

View File

@@ -290,7 +290,7 @@ class LiveActivityService {
);
}
globalUpdate.update();
initData.themeCubit?.refresh();
} catch (e) {
_logger.warning('Error syncing global settings: $e');
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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++;
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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 &&

View File

@@ -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;

View File

@@ -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 -

View File

@@ -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');

View File

@@ -1,5 +1,3 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

View File

@@ -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();
}

View File

@@ -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: