Skip to content

Commit 2b6216a

Browse files
authored
Fix auth timeout routing (#1000)
* Creating app lifecycle observer and use it in main.dart w navigator key, Replace Navigator routes w navigator key * Remove unused code * Revert navigator key changes from screens * Revert main screen changes
1 parent a7fe9fb commit 2b6216a

3 files changed

Lines changed: 143 additions & 93 deletions

File tree

app/lib/main.dart

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:flutter/services.dart';
33
import 'package:flutter_riverpod/flutter_riverpod.dart';
44
import 'package:threebotlogin/helpers/globals.dart';
5+
import 'package:threebotlogin/screens/app_lifecycle_observer.dart';
56
import 'package:threebotlogin/screens/splash_screen.dart';
67
import 'package:threebotlogin/services/shared_preference_service.dart';
78
import 'package:google_fonts/google_fonts.dart';
@@ -29,6 +30,11 @@ extension ColorSchemeExtension on ColorScheme {
2930
: const Color.fromARGB(255, 10, 10, 10);
3031
}
3132

33+
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
34+
35+
final lastPausedProvider =
36+
StateProvider<int>((ref) => DateTime.now().millisecondsSinceEpoch);
37+
3238
Future<void> main() async {
3339
WidgetsFlutterBinding.ensureInitialized();
3440
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
@@ -83,60 +89,65 @@ class MyApp extends ConsumerWidget {
8389
Theme.of(context).textTheme,
8490
);
8591

86-
return MaterialApp(
87-
theme: ThemeData().copyWith(
88-
colorScheme: kColorScheme,
89-
brightness: Brightness.light,
90-
textTheme: textTheme,
91-
appBarTheme: const AppBarTheme().copyWith(
92-
backgroundColor: kColorScheme.primary,
93-
foregroundColor: kColorScheme.onPrimary,
94-
),
95-
cardTheme: const CardTheme().copyWith(
96-
color: kColorScheme.surfaceContainer,
97-
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
98-
elevatedButtonTheme: ElevatedButtonThemeData(
99-
style: ElevatedButton.styleFrom(
100-
shape: RoundedRectangleBorder(
101-
borderRadius: BorderRadius.circular(5)),
102-
backgroundColor: kColorScheme.primaryContainer),
103-
),
104-
expansionTileTheme: const ExpansionTileThemeData().copyWith(
105-
backgroundColor: kColorScheme.backgroundDarker,
106-
collapsedBackgroundColor: ThemeData().colorScheme.surface),
107-
bottomNavigationBarTheme: const BottomNavigationBarThemeData().copyWith(
108-
selectedItemColor: kColorScheme.primary,
109-
unselectedItemColor: kColorScheme.secondary,
110-
),
111-
),
112-
darkTheme: ThemeData(
113-
useMaterial3: true,
114-
colorScheme: kDarkColorScheme,
115-
brightness: Brightness.dark,
116-
textTheme: textTheme,
117-
appBarTheme: const AppBarTheme().copyWith(
118-
backgroundColor: kDarkColorScheme.primaryContainer,
119-
foregroundColor: kDarkColorScheme.onPrimaryContainer,
120-
),
121-
cardTheme: const CardTheme().copyWith(
122-
color: kDarkColorScheme.surfaceContainer,
123-
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
124-
elevatedButtonTheme: ElevatedButtonThemeData(
125-
style: ElevatedButton.styleFrom(
126-
shape: RoundedRectangleBorder(
127-
borderRadius: BorderRadius.circular(5)),
128-
backgroundColor: kDarkColorScheme.primaryContainer),
92+
return AppLifecycleObserver(
93+
child: MaterialApp(
94+
navigatorKey: navigatorKey,
95+
theme: ThemeData().copyWith(
96+
colorScheme: kColorScheme,
97+
brightness: Brightness.light,
98+
textTheme: textTheme,
99+
appBarTheme: const AppBarTheme().copyWith(
100+
backgroundColor: kColorScheme.primary,
101+
foregroundColor: kColorScheme.onPrimary,
102+
),
103+
cardTheme: const CardTheme().copyWith(
104+
color: kColorScheme.surfaceContainer,
105+
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
106+
elevatedButtonTheme: ElevatedButtonThemeData(
107+
style: ElevatedButton.styleFrom(
108+
shape: RoundedRectangleBorder(
109+
borderRadius: BorderRadius.circular(5)),
110+
backgroundColor: kColorScheme.primaryContainer),
111+
),
112+
expansionTileTheme: const ExpansionTileThemeData().copyWith(
113+
backgroundColor: kColorScheme.backgroundDarker,
114+
collapsedBackgroundColor: ThemeData().colorScheme.surface),
115+
bottomNavigationBarTheme:
116+
const BottomNavigationBarThemeData().copyWith(
117+
selectedItemColor: kColorScheme.primary,
118+
unselectedItemColor: kColorScheme.secondary,
119+
),
129120
),
130-
expansionTileTheme: const ExpansionTileThemeData().copyWith(
131-
backgroundColor: kDarkColorScheme.backgroundDarker,
132-
collapsedBackgroundColor: kDarkColorScheme.surface),
133-
bottomNavigationBarTheme: const BottomNavigationBarThemeData().copyWith(
134-
selectedItemColor: kDarkColorScheme.primary,
135-
unselectedItemColor: kDarkColorScheme.secondary,
121+
darkTheme: ThemeData(
122+
useMaterial3: true,
123+
colorScheme: kDarkColorScheme,
124+
brightness: Brightness.dark,
125+
textTheme: textTheme,
126+
appBarTheme: const AppBarTheme().copyWith(
127+
backgroundColor: kDarkColorScheme.primaryContainer,
128+
foregroundColor: kDarkColorScheme.onPrimaryContainer,
129+
),
130+
cardTheme: const CardTheme().copyWith(
131+
color: kDarkColorScheme.surfaceContainer,
132+
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
133+
elevatedButtonTheme: ElevatedButtonThemeData(
134+
style: ElevatedButton.styleFrom(
135+
shape: RoundedRectangleBorder(
136+
borderRadius: BorderRadius.circular(5)),
137+
backgroundColor: kDarkColorScheme.primaryContainer),
138+
),
139+
expansionTileTheme: const ExpansionTileThemeData().copyWith(
140+
backgroundColor: kDarkColorScheme.backgroundDarker,
141+
collapsedBackgroundColor: kDarkColorScheme.surface),
142+
bottomNavigationBarTheme:
143+
const BottomNavigationBarThemeData().copyWith(
144+
selectedItemColor: kDarkColorScheme.primary,
145+
unselectedItemColor: kDarkColorScheme.secondary,
146+
),
136147
),
148+
themeMode: themeMode,
149+
home: SplashScreen(initDone: initDone, registered: registered),
137150
),
138-
themeMode: themeMode,
139-
home: SplashScreen(initDone: initDone, registered: registered),
140151
);
141152
}
142153
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:threebotlogin/helpers/logger.dart';
4+
import 'package:threebotlogin/main.dart';
5+
import 'package:threebotlogin/screens/home_screen.dart';
6+
7+
class AppLifecycleObserver extends ConsumerStatefulWidget {
8+
final Widget child;
9+
const AppLifecycleObserver({super.key, required this.child});
10+
11+
@override
12+
ConsumerState<AppLifecycleObserver> createState() =>
13+
_AppLifecycleObserverState();
14+
}
15+
16+
class _AppLifecycleObserverState extends ConsumerState<AppLifecycleObserver>
17+
with WidgetsBindingObserver {
18+
final int pinCheckTimeout = 60000 * 5; // 5 minute
19+
20+
@override
21+
void initState() {
22+
super.initState();
23+
WidgetsBinding.instance.addObserver(this);
24+
Future.microtask(() {
25+
ref.read(lastPausedProvider.notifier).state =
26+
DateTime.now().millisecondsSinceEpoch;
27+
});
28+
logger.i('AppLifecycleObserver initialized.');
29+
}
30+
31+
@override
32+
void dispose() {
33+
WidgetsBinding.instance.removeObserver(this);
34+
logger.i('AppLifecycleObserver disposed.');
35+
super.dispose();
36+
}
37+
38+
@override
39+
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
40+
super.didChangeAppLifecycleState(state);
41+
logger.i('AppLifecycleState changed: $state');
42+
43+
final now = DateTime.now().millisecondsSinceEpoch;
44+
final lastPaused = ref.read(lastPausedProvider);
45+
46+
if (state == AppLifecycleState.paused) {
47+
ref.read(lastPausedProvider.notifier).state = now;
48+
logger.i('App Paused. Last paused time updated: $now');
49+
} else if (state == AppLifecycleState.resumed) {
50+
if (now - lastPaused >= pinCheckTimeout) {
51+
logger.i('Timeout expired. Requiring authentication.');
52+
// Navigate to Home Screen
53+
WidgetsBinding.instance.addPostFrameCallback((_) {
54+
navigatorKey.currentState?.push(
55+
MaterialPageRoute(builder: (context) => const HomeScreen()),
56+
);
57+
});
58+
ref.read(lastPausedProvider.notifier).state = now;
59+
}
60+
}
61+
}
62+
63+
@override
64+
Widget build(BuildContext context) {
65+
return widget.child;
66+
}
67+
}

app/lib/screens/home_screen.dart

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'package:threebotlogin/events/go_wallet_event.dart';
1616
import 'package:threebotlogin/events/new_login_event.dart';
1717
import 'package:threebotlogin/events/uni_link_event.dart';
1818
import 'package:threebotlogin/helpers/globals.dart';
19+
import 'package:threebotlogin/main.dart';
1920
import 'package:threebotlogin/providers/wallets_provider.dart';
2021
import 'package:threebotlogin/screens/authentication_screen.dart';
2122
import 'package:threebotlogin/services/socket_service.dart';
@@ -25,26 +26,30 @@ import 'package:threebotlogin/widgets/email_verification_needed.dart';
2526
import 'package:uni_links/uni_links.dart';
2627

2728
/* Screen shows tab bar and all pages defined in router.dart */
28-
class HomeScreen extends StatefulWidget {
29+
class HomeScreen extends ConsumerStatefulWidget {
2930
const HomeScreen({super.key, this.initialLink, this.backendConnection});
3031
final String? initialLink;
3132
final BackendConnection? backendConnection;
3233

3334
@override
34-
State<HomeScreen> createState() => _HomeScreenState();
35+
ConsumerState<HomeScreen> createState() => _HomeScreenState();
3536
}
3637

37-
class _HomeScreenState extends State<HomeScreen>
38-
with WidgetsBindingObserver, SingleTickerProviderStateMixin {
38+
class _HomeScreenState extends ConsumerState<HomeScreen>
39+
with SingleTickerProviderStateMixin {
3940
Globals globals = Globals();
4041
StreamSubscription? _sub;
4142
String? initialLink;
4243
bool timeoutExpiredInBackground = true;
4344
bool pinCheckOpen = false;
44-
int lastCheck = 0;
45-
final int pinCheckTimeout = 60000 * 5;
4645

47-
_HomeScreenState();
46+
@override
47+
void dispose() {
48+
_sub?.cancel();
49+
globals.tabController.removeListener(_handleTabSelection);
50+
globals.tabController.dispose();
51+
super.dispose();
52+
}
4853

4954
void checkPinAndNavigateIfSuccess(int indexIfAuthIsSuccess) async {
5055
String? pin = await getPin();
@@ -63,7 +68,8 @@ class _HomeScreenState extends State<HomeScreen>
6368
pinCheckOpen = false;
6469

6570
if (authenticated != null && authenticated) {
66-
lastCheck = DateTime.now().millisecondsSinceEpoch;
71+
ref.read(lastPausedProvider.notifier).state =
72+
DateTime.now().millisecondsSinceEpoch;
6773
timeoutExpiredInBackground = false;
6874
globals.tabController.animateTo(indexIfAuthIsSuccess);
6975
}
@@ -175,40 +181,6 @@ class _HomeScreenState extends State<HomeScreen>
175181
Events().onEvent(PhoneEvent().runtimeType, (PhoneEvent event) {
176182
phoneVerification(context);
177183
});
178-
179-
WidgetsBinding.instance.addObserver(this);
180-
}
181-
182-
@override
183-
void dispose() {
184-
_sub?.cancel();
185-
WidgetsBinding.instance.removeObserver(this);
186-
super.dispose();
187-
}
188-
189-
@override
190-
void didChangeAppLifecycleState(AppLifecycleState state) {
191-
if (state == AppLifecycleState.resumed) {
192-
if (pinCheckOpen) {
193-
return;
194-
}
195-
196-
int timeSpendWithPausedApp =
197-
DateTime.now().millisecondsSinceEpoch - lastCheck;
198-
199-
if (timeSpendWithPausedApp >= pinCheckTimeout) {
200-
timeoutExpiredInBackground = true;
201-
}
202-
203-
if (Globals().router.pinRequired(globals.tabController.index) &&
204-
timeoutExpiredInBackground) {
205-
int homeTab = 0;
206-
globals.tabController.animateTo(homeTab);
207-
}
208-
} else if (state == AppLifecycleState.inactive) {
209-
} else if (state == AppLifecycleState.paused) {
210-
lastCheck = DateTime.now().millisecondsSinceEpoch;
211-
}
212184
}
213185

214186
Future<void> initUniLinks() async {

0 commit comments

Comments
 (0)