Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 62 additions & 51 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:threebotlogin/helpers/globals.dart';
import 'package:threebotlogin/screens/app_lifecycle_observer.dart';
import 'package:threebotlogin/screens/splash_screen.dart';
import 'package:threebotlogin/services/shared_preference_service.dart';
import 'package:google_fonts/google_fonts.dart';
Expand Down Expand Up @@ -29,6 +30,11 @@ extension ColorSchemeExtension on ColorScheme {
: const Color.fromARGB(255, 10, 10, 10);
}

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

final lastPausedProvider =
StateProvider<int>((ref) => DateTime.now().millisecondsSinceEpoch);

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
Expand Down Expand Up @@ -83,60 +89,65 @@ class MyApp extends ConsumerWidget {
Theme.of(context).textTheme,
);

return MaterialApp(
theme: ThemeData().copyWith(
colorScheme: kColorScheme,
brightness: Brightness.light,
textTheme: textTheme,
appBarTheme: const AppBarTheme().copyWith(
backgroundColor: kColorScheme.primary,
foregroundColor: kColorScheme.onPrimary,
),
cardTheme: const CardTheme().copyWith(
color: kColorScheme.surfaceContainer,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
backgroundColor: kColorScheme.primaryContainer),
),
expansionTileTheme: const ExpansionTileThemeData().copyWith(
backgroundColor: kColorScheme.backgroundDarker,
collapsedBackgroundColor: ThemeData().colorScheme.surface),
bottomNavigationBarTheme: const BottomNavigationBarThemeData().copyWith(
selectedItemColor: kColorScheme.primary,
unselectedItemColor: kColorScheme.secondary,
),
),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: kDarkColorScheme,
brightness: Brightness.dark,
textTheme: textTheme,
appBarTheme: const AppBarTheme().copyWith(
backgroundColor: kDarkColorScheme.primaryContainer,
foregroundColor: kDarkColorScheme.onPrimaryContainer,
),
cardTheme: const CardTheme().copyWith(
color: kDarkColorScheme.surfaceContainer,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
backgroundColor: kDarkColorScheme.primaryContainer),
return AppLifecycleObserver(
child: MaterialApp(
navigatorKey: navigatorKey,
theme: ThemeData().copyWith(
colorScheme: kColorScheme,
brightness: Brightness.light,
textTheme: textTheme,
appBarTheme: const AppBarTheme().copyWith(
backgroundColor: kColorScheme.primary,
foregroundColor: kColorScheme.onPrimary,
),
cardTheme: const CardTheme().copyWith(
color: kColorScheme.surfaceContainer,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
backgroundColor: kColorScheme.primaryContainer),
),
expansionTileTheme: const ExpansionTileThemeData().copyWith(
backgroundColor: kColorScheme.backgroundDarker,
collapsedBackgroundColor: ThemeData().colorScheme.surface),
bottomNavigationBarTheme:
const BottomNavigationBarThemeData().copyWith(
selectedItemColor: kColorScheme.primary,
unselectedItemColor: kColorScheme.secondary,
),
),
expansionTileTheme: const ExpansionTileThemeData().copyWith(
backgroundColor: kDarkColorScheme.backgroundDarker,
collapsedBackgroundColor: kDarkColorScheme.surface),
bottomNavigationBarTheme: const BottomNavigationBarThemeData().copyWith(
selectedItemColor: kDarkColorScheme.primary,
unselectedItemColor: kDarkColorScheme.secondary,
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: kDarkColorScheme,
brightness: Brightness.dark,
textTheme: textTheme,
appBarTheme: const AppBarTheme().copyWith(
backgroundColor: kDarkColorScheme.primaryContainer,
foregroundColor: kDarkColorScheme.onPrimaryContainer,
),
cardTheme: const CardTheme().copyWith(
color: kDarkColorScheme.surfaceContainer,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
backgroundColor: kDarkColorScheme.primaryContainer),
),
expansionTileTheme: const ExpansionTileThemeData().copyWith(
backgroundColor: kDarkColorScheme.backgroundDarker,
collapsedBackgroundColor: kDarkColorScheme.surface),
bottomNavigationBarTheme:
const BottomNavigationBarThemeData().copyWith(
selectedItemColor: kDarkColorScheme.primary,
unselectedItemColor: kDarkColorScheme.secondary,
),
),
themeMode: themeMode,
home: SplashScreen(initDone: initDone, registered: registered),
),
themeMode: themeMode,
home: SplashScreen(initDone: initDone, registered: registered),
);
}
}
67 changes: 67 additions & 0 deletions app/lib/screens/app_lifecycle_observer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:threebotlogin/helpers/logger.dart';
import 'package:threebotlogin/main.dart';
import 'package:threebotlogin/screens/home_screen.dart';

class AppLifecycleObserver extends ConsumerStatefulWidget {
final Widget child;
const AppLifecycleObserver({super.key, required this.child});

@override
ConsumerState<AppLifecycleObserver> createState() =>
_AppLifecycleObserverState();
}

