diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index 479b2f0..571e9c0 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -2,6 +2,7 @@ import "dart:ffi"; import "dart:isolate"; import "package:ffi/ffi.dart"; import "package:flutter/foundation.dart"; +import "package:nexus/controllers/client_state_controller.dart"; import "package:nexus/controllers/sync_status_controller.dart"; import "package:nexus/helpers/extensions/gomuks_buffer.dart"; import "package:nexus/models/client_state.dart"; @@ -31,18 +32,22 @@ class ClientController extends AsyncNotifier { switch (muksEventType) { case "client_state": - final clientState = ClientState.fromJson(decodedMuksEvent); - debugPrint("Received event: $clientState"); + ref + .watch(ClientStateController.provider.notifier) + .set(ClientState.fromJson(decodedMuksEvent)); break; case "sync_status": ref .watch(SyncStatusController.provider.notifier) .set(SyncStatus.fromJson(decodedMuksEvent)); break; + case "sync_complete": + // ref + // .watch(SyncStatusController.provider.notifier) + // .set(SyncStatus.fromJson(decodedMuksEvent)); + break; default: - debugPrint( - "Unhandled event: $muksEventType: $decodedMuksEvent", - ); + debugPrint("Unhandled event: $muksEventType"); } } catch (error, stackTrace) { debugPrintStack(stackTrace: stackTrace, label: error.toString()); @@ -73,6 +78,15 @@ class ClientController extends AsyncNotifier { return response.buf.toJson(); } + Future verify(String recoveryKey) async { + try { + await sendCommand("verify", {"recovery_key": recoveryKey}); + return true; + } catch (_) { + return false; + } + } + Future login(Login login) async { try { await sendCommand("login", login.toJson()); diff --git a/lib/controllers/client_state_controller.dart b/lib/controllers/client_state_controller.dart new file mode 100644 index 0000000..998d4a1 --- /dev/null +++ b/lib/controllers/client_state_controller.dart @@ -0,0 +1,15 @@ +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/models/client_state.dart"; + +class ClientStateController extends Notifier { + @override + Null build() => null; + + void set(ClientState newState) { + state = newState; + } + + static final provider = NotifierProvider( + ClientStateController.new, + ); +} diff --git a/lib/controllers/multi_provider_controller.dart b/lib/controllers/multi_provider_controller.dart new file mode 100644 index 0000000..e23ecaa --- /dev/null +++ b/lib/controllers/multi_provider_controller.dart @@ -0,0 +1,20 @@ +import "dart:async"; +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; + +class MultiProviderController extends AsyncNotifier { + MultiProviderController(this.providers); + final IList providers; + + @override + FutureOr build() async => await Future.wait( + providers.map((provider) => ref.watch(provider.future)), + ); + + static final provider = + AsyncNotifierProvider.family< + MultiProviderController, + void, + IList + >(MultiProviderController.new); +} diff --git a/lib/main.dart b/lib/main.dart index 44757fd..a84f3f7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,18 @@ import "dart:io"; +import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/foundation.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; +import "package:nexus/controllers/client_state_controller.dart"; +import "package:nexus/controllers/multi_provider_controller.dart"; import "package:nexus/controllers/shared_prefs_controller.dart"; -import "package:nexus/controllers/sync_status_controller.dart"; import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/scheme_to_theme.dart"; -import "package:nexus/models/sync_status.dart"; +import "package:nexus/pages/chat_page.dart"; import "package:nexus/pages/login_page.dart"; +import "package:nexus/pages/verify_page.dart"; import "package:nexus/widgets/error_dialog.dart"; +import "package:nexus/widgets/loading.dart"; import "package:window_manager/window_manager.dart"; import "package:flutter/material.dart"; import "package:dynamic_system_colors/dynamic_system_colors.dart"; @@ -78,11 +82,11 @@ void main() async { ); } -class App extends ConsumerWidget { +class App extends StatelessWidget { const App({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) => DynamicColorBuilder( + Widget build(BuildContext context) => DynamicColorBuilder( builder: (lightDynamic, darkDynamic) => MaterialApp( navigatorKey: navigatorKey, debugShowCheckedModeBanner: false, @@ -96,10 +100,38 @@ class App extends ConsumerWidget { brightness: Brightness.dark, )) .theme, - home: Builder( - builder: (context) => ref - .watch(SharedPrefsController.provider) - .betterWhen(data: (_) => LoginPage()), + home: Scaffold( + body: Consumer( + builder: (_, ref, _) => ref + .watch( + MultiProviderController.provider( + IListConst([ + SharedPrefsController.provider, + ClientController.provider, + ]), + ), + ) + .betterWhen( + data: (_) => Consumer( + builder: (_, ref, _) { + final clientState = ref.watch( + ClientStateController.provider, + ); + if (clientState == null || !clientState.isInitialized) { + return Loading(); + } + + if (!clientState.isLoggedIn) { + return LoginPage(); + } else if (!clientState.isVerified) { + return VerifyPage(); + } else { + return ChatPage(); + } + }, + ), + ), + ), ), ), ); diff --git a/lib/pages/chat_page.dart b/lib/pages/chat_page.dart index c60cc45..23f6a09 100644 --- a/lib/pages/chat_page.dart +++ b/lib/pages/chat_page.dart @@ -1,6 +1,6 @@ import "package:flutter/material.dart"; -import "package:nexus/widgets/chat_page/room_chat.dart"; -import "package:nexus/widgets/chat_page/sidebar.dart"; +// import "package:nexus/widgets/chat_page/room_chat.dart"; +// import "package:nexus/widgets/chat_page/sidebar.dart"; class ChatPage extends StatelessWidget { const ChatPage({super.key}); @@ -10,23 +10,23 @@ class ChatPage extends StatelessWidget { builder: (context, constraints) { final isDesktop = constraints.maxWidth > 650; final showMembersByDefault = constraints.maxWidth > 1000; - - return Scaffold( - body: Builder( - builder: (context) => Row( - children: [ - if (isDesktop) Sidebar(), - Expanded( - child: RoomChat( - isDesktop: isDesktop, - showMembersByDefault: showMembersByDefault, - ), - ), - ], - ), - ), - drawer: isDesktop ? null : Sidebar(), - ); + return Placeholder(); + // return Scaffold( + // body: Builder( + // builder: (context) => Row( + // children: [ + // if (isDesktop) Sidebar(), + // Expanded( + // child: RoomChat( + // isDesktop: isDesktop, + // showMembersByDefault: showMembersByDefault, + // ), + // ), + // ], + // ), + // ), + // drawer: isDesktop ? null : Sidebar(), + // ); }, ); } diff --git a/lib/pages/verify_page.dart b/lib/pages/verify_page.dart new file mode 100644 index 0000000..bc33151 --- /dev/null +++ b/lib/pages/verify_page.dart @@ -0,0 +1,46 @@ +import "package:flutter/material.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/client_controller.dart"; +import "package:nexus/widgets/form_text_input.dart"; + +class VerifyPage extends HookConsumerWidget { + const VerifyPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final passphraseController = useTextEditingController(); + return AlertDialog( + title: Text("Verify"), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Enter your recovery key or passphrase below to unlock encrypted messages.\nYour passphrase is usually not the same as your password.", + ), + SizedBox(height: 12), + FormTextInput( + required: false, + autofocus: true, + capitalize: true, + controller: passphraseController, + obscure: true, + title: "Recovery Key or Passphrase", + ), + ], + ), + actions: [ + TextButton( + onPressed: () async { + ref + .watch(ClientController.provider.notifier) + .verify(passphraseController.text); + Navigator.of(context).pop(); + }, + child: Text("Verify"), + ), + ], + ); + } +} diff --git a/lib/widgets/form_text_input.dart b/lib/widgets/form_text_input.dart index 492439b..21b2e5c 100644 --- a/lib/widgets/form_text_input.dart +++ b/lib/widgets/form_text_input.dart @@ -20,11 +20,13 @@ class FormTextInput extends StatelessWidget { final Widget? trailing; final InputBorder? border; final List? formatters; + final bool autofocus; const FormTextInput({ super.key, this.border, this.controller, + this.autofocus = false, this.title, this.obscure = false, this.readOnly = false, @@ -45,6 +47,7 @@ class FormTextInput extends StatelessWidget { @override Widget build(BuildContext context) => TextFormField( + autofocus: autofocus, controller: controller, keyboardType: keyboardType, readOnly: readOnly,