import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; import 'matrix/notifications.dart'; import 'screens/home_screen.dart'; import 'screens/login_screen.dart'; import 'screens/room_screen.dart'; import 'state.dart'; import 'theme.dart'; import 'widgets/uia.dart'; import 'widgets/verification_sheet.dart'; final navigatorKey = GlobalKey(); class WarpApp extends ConsumerWidget { const WarpApp({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final loginState = ref.watch(loginStateProvider); final signedIn = loginState.value == LoginState.loggedIn; // Signing in from the sign-up screen, or out from settings, leaves those // screens on top of the new home: close them. ref.listen(loginStateProvider, (prev, next) { if (prev?.value != next.value) { navigatorKey.currentState?.popUntil((route) => route.isFirst); } }); return MaterialApp( title: 'Warp', navigatorKey: navigatorKey, debugShowCheckedModeBanner: false, theme: warpTheme(Brightness.light), darkTheme: warpTheme(Brightness.dark), themeMode: ref.watch(themeModeProvider), home: loginState.isLoading ? const Scaffold() : signedIn ? const _SignedIn(key: ValueKey('signed-in')) : const LoginScreen(key: ValueKey('signed-out')), ); } } /// Everything that runs only while signed in: the notification listener, /// prompts the SDK raises (a password for an account change, an incoming /// verification request) and opening rooms from notifications. class _SignedIn extends ConsumerStatefulWidget { const _SignedIn({super.key}); @override ConsumerState<_SignedIn> createState() => _SignedInState(); } class _SignedInState extends ConsumerState<_SignedIn> with WidgetsBindingObserver { final _subs = []; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); final client = ref.read(clientProvider); _subs ..add(Notifications.watch(client)) ..add(Notifications.opened.listen(_openRoom)) ..add(client.onUiaRequest.stream.listen(_onUia)) ..add(client.onKeyVerificationRequest.stream.listen(_onVerification)); Notifications.launchRoomId().then((roomId) { if (roomId != null) _openRoom(roomId); }); Notifications.requestPermission(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); for (final sub in _subs) { sub.cancel(); } super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { // The sync loop keeps running in the background for as long as Android // lets the process live; pushes cover the time after that. Notifications.foreground = state == AppLifecycleState.resumed; } void _openRoom(String roomId) { final nav = navigatorKey.currentState; if (nav == null) return; nav.popUntil((route) => route.isFirst); nav.push(RoomScreen.route(roomId)); } void _onUia(UiaRequest request) { final context = navigatorKey.currentContext; if (context == null) return request.cancel(); handleUia(context, ref.read(clientProvider), request); } void _onVerification(KeyVerification request) { final context = navigatorKey.currentContext; if (context == null) return; showVerificationSheet(context, request); } @override Widget build(BuildContext context) => const HomeScreen(); }