class _AppLifecycleObserverState extends ConsumerState<AppLifecycleObserver>
with WidgetsBindingObserver {
final int pinCheckTimeout = 60000 * 5; // 5 minute

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
Future.microtask(() {
ref.read(lastPausedProvider.notifier).state =
DateTime.now().millisecondsSinceEpoch;
});
logger.i('AppLifecycleObserver initialized.');
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
logger.i('AppLifecycleObserver disposed.');
super.dispose();
}

@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
logger.i('AppLifecycleState changed: $state');

final now = DateTime.now().millisecondsSinceEpoch;
final lastPaused = ref.read(lastPausedProvider);

if (state == AppLifecycleState.paused) {
ref.read(lastPausedProvider.notifier).state = now;
logger.i('App Paused. Last paused time updated: $now');
} else if (state == AppLifecycleState.resumed) {
if (now - lastPaused >= pinCheckTimeout) {
logger.i('Timeout expired. Requiring authentication.');
// Navigate to Home Screen
WidgetsBinding.instance.addPostFrameCallback((_) {
navigatorKey.currentState?.push(
MaterialPageRoute(builder: (context) => const HomeScreen()),
);
});
ref.read(lastPausedProvider.notifier).state = now;
}
}
}

@override
Widget build(BuildContext context) {
return widget.child;
}
}
56 changes: 14 additions & 42 deletions app/lib/screens/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:threebotlogin/events/go_wallet_event.dart';
import 'package:threebotlogin/events/new_login_event.dart';
import 'package:threebotlogin/events/uni_link_event.dart';
import 'package:threebotlogin/helpers/globals.dart';
import 'package:threebotlogin/main.dart';
import 'package:threebotlogin/providers/wallets_provider.dart';
import 'package:threebotlogin/screens/authentication_screen.dart';
import 'package:threebotlogin/services/socket_service.dart';
Expand All @@ -25,26 +26,30 @@ import 'package:threebotlogin/widgets/email_verification_needed.dart';
import 'package:uni_links/uni_links.dart';

/* Screen shows tab bar and all pages defined in router.dart */
class HomeScreen extends StatefulWidget {
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({super.key, this.initialLink, this.backendConnection});
final String? initialLink;
final BackendConnection? backendConnection;

@override
State<HomeScreen> createState() => _HomeScreenState();
ConsumerState<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen>
with WidgetsBindingObserver, SingleTickerProviderStateMixin {
class _HomeScreenState extends ConsumerState<HomeScreen>
with SingleTickerProviderStateMixin {
Globals globals = Globals();
StreamSubscription? _sub;
String? initialLink;
bool timeoutExpiredInBackground = true;
bool pinCheckOpen = false;
int lastCheck = 0;
final int pinCheckTimeout = 60000 * 5;

_HomeScreenState();
@override
void dispose() {
_sub?.cancel();
globals.tabController.removeListener(_handleTabSelection);
globals.tabController.dispose();
super.dispose();
}

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

if (authenticated != null && authenticated) {
lastCheck = DateTime.now().millisecondsSinceEpoch;
ref.read(lastPausedProvider.notifier).state =
DateTime.now().millisecondsSinceEpoch;
timeoutExpiredInBackground = false;
globals.tabController.animateTo(indexIfAuthIsSuccess);
}
Expand Down Expand Up @@ -175,40 +181,6 @@ class _HomeScreenState extends State<HomeScreen>
Events().onEvent(PhoneEvent().runtimeType, (PhoneEvent event) {
phoneVerification(context);
});

WidgetsBinding.instance.addObserver(this);
}

@override
void dispose() {
_sub?.cancel();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
if (pinCheckOpen) {
return;
}

int timeSpendWithPausedApp =
DateTime.now().millisecondsSinceEpoch - lastCheck;

if (timeSpendWithPausedApp >= pinCheckTimeout) {
timeoutExpiredInBackground = true;
}

if (Globals().router.pinRequired(globals.tabController.index) &&
timeoutExpiredInBackground) {
int homeTab = 0;
globals.tabController.animateTo(homeTab);
}
} else if (state == AppLifecycleState.inactive) {
} else if (state == AppLifecycleState.paused) {
lastCheck = DateTime.now().millisecondsSinceEpoch;
}
}

Future<void> initUniLinks() async {
Expand Down
12 changes: 11 additions & 1 deletion app/lib/widgets/add_farm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class _NewFarmState extends State<NewFarm> {
nameError = "Name can't be empty";
return false;
}
if (farmName.contains(' ')) {
nameError = "Name can't contain spaces";
return false;
}

if (widget.isV4) {
if (!isAlphanumeric(farmName)) {
Expand All @@ -108,6 +112,12 @@ class _NewFarmState extends State<NewFarm> {
});
return false;
}
if (_selectedWallet!.stellarBalance == '-1') {
setState(() {
walletError = 'Wallet not activated on stellar';
});
return false;
}
return true;
}

Expand Down Expand Up @@ -160,7 +170,7 @@ class _NewFarmState extends State<NewFarm> {
}

Future<void> _validateAndAdd() async {
if(!_validateWallet()) return;
if (!_validateWallet()) return;
final farmName = _nameController.text.trim();
setState(() {
saveLoading = true;
Expand Down