From 47a758a33b2138bdb35b7a11c4a1ec7f6f1f445f Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Sun, 25 Jan 2026 13:02:40 +0000 Subject: [PATCH] working login page --- lib/controllers/client_controller.dart | 102 +++++++++------ lib/controllers/sync_status_controller.dart | 13 ++ lib/main.dart | 48 +------ lib/models/homeserver.dart | 12 ++ lib/models/login.dart | 14 ++ lib/models/sync_status.dart | 4 +- lib/pages/login_page.dart | 39 +++--- linux/flutter/generated_plugins.cmake | 1 - pubspec.lock | 136 -------------------- pubspec.yaml | 4 - scripts/generate.dart | 4 +- windows/flutter/generated_plugins.cmake | 1 - 12 files changed, 133 insertions(+), 245 deletions(-) create mode 100644 lib/controllers/sync_status_controller.dart create mode 100644 lib/models/homeserver.dart create mode 100644 lib/models/login.dart diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index ec10638..479b2f0 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -1,61 +1,71 @@ import "dart:ffi"; +import "dart:isolate"; import "package:ffi/ffi.dart"; import "package:flutter/foundation.dart"; +import "package:nexus/controllers/sync_status_controller.dart"; import "package:nexus/helpers/extensions/gomuks_buffer.dart"; import "package:nexus/models/client_state.dart"; +import "package:nexus/models/login.dart"; import "package:nexus/models/sync_status.dart"; import "package:nexus/src/third_party/gomuks.g.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -void gomuksCallback( - Pointer command, - int requestId, - GomuksBorrowedBuffer data, -) { - try { - final muksEventType = command.cast().toDartString(); - final Map decodedMuksEvent = data.toJson(); - - switch (muksEventType) { - case "client_state": - final clientState = ClientState.fromJson(decodedMuksEvent); - debugPrint("Received event: $clientState"); - break; - case "sync_status": - final syncStatus = SyncStatus.fromJson(decodedMuksEvent); - debugPrint("Received event: $syncStatus"); - break; - default: - debugPrint("Unhandled event: $muksEventType: $decodedMuksEvent"); - } - } catch (error, stackTrace) { - debugPrintStack(stackTrace: stackTrace, label: error.toString()); - } -} - -class ClientController extends Notifier { +class ClientController extends AsyncNotifier { @override - int build() { - final handle = GomuksInit(); + Future build() async { + final handle = await Isolate.run(GomuksInit); ref.onDispose(() => GomuksDestroy(handle)); GomuksStart( handle, NativeCallable< Void Function(Pointer, Int64, GomuksBorrowedBuffer) - >.listener(gomuksCallback) + >.listener(( + Pointer command, + int requestId, + GomuksBorrowedBuffer data, + ) { + try { + final muksEventType = command.cast().toDartString(); + final Map decodedMuksEvent = data.toJson(); + + switch (muksEventType) { + case "client_state": + final clientState = ClientState.fromJson(decodedMuksEvent); + debugPrint("Received event: $clientState"); + break; + case "sync_status": + ref + .watch(SyncStatusController.provider.notifier) + .set(SyncStatus.fromJson(decodedMuksEvent)); + break; + default: + debugPrint( + "Unhandled event: $muksEventType: $decodedMuksEvent", + ); + } + } catch (error, stackTrace) { + debugPrintStack(stackTrace: stackTrace, label: error.toString()); + } + }) .nativeFunction, ); return handle; } - Map sendCommand(String command, Map data) { + Future> sendCommand( + String command, + Map data, + ) async { final bufferPointer = data.toGomuksBufferPtr(); - final response = GomuksSubmitCommand( - state, - command.toNativeUtf8().cast(), - bufferPointer.ref, + final handle = await future; + final response = await Isolate.run( + () => GomuksSubmitCommand( + handle, + command.toNativeUtf8().cast(), + bufferPointer.ref, + ), ); calloc.free(bufferPointer); @@ -63,7 +73,27 @@ class ClientController extends Notifier { return response.buf.toJson(); } - static final provider = NotifierProvider( + Future login(Login login) async { + try { + await sendCommand("login", login.toJson()); + return true; + } catch (_) { + return false; + } + } + + Future discoverHomeserver(Uri homeserver) async { + try { + final response = await sendCommand("discover_homeserver", { + "user_id": "@fakeuser:${homeserver.host}", + }); + return (response["m.homeserver"] as Map)["base_url"]; + } catch (_) { + return null; + } + } + + static final provider = AsyncNotifierProvider( ClientController.new, ); } diff --git a/lib/controllers/sync_status_controller.dart b/lib/controllers/sync_status_controller.dart new file mode 100644 index 0000000..fe65732 --- /dev/null +++ b/lib/controllers/sync_status_controller.dart @@ -0,0 +1,13 @@ +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/models/sync_status.dart"; + +class SyncStatusController extends Notifier { + @override + Null build() => null; + + void set(SyncStatus newStatus) => state = newStatus; + + static final provider = NotifierProvider( + SyncStatusController.new, + ); +} diff --git a/lib/main.dart b/lib/main.dart index eaccbfb..44757fd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,8 +3,11 @@ import "package:flutter/foundation.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_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/login_page.dart"; import "package:nexus/widgets/error_dialog.dart"; import "package:window_manager/window_manager.dart"; import "package:flutter/material.dart"; @@ -96,50 +99,7 @@ class App extends ConsumerWidget { home: Builder( builder: (context) => ref .watch(SharedPrefsController.provider) - .betterWhen( - data: (_) => ElevatedButton( - onPressed: () async { - final response = ref - .watch(ClientController.provider.notifier) - .sendCommand("login", { - "homeserver_url": "https://matrix.federated.nexus", - "username": "quadradical", - "password": "Quadfnrad1!", - }); - print(response); - }, - child: Text("foo"), - ), - // .betterWhen( - // data: (client) => - // client.accessToken == null ? LoginPage() : ChatPage(), - // loading: () => Scaffold( - // body: Center( - // child: Column( - // mainAxisSize: MainAxisSize.min, - // spacing: 16, - // children: [ - // Text( - // "Syncing...", - // style: Theme.of(context).textTheme.headlineMedium, - // ), - // Loading(), - // ], - // ), - // ), - // appBar: Appbar( - // actions: [ - // IconButton( - // onPressed: () => Navigator.of(context).push( - // MaterialPageRoute(builder: (_) => SettingsPage()), - // ), - // icon: Icon(Icons.settings), - // ), - // ], - // ), - // ), - // ), - ), + .betterWhen(data: (_) => LoginPage()), ), ), ); diff --git a/lib/models/homeserver.dart b/lib/models/homeserver.dart new file mode 100644 index 0000000..903e23d --- /dev/null +++ b/lib/models/homeserver.dart @@ -0,0 +1,12 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +part "homeserver.freezed.dart"; + +@freezed +abstract class Homeserver with _$Homeserver { + const factory Homeserver({ + required String name, + required String description, + required Uri url, + required String iconUrl, + }) = _Homeserver; +} diff --git a/lib/models/login.dart b/lib/models/login.dart new file mode 100644 index 0000000..64b3c55 --- /dev/null +++ b/lib/models/login.dart @@ -0,0 +1,14 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +part "login.freezed.dart"; +part "login.g.dart"; + +@freezed +abstract class Login with _$Login { + const factory Login({ + required String username, + required String password, + required String homeserverUrl, + }) = _Login; + + factory Login.fromJson(Map json) => _$LoginFromJson(json); +} diff --git a/lib/models/sync_status.dart b/lib/models/sync_status.dart index 4ea08e5..fc70139 100644 --- a/lib/models/sync_status.dart +++ b/lib/models/sync_status.dart @@ -5,7 +5,7 @@ part "sync_status.g.dart"; @freezed abstract class SyncStatus with _$SyncStatus { const factory SyncStatus({ - required Type type, + required SyncStatusType type, required int errorCount, required int lastSync, }) = _SyncStatus; @@ -15,4 +15,4 @@ abstract class SyncStatus with _$SyncStatus { } @JsonEnum(fieldRename: FieldRename.snake) -enum Type { ok, waiting, erroring, permanentlyFailed } +enum SyncStatusType { ok, waiting, erroring, permanentlyFailed } diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index a31498c..371ad66 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -5,6 +5,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/models/homeserver.dart"; +import "package:nexus/models/login.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/divider_text.dart"; import "package:nexus/widgets/loading.dart"; @@ -15,27 +16,25 @@ class LoginPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); + final client = ref.watch(ClientController.provider.notifier); final isLoading = useState(false); - final allowLogin = useState(false); + final homeserver = useState(null); final launch = ref.watch(LaunchHelper.provider).launchUrl; - Future setHomeserver(Uri? homeserver) async { + Future setHomeserver(Uri? newHomeserver) async { isLoading.value = true; - final succeeded = homeserver == null - ? false - : await ref - .watch(ClientController.provider.notifier) - .setHomeserver( - homeserver.hasScheme - ? homeserver - : Uri.https(homeserver.path), - ); - if (succeeded) { - allowLogin.value = true; - } else if (context.mounted) { + homeserver.value = newHomeserver == null + ? null + : await client.discoverHomeserver( + newHomeserver.hasScheme + ? newHomeserver + : Uri.https(newHomeserver.path), + ); + + if (homeserver.value == null && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( @@ -157,7 +156,7 @@ class LoginPage extends HookConsumerWidget { ), if (isLoading.value) Padding(padding: EdgeInsets.only(top: 32), child: Loading()) - else if (allowLogin.value) ...[ + else if (homeserver.value != null) ...[ DividerText("Then, sign in:"), SizedBox(height: 4), TextField( @@ -174,9 +173,13 @@ class LoginPage extends HookConsumerWidget { ElevatedButton( onPressed: () async { isLoading.value = true; - final succeeded = await ref - .watch(ClientController.provider.notifier) - .login(username.text, password.text); + final succeeded = await client.login( + Login( + username: username.text, + password: password.text, + homeserverUrl: homeserver.value!, + ), + ); if (!succeeded && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 1cac43c..8b658f4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -13,7 +13,6 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - flutter_vodozemac ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/pubspec.lock b/pubspec.lock index 49a2fc3..222e779 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,14 +73,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" - base58check: - dependency: transitive - description: - name: base58check - sha256: "6c300dfc33e598d2fe26319e13f6243fea81eaf8204cb4c6b69ef20a625319a5" - url: "https://pub.dev" - source: hosted - version: "2.0.0" blurhash_dart: dependency: transitive description: @@ -105,14 +97,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.3" - build_cli_annotations: - dependency: transitive - description: - name: build_cli_annotations - sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95 - url: "https://pub.dev" - source: hosted - version: "2.1.1" build_config: dependency: transitive description: @@ -153,14 +137,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.12.1" - canonical_json: - dependency: transitive - description: - name: canonical_json - sha256: d6be1dd66b420c6ac9f42e3693e09edf4ff6edfee26cb4c28c1c019fdb8c0c15 - url: "https://pub.dev" - source: hosted - version: "1.1.2" characters: dependency: transitive description: @@ -557,14 +533,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" - flutter_rust_bridge: - dependency: transitive - description: - name: flutter_rust_bridge - sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e" - url: "https://pub.dev" - source: hosted - version: "2.11.1" flutter_secure_storage: dependency: "direct main" description: @@ -626,14 +594,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_vodozemac: - dependency: "direct main" - description: - name: flutter_vodozemac - sha256: "16d4b44dd338689441fe42a80d0184e5c864e9563823de9e7e6371620d2c0590" - url: "https://pub.dev" - source: hosted - version: "0.4.1" flutter_web_plugins: dependency: transitive description: flutter @@ -768,14 +728,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.15.6" - html_unescape: - dependency: transitive - description: - name: html_unescape - sha256: "15362d7a18f19d7b742ef8dcb811f5fd2a2df98db9f80ea393c075189e0b61e3" - url: "https://pub.dev" - source: hosted - version: "2.0.0" http: dependency: transitive description: @@ -960,14 +912,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - markdown: - dependency: transitive - description: - name: markdown - sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" - url: "https://pub.dev" - source: hosted - version: "7.3.0" matcher: dependency: transitive description: @@ -984,14 +928,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.1" - matrix: - dependency: "direct main" - description: - name: matrix - sha256: fb116ee89f6871441f22f76a988db15cfcfb6dfac97e3e2d654c240080015707 - url: "https://pub.dev" - source: hosted - version: "4.1.0" mention_tag_text_field: dependency: "direct main" description: @@ -1192,14 +1128,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.2" - random_string: - dependency: transitive - description: - name: random_string - sha256: "03b52435aae8cbdd1056cf91bfc5bf845e9706724dd35ae2e99fa14a1ef79d02" - url: "https://pub.dev" - source: hosted - version: "2.3.1" riverpod: dependency: transitive description: @@ -1280,14 +1208,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.26.3" - sdp_transform: - dependency: transitive - description: - name: sdp_transform - sha256: "73e412a5279a5c2de74001535208e20fff88f225c9a4571af0f7146202755e45" - url: "https://pub.dev" - source: hosted - version: "0.3.2" sembast: dependency: transitive description: @@ -1389,14 +1309,6 @@ packages: description: flutter source: sdk version: "0.0.0" - slugify: - dependency: transitive - description: - name: slugify - sha256: b272501565cb28050cac2d96b7bf28a2d24c8dae359280361d124f3093d337c3 - url: "https://pub.dev" - source: hosted - version: "2.0.0" source_gen: dependency: "direct overridden" description: @@ -1437,30 +1349,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" - url: "https://pub.dev" - source: hosted - version: "2.5.6" - sqflite_common_ffi: - dependency: "direct main" - description: - name: sqflite_common_ffi - sha256: "8d7b8749a516cbf6e9057f9b480b716ad14fc4f3d3873ca6938919cc626d9025" - url: "https://pub.dev" - source: hosted - version: "2.3.7+1" - sqlite3: - dependency: transitive - description: - name: sqlite3 - sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" - url: "https://pub.dev" - source: hosted - version: "2.9.4" stack_trace: dependency: transitive description: @@ -1581,14 +1469,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" - unorm_dart: - dependency: transitive - description: - name: unorm_dart - sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b" - url: "https://pub.dev" - source: hosted - version: "0.2.0" url_launcher: dependency: "direct main" description: @@ -1701,14 +1581,6 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" - vodozemac: - dependency: "direct main" - description: - name: vodozemac - sha256: "39144e20740807731871c9248d811ed5a037b21d0aa9ffcfa630954de74139d9" - url: "https://pub.dev" - source: hosted - version: "0.4.0" watcher: dependency: transitive description: @@ -1749,14 +1621,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - webrtc_interface: - dependency: transitive - description: - name: webrtc_interface - sha256: "2e604a31703ad26781782fb14fa8a4ee621154ee2c513d2b9938e486fa695233" - url: "https://pub.dev" - source: hosted - version: "1.3.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0e9adce..7893653 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,14 +54,10 @@ dependencies: git: url: https://github.com/Henry-Hiles/flutter_chat_ui path: packages/flutter_link_previewer - matrix: ^4.1.0 - sqflite_common_ffi: ^2.3.6 color_hash: ^1.0.1 - flutter_vodozemac: ^0.4.1 flutter_widget_from_html_core: ^0.17.0 flutter_svg: ^2.2.2 json_annotation: ^4.9.0 - vodozemac: ^0.4.0 shared_preferences: ^2.5.3 mention_tag_text_field: ^0.0.9 fluttertagger: ^2.3.1 diff --git a/scripts/generate.dart b/scripts/generate.dart index c332b2e..9806603 100644 --- a/scripts/generate.dart +++ b/scripts/generate.dart @@ -12,8 +12,6 @@ void main(List args) async { print("Cloning Gomuks repository..."); final cloneResult = await Process.run("git", [ "clone", - "--branch", - "tulir/ffi", "--depth", "1", "https://mau.dev/gomuks/gomuks", @@ -32,7 +30,7 @@ void main(List args) async { dartFile: Platform.script.resolve("../lib/src/third_party/gomuks.g.dart"), ), headers: Headers( - entryPoints: [File(join(repoDir.path, "pkg", "ffi", "ffi.h")).uri], + entryPoints: [File(join(repoDir.path, "pkg", "ffi", "gomuksffi.h")).uri], compilerOptions: ["--no-warnings"], ), functions: Functions.includeAll, diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 3c6fdca..8967b80 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -13,7 +13,6 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST - flutter_vodozemac ) set(PLUGIN_BUNDLED_LIBRARIES)