diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml deleted file mode 100644 index dc1e9c7..0000000 --- a/.github/workflows/android.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: "Build APK" - -on: - push: - branches: ["main"] - tags: ["*"] - workflow_dispatch: - -jobs: - build-apk: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Lix GHA Installer Action - uses: samueldr/lix-gha-installer-action@v2026-02-22 - with: - extra_nix_config: experimental-features = nix-command flakes flake-self-attrs - - - name: Decode keystore - run: echo "$KEYSTORE_CONTENT" | base64 --decode > keystore.jks - env: - KEYSTORE_CONTENT: ${{ secrets.KEYSTORE_CONTENT }} - - - name: Build app - run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build && flutter build apk --release" - env: - KEYSTORE_PATH: ../../keystore.jks - KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - - - name: Upload installer artifact - uses: actions/upload-artifact@v6 - with: - name: APK - path: build/app/outputs/flutter-apk/app-release.apk \ No newline at end of file diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml deleted file mode 100644 index 5e693f0..0000000 --- a/.github/workflows/flatpak.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: "Build Flatpaks" - -on: - push: - branches: ["main"] - tags: ["*"] - workflow_dispatch: - -jobs: - build-flatpak: - strategy: - fail-fast: false - matrix: - include: - - arch: x86_64 - runner: ubuntu-latest - - arch: aarch64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Lix GHA Installer Action - uses: samueldr/lix-gha-installer-action@v2026-02-22 - with: - extra_nix_config: experimental-features = nix-command flakes flake-self-attrs - - - name: Build app - run: nix build .#flatpak - - - name: Upload installer artifact - uses: actions/upload-artifact@v6 - with: - name: flatpak-${{ matrix.arch }} - path: result/nexus.federated.Nexus.flatpak \ No newline at end of file diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e66e45a..c8099d1 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,50 +1,46 @@ -name: "Build EXE" +name: "Build Windows Version" on: - push: - branches: ["main"] - tags: ["*"] workflow_dispatch: jobs: - build-exe: - runs-on: windows-latest + build-windows: + runs-on: "windows-latest" steps: - - name: Checkout repository - uses: actions/checkout@v6 + - name: "Checkout repository" + uses: "actions/checkout@v4" + + - name: "Set up Flutter" + uses: "subosito/flutter-action@v2" + + - name: "Set up Rust" + uses: "dtolnay/rust-toolchain@stable" with: - submodules: recursive + targets: "x86_64-pc-windows-msvc" - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - flutter-version: 3.41.5 + - name: "Install Flutter dependencies" + run: flutter pub get - - name: Set up Go - uses: actions/setup-go@v6 - - - name: Build with Flutter + - name: "Run build_runner & build Windows EXE" run: | - flutter pub get - dart scripts/generate.dart - flutter pub run build_runner build + flutter pub run build_runner build --delete-conflicting-outputs flutter build windows --release - - name: Upload exe zip - uses: actions/upload-artifact@v6 + - name: "Upload exe zip" + uses: "actions/upload-artifact@v4" with: - name: windows-portable - path: build/windows/x64/runner/Release/ + name: "windows-portable" + path: "build/windows/x64/runner/Release/" - - name: Install Inno Setup + - name: "Install Inno Setup" run: choco install innosetup -y - - name: Build Inno Setup installer + - name: "Build Inno Setup installer" run: iscc windows/installer.iss - - name: Upload installer artifact - uses: actions/upload-artifact@v6 + - name: "Upload installer artifact" + uses: "actions/upload-artifact@v4" with: - name: windows-installer - path: windows/dist/Nexus-Setup.exe \ No newline at end of file + name: "windows-installer" + path: "windows/dist/Nexus-Setup.exe" diff --git a/.gitignore b/.gitignore index 2bec583..d6616e1 100644 --- a/.gitignore +++ b/.gitignore @@ -36,9 +36,7 @@ key.properties # Generated Files *.g.dart *.freezed.dart +src/ # Devel Password -password.txt - -# Nix -/result \ No newline at end of file +password.txt \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 17d64ba..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "gomuks"] - path = gomuks - url = https://github.com/zachatrocity/gomuks - branch = init-root-dir diff --git a/README.md b/README.md index 7af23a0..1299c71 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Description -A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. +A simple and user-friendly Matrix client made with Flutter and the Matrix Dart SDK. ## Screenshots @@ -17,8 +17,8 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. - [ ] New logo - [ ] Make context menus appear as bottom sheets on mobile -- [x] Move from the Dart SDK to the Gomuks Backend with Dart bindings: https://git.federated.nexus/Henry-Hiles/nexus/pulls/2 - - [ ] Allow using remote Gomuks over websocket +- [x] Move from the Dart SDK to the Gomuks SDK with Dart bindings: https://git.federated.nexus/Henry-Hiles/nexus/pulls/2 + - [ ] Allow using remote gomuks over websocket - [ ] Platform Support - [x] Linux - [x] Windows @@ -54,7 +54,6 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. - [x] HTML/Markdown - [x] Replies - [x] Choose ping on/off - - [ ] Per message profiles - [ ] Attachments - [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391) - [x] Mentions @@ -65,7 +64,6 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. - [ ] GIFs using Gomuks' GIF proxies - [x] Recieving - [x] Plain text - - [x] Per message profiles - [x] HTML - [x] Replies - [x] Viewing @@ -130,7 +128,7 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. First, clone and open the repo: ```sh -git clone --recurse-submodules https://git.federated.nexus/Henry-Hiles/nexus +git clone https://git.federated.nexus/Henry-Hiles/nexus cd nexus ``` @@ -138,12 +136,12 @@ cd nexus #### Linux -- With Nix: Either use direnv and `direnv allow`, or `nix flake develop` +- With Nix: Either use direnv, or `nix flake develop` - Without Nix: Install Flutter, Go, Olm, Git, Clang, and GLibc. #### Windows / MacOS -I don't really know. You will need Flutter, Git, Go, and Visual Studio tools, and otherwise I guess just keep installing stuff until there aren't any errors. I will look into this sometimeTM. +I don't really know. You will need Flutter, Git, Olm, Go, and Visual Studio tools, and otherwise I guess just keep installing stuff until there aren't any errors. I will look into this sometimeTM. ### Set up Flutter @@ -153,7 +151,13 @@ Get dependencies: flutter pub get ``` -Generate Gomuks bindings: +Get dependencies: + +```sh +flutter pub get +``` + +Clone Gomuks and generate bindings: ```sh scripts/generate.sh diff --git a/android/app/build.gradle b/android/app/build.gradle index fd51ea0..ce5f465 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -39,11 +39,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - // do we want to update.. eventually? - jvmTarget = "17" - } - defaultConfig { applicationId = "nexus.federated.Nexus" minSdk = 29 @@ -55,8 +50,7 @@ android { signingConfigs { release { keyAlias "key" - def storePath = keystoreProperties['path'] ?: System.getenv("KEYSTORE_PATH") - storeFile storePath ? file(storePath) : null + storeFile keystoreProperties['path'] ? file(keystoreProperties['path']) : file(System.getenv("KEYSTORE_PATH")) keyPassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD") storePassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD") } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 666977e..1c369c9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ android:label="Nexus" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" - android:roundIcon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/nexus_round" android:allowBackup="false" android:fullBackupContent="false"> - - - diff --git a/assets/foreground.png b/assets/foreground.png index 76a446a..4249989 100644 Binary files a/assets/foreground.png and b/assets/foreground.png differ diff --git a/assets/reply-preview.png b/assets/reply-preview.png new file mode 100644 index 0000000..3c4cc3e Binary files /dev/null and b/assets/reply-preview.png differ diff --git a/assets/reply.webp b/assets/reply.webp new file mode 100644 index 0000000..e8f139e Binary files /dev/null and b/assets/reply.webp differ diff --git a/flake.lock b/flake.lock index 8070c6c..7826732 100644 --- a/flake.lock +++ b/flake.lock @@ -18,54 +18,17 @@ "type": "github" } }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nix2flatpak": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1774604963, - "narHash": "sha256-MtAW1FIdirSlUAAO7s1u9auv5y3I6t3uJ+GeEbqiqxI=", - "owner": "neobrain", - "repo": "nix2flatpak", - "rev": "3e04657fbcb49956ac301410b071a7f0b2ad5988", - "type": "github" - }, - "original": { - "owner": "neobrain", - "repo": "nix2flatpak", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1773389992, - "narHash": "sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk=", - "owner": "NixOS", + "lastModified": 1767640445, + "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=", + "owner": "nixos", "repo": "nixpkgs", - "rev": "c06b4ae3d6599a672a6210b7021d699c351eebda", + "rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5", "type": "github" }, "original": { - "owner": "NixOS", + "owner": "nixos", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" @@ -86,42 +49,10 @@ "type": "github" } }, - "nixpkgs_2": { - "locked": { - "lastModified": 1767640445, - "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "root": { "inputs": { "flake-parts": "flake-parts", - "nix2flatpak": "nix2flatpak", - "nixpkgs": "nixpkgs_2" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" + "nixpkgs": "nixpkgs" } } }, diff --git a/flake.nix b/flake.nix index 4c06ac6..de21b13 100644 --- a/flake.nix +++ b/flake.nix @@ -2,10 +2,8 @@ description = "Nexus Flutter Flake"; inputs = { - self.submodules = true; nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; - nix2flatpak.url = "github:neobrain/nix2flatpak"; }; outputs = @@ -35,42 +33,36 @@ _module.args.pkgs = import nixpkgs { inherit system; config = { + permittedInsecurePackages = [ "olm-3.2.16" ]; android_sdk.accept_license = true; allowUnfree = true; }; }; - packages = + devShells = let - default = pkgs.callPackage ./linux/nix/pkg { - src = self; + packages = with pkgs; [ + go + olm + git + ]; + + env = { + LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ]; + LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}"; + CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ]; }; in { - inherit default; - - flatpak = inputs.nix2flatpak.lib.${system}.mkFlatpak { - appName = "Nexus"; - developer = "QuadRadical"; - appId = "nexus.federated.Nexus"; - package = default; - runtime = "org.gnome.Platform/49"; - permissions = { - share = [ "network" ]; - sockets = [ - "fallback-x11" - "wayland" - ]; - devices = [ "dri" ]; - }; + default = pkgs.mkShell { + inherit env; + packages = packages ++ [ + pkgs.flutter + ]; }; - gomuks = pkgs.callPackage ./linux/nix/pkg/gomuks.nix { - src = self; - }; + nix = pkgs.mkShell { inherit packages env; }; }; - - devShells.default = pkgs.callPackage ./linux/nix/devshell.nix { }; }; }; } diff --git a/gomuks b/gomuks deleted file mode 160000 index daa0ba0..0000000 --- a/gomuks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit daa0ba028e7d89ba9fc7580fc8099348e6145cb3 diff --git a/hook/build.dart b/hook/build.dart index 13eb804..4cb2f91 100644 --- a/hook/build.dart +++ b/hook/build.dart @@ -3,12 +3,11 @@ import "package:hooks/hooks.dart"; import "package:code_assets/code_assets.dart"; Future main(List args) => build(args, (input, output) async { - final codeConfig = input.config.code; - final targetOS = codeConfig.targetOS; - final targetArch = codeConfig.targetArchitecture; + final buildDir = input.packageRoot.resolve("src/"); + if (await File(buildDir.resolve("lock").toFilePath()).exists()) return; + final targetOS = input.config.code.targetOS; String libFileName; - Map env = {}; switch (targetOS) { case OS.linux: libFileName = "libgomuks.so"; @@ -18,60 +17,24 @@ Future main(List args) => build(args, (input, output) async { break; case OS.windows: libFileName = "libgomuks.dll"; - env = {"GOCACHE": r"C:\Users\runneradmin\AppData\Local\go-build"}; - break; - case OS.android: - libFileName = "libgomuks.so"; - - final targetNdkApi = codeConfig.android.targetNdkApi; - - final ndkHome = - Platform.environment["ANDROID_NDK_HOME"] ?? - Platform.environment["ANDROID_NDK_ROOT"] ?? - Platform.environment["NDK_HOME"] ?? - await _findNdkFromSdk(); - if (ndkHome == null) { - throw Exception( - "Could not find Android NDK. Set ANDROID_NDK_HOME or install via sdkmanager.", - ); - } - - final hostTag = _ndkHostTag(); - final (goArch, ccTriple) = _androidArch(targetArch); - final cc = - "$ndkHome/toolchains/llvm/prebuilt/$hostTag/bin/$ccTriple$targetNdkApi-clang"; - - env = {"CGO_ENABLED": "1", "GOOS": "android", "GOARCH": goArch, "CC": cc}; break; default: throw UnsupportedError("Unsupported OS: $targetOS"); } - var libFile = input.packageRoot.resolve(libFileName); - final gomuksBuildDir = input.packageRoot.resolve("gomuks/"); + final gomuksBuildDir = buildDir.resolve("gomuks/"); + final libFile = gomuksBuildDir.resolve(libFileName); - if (!(await File.fromUri(libFile).exists())) { - final buildDir = input.packageRoot.resolve("build/"); - libFile = buildDir.resolve("${targetArch.name}/$libFileName"); + print("Building Gomuks shared library $libFileName from source..."); + final result = await Process.run("go", [ + "build", + "-o", + libFile.path, + "-buildmode=c-shared", + ], workingDirectory: gomuksBuildDir.resolve("source/pkg/ffi/").toFilePath()); - // goheif/dav1d supported on Android would need to fix upstream - final tags = targetOS == OS.android ? "goolm,noheic" : "goolm"; - - print( - "Building Gomuks shared library $libFileName (${targetOS.name}/${targetArch.name}) from source...", - ); - final result = await Process.run( - "go", - ["build", "-tags", tags, "-o", libFile.path, "-buildmode=c-shared"], - workingDirectory: gomuksBuildDir.resolve("pkg/ffi/").toFilePath(), - environment: env.isNotEmpty ? env : null, - ); - - if (result.exitCode != 0) { - throw Exception( - "Failed to build Gomuks shared library\n${result.stderr}", - ); - } + if (result.exitCode != 0) { + throw Exception("Failed to build Gomuks shared library\n${result.stderr}"); } final generatedFile = "src/third_party/gomuks.g.dart"; @@ -89,43 +52,3 @@ Future main(List args) => build(args, (input, output) async { ..dependencies.add(gomuksBuildDir); print("Done!"); }); - -Future _findNdkFromSdk() async { - // pretty sure this wont be needed with nix, i'll get this removed - final androidHome = - Platform.environment["ANDROID_HOME"] ?? - Platform.environment["ANDROID_SDK_ROOT"]; - if (androidHome == null) return null; - final ndkDir = Directory("$androidHome/ndk"); - if (!await ndkDir.exists()) return null; - final versions = await ndkDir.list().toList(); - if (versions.isEmpty) return null; - versions.sort((a, b) => a.path.compareTo(b.path)); - return versions.last.path; -} - -String _ndkHostTag() { - if (Platform.isMacOS) { - return "darwin-x86_64"; - } else if (Platform.isLinux) { - return "linux-x86_64"; - } else if (Platform.isWindows) { - return "windows-x86_64"; - } - throw UnsupportedError("Unsupported host platform for Android NDK"); -} - -(String goArch, String ccTriple) _androidArch(Architecture arch) { - switch (arch) { - case Architecture.arm64: - return ("arm64", "aarch64-linux-android"); - case Architecture.arm: - return ("arm", "armv7a-linux-androideabi"); - case Architecture.x64: - return ("amd64", "x86_64-linux-android"); - case Architecture.ia32: - return ("386", "i686-linux-android"); - default: - throw UnsupportedError("Unsupported Android architecture: $arch"); - } -} diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 44f96da..d4af5a1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -387,7 +387,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus; + PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -519,7 +519,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus; + PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -545,7 +545,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus; + PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/lib/controllers/author_controller.dart b/lib/controllers/author_controller.dart deleted file mode 100644 index c7e4e05..0000000 --- a/lib/controllers/author_controller.dart +++ /dev/null @@ -1,44 +0,0 @@ -import "dart:async"; -import "package:collection/collection.dart"; -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/members_controller.dart"; -import "package:nexus/models/configs/author_config.dart"; -import "package:nexus/models/membership.dart"; - -class AuthorController extends AsyncNotifier { - final AuthorConfig config; - AuthorController(this.config); - - @override - Future build() async { - var member = await ref.watch( - MembersController.provider(config.room).selectAsync( - (value) => value.firstWhereOrNull( - (membership) => membership.userId == config.message.authorId, - ), - ), - ); - - final pmp = config.message.metadata?["pmp"] == null - ? null - : Membership.fromContent( - IMap(config.message.metadata?["pmp"]), - config.message.authorId, - ); - - return Membership( - avatarUrl: pmp?.avatarUrl ?? member?.avatarUrl, - displayName: - pmp?.displayName ?? - member?.displayName ?? - config.message.authorId.substring(1).split(":").first, - userId: config.message.authorId, - ); - } - - static final provider = AsyncNotifierProvider.family - .autoDispose( - AuthorController.new, - ); -} diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index 37dde3d..de6e909 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -1,6 +1,5 @@ import "dart:developer"; import "dart:ffi"; -import "dart:io"; import "dart:isolate"; import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; @@ -32,20 +31,11 @@ import "package:nexus/models/sync_data.dart"; import "package:nexus/models/sync_status.dart"; import "package:nexus/src/third_party/gomuks.g.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:path_provider/path_provider.dart"; class ClientController extends AsyncNotifier { @override Future build() async { - final Pointer root; - if (Platform.isAndroid) { - final dir = await getApplicationSupportDirectory(); - root = "${dir.path}/gomuks".toNativeUtf8().cast(); - } else { - root = nullptr.cast(); - } - - final handle = GomuksInit(root); + final handle = await Isolate.run(GomuksInit); final callable = NativeCallable< @@ -153,12 +143,12 @@ class ClientController extends AsyncNotifier { Future sendMessage(SendMessageRequest request) => _sendCommand("send_message", request.toJson()); - Future verify(String recoveryKey) async { + Future verify(String recoveryKey) async { try { await _sendCommand("verify", {"recovery_key": recoveryKey}); - return null; + return true; } catch (error) { - return error.toString(); + return false; } } @@ -228,12 +218,12 @@ class ClientController extends AsyncNotifier { }); } - Future login(LoginRequest login) async { + Future login(LoginRequest login) async { try { await _sendCommand("login", login.toJson()); - return null; + return true; } catch (error) { - return error.toString(); + return false; } } diff --git a/lib/controllers/members_controller.dart b/lib/controllers/members_controller.dart index 80e73a0..268a30d 100644 --- a/lib/controllers/members_controller.dart +++ b/lib/controllers/members_controller.dart @@ -1,39 +1,25 @@ +import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/client_controller.dart"; -import "package:nexus/models/membership.dart"; -import "package:nexus/models/requests/get_room_state_request.dart"; +import "package:nexus/models/event.dart"; import "package:nexus/models/room.dart"; -class MembersController extends AsyncNotifier> { +class MembersController extends Notifier> { final Room room; MembersController(this.room); @override - Future> build() async { - if (room.metadata == null) return const IList.empty(); + IList build() => (room.state["m.room.member"]?.values ?? []) + .map( + (eventRowId) => + room.events.firstWhereOrNull((event) => event.rowId == eventRowId), + ) + .nonNulls + .where((member) => member.content["membership"] == "join") + .toIList(); - final state = await ref - .watch(ClientController.provider.notifier) - .getRoomState( - GetRoomStateRequest( - roomId: room.metadata!.id, - fetchMembers: room.metadata!.hasMemberList == false, - includeMembers: true, - ), - ); - - return state.nonNulls - .where((member) => member.content["membership"] == "join") - .map( - (membership) => - Membership.fromContent(membership.content, membership.stateKey!), - ) - .toIList(); - } - - static final provider = - AsyncNotifierProvider.family, Room>( + static final provider = NotifierProvider.family + .autoDispose, Room>( MembersController.new, ); } diff --git a/lib/controllers/message_controller.dart b/lib/controllers/message_controller.dart index d84aabb..f3ef13b 100644 --- a/lib/controllers/message_controller.dart +++ b/lib/controllers/message_controller.dart @@ -2,8 +2,9 @@ import "package:collection/collection.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_state_controller.dart"; +import "package:nexus/controllers/members_controller.dart"; import "package:nexus/helpers/extensions/mxc_to_https.dart"; -import "package:nexus/models/configs/message_config.dart"; +import "package:nexus/models/message_config.dart"; class MessageController extends AsyncNotifier { final MessageConfig config; @@ -26,6 +27,12 @@ class MessageController extends AsyncNotifier { if (!ref.mounted) return null; + final members = ref.read(MembersController.provider(config.room)); + final author = members.firstWhereOrNull( + (member) => member.stateKey == event.authorId, + ); + if (!ref.mounted) return null; + final content = (event.decrypted ?? event.content); final type = (config.event.decryptedType ?? config.event.type); final newContent = content["m.new_content"] as Map?; @@ -45,11 +52,14 @@ class MessageController extends AsyncNotifier { "timelineId": event.timelineRowId, "big": event.localContent?.bigEmoji == true, "eventType": type, - "pmp": event.content["com.beeper.per_message_profile"], + "avatarUrl": author?.content["avatar_url"], "editSource": event.localContent?.editSource ?? newContent?["body"] ?? content["body"], + "displayName": author?.content["displayname"]?.isNotEmpty == true + ? author?.content["displayname"] + : event.authorId.substring(1).split(":")[0], "txnId": config.event.transactionId, }; diff --git a/lib/controllers/messages_controller.dart b/lib/controllers/messages_controller.dart index 28885fb..83bd815 100644 --- a/lib/controllers/messages_controller.dart +++ b/lib/controllers/messages_controller.dart @@ -2,8 +2,8 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/message_controller.dart"; -import "package:nexus/models/configs/message_config.dart"; -import "package:nexus/models/configs/messages_config.dart"; +import "package:nexus/models/message_config.dart"; +import "package:nexus/models/messages_config.dart"; class MessagesController extends AsyncNotifier> { final MessagesConfig config; diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index d737154..4a4dba2 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -1,4 +1,5 @@ import "dart:async"; + import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; @@ -10,8 +11,8 @@ import "package:nexus/controllers/message_controller.dart"; import "package:nexus/controllers/messages_controller.dart"; import "package:nexus/controllers/new_events_controller.dart"; import "package:nexus/controllers/rooms_controller.dart"; -import "package:nexus/models/configs/messages_config.dart"; -import "package:nexus/models/configs/message_config.dart"; +import "package:nexus/models/message_config.dart"; +import "package:nexus/models/messages_config.dart"; import "package:nexus/models/requests/get_room_state_request.dart"; import "package:nexus/models/requests/paginate_request.dart"; import "package:nexus/models/requests/redact_event_request.dart"; @@ -30,7 +31,11 @@ class RoomChatController extends AsyncNotifier { if (room == null) return InMemoryChatController(); final state = await client.getRoomState( - GetRoomStateRequest(roomId: roomId), + GetRoomStateRequest( + roomId: roomId, + fetchMembers: room.metadata?.hasMemberList == false, + includeMembers: true, + ), ); ref diff --git a/lib/controllers/rooms_controller.dart b/lib/controllers/rooms_controller.dart index 3c6e287..0945644 100644 --- a/lib/controllers/rooms_controller.dart +++ b/lib/controllers/rooms_controller.dart @@ -36,7 +36,6 @@ class RoomsController extends Notifier> { return acc.add( roomId, existing?.copyWith( - hasMore: incoming.hasMore, metadata: incoming.metadata ?? existing.metadata, events: events!, state: incoming.state.entries.fold( diff --git a/lib/helpers/extensions/join_room_with_snackbars.dart b/lib/helpers/extensions/join_room_with_snackbars.dart index eaa0659..05b045d 100644 --- a/lib/helpers/extensions/join_room_with_snackbars.dart +++ b/lib/helpers/extensions/join_room_with_snackbars.dart @@ -15,7 +15,6 @@ extension JoinRoomWithSnackbars on ClientController { WidgetRef ref, ) async { final roomIdOrAlias = roomAlias.mention ?? roomAlias; - // TODO: Parse vias properly final scaffoldMessenger = ScaffoldMessenger.of(context); diff --git a/lib/main.dart b/lib/main.dart index 192ca29..5ad6c24 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ 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"; +import "package:window_size/window_size.dart"; final GlobalKey navigatorKey = GlobalKey(); @@ -58,11 +59,14 @@ void showError(Object error, [StackTrace? stackTrace]) { void main() async { WidgetsFlutterBinding.ensureInitialized(); - if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { - await windowManager.ensureInitialized(); - await windowManager.waitUntilReadyToShow( - WindowOptions(titleBarStyle: TitleBarStyle.hidden), - ); + await windowManager.ensureInitialized(); + await windowManager.waitUntilReadyToShow( + WindowOptions(titleBarStyle: TitleBarStyle.hidden), + ); + + if (Platform.isLinux) { + setWindowMinSize(const Size.square(500)); + } else { await windowManager.setMinimumSize(Size.square(500)); } diff --git a/lib/models/configs/author_config.dart b/lib/models/configs/author_config.dart deleted file mode 100644 index af63c63..0000000 --- a/lib/models/configs/author_config.dart +++ /dev/null @@ -1,14 +0,0 @@ -import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -import "package:nexus/models/room.dart"; -part "author_config.freezed.dart"; -part "author_config.g.dart"; - -@freezed -abstract class AuthorConfig with _$AuthorConfig { - const factory AuthorConfig({required Message message, required Room room}) = - _AuthorConfig; - - factory AuthorConfig.fromJson(Map json) => - _$AuthorConfigFromJson(json); -} diff --git a/lib/models/membership.dart b/lib/models/membership.dart deleted file mode 100644 index ec18be7..0000000 --- a/lib/models/membership.dart +++ /dev/null @@ -1,22 +0,0 @@ -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:freezed_annotation/freezed_annotation.dart"; -part "membership.freezed.dart"; - -@freezed -abstract class Membership with _$Membership { - const Membership._(); - const factory Membership({ - required Uri? avatarUrl, - required String displayName, - required String userId, - }) = _Membership; - - factory Membership.fromContent( - IMap content, - String userId, - ) => Membership( - avatarUrl: Uri.tryParse(content["avatar_url"] ?? ""), - userId: userId, - displayName: content["displayname"] ?? userId.substring(1).split(":").first, - ); -} diff --git a/lib/models/configs/message_config.dart b/lib/models/message_config.dart similarity index 100% rename from lib/models/configs/message_config.dart rename to lib/models/message_config.dart diff --git a/lib/models/configs/messages_config.dart b/lib/models/messages_config.dart similarity index 100% rename from lib/models/configs/messages_config.dart rename to lib/models/messages_config.dart diff --git a/lib/models/requests/get_room_state_request.dart b/lib/models/requests/get_room_state_request.dart index de66b72..a154d5f 100644 --- a/lib/models/requests/get_room_state_request.dart +++ b/lib/models/requests/get_room_state_request.dart @@ -6,7 +6,7 @@ part "get_room_state_request.g.dart"; abstract class GetRoomStateRequest with _$GetRoomStateRequest { const factory GetRoomStateRequest({ required String roomId, - @Default(false) bool fetchMembers, + required bool fetchMembers, @Default(false) bool includeMembers, }) = _GetRoomStateRequest; diff --git a/lib/pages/chat_page.dart b/lib/pages/chat_page.dart index 7aa8156..ee2f4d0 100644 --- a/lib/pages/chat_page.dart +++ b/lib/pages/chat_page.dart @@ -16,7 +16,7 @@ class ChatPage extends ConsumerWidget { body: Builder( builder: (context) => Row( children: [ - if (isDesktop) Sidebar(isDesktop: isDesktop), + if (isDesktop) Sidebar(), Expanded( child: RoomChat( isDesktop: isDesktop, @@ -26,7 +26,7 @@ class ChatPage extends ConsumerWidget { ], ), ), - drawer: isDesktop ? null : Sidebar(isDesktop: isDesktop), + drawer: isDesktop ? null : Sidebar(), ); }, ); diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index fda53d0..bd41d51 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -175,7 +175,7 @@ class LoginPage extends HookConsumerWidget { ElevatedButton( onPressed: () async { isLoading.value = true; - final error = await client.login( + final succeeded = await client.login( LoginRequest( username: username.text, password: password.text, @@ -183,11 +183,11 @@ class LoginPage extends HookConsumerWidget { ), ); - if (error != null && context.mounted) { + if (!succeeded && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - "Login failed. Is your password right?\nError: $error", + "Login failed. Is your password right?", style: TextStyle( color: theme.colorScheme.onErrorContainer, ), diff --git a/lib/pages/verify_page.dart b/lib/pages/verify_page.dart index 962701c..1011f80 100644 --- a/lib/pages/verify_page.dart +++ b/lib/pages/verify_page.dart @@ -2,7 +2,6 @@ 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/appbar.dart"; import "package:nexus/widgets/form_text_input.dart"; class VerifyPage extends HookConsumerWidget { @@ -12,75 +11,72 @@ class VerifyPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final passphraseController = useTextEditingController(); final isVerifying = useState(false); - return Scaffold( - appBar: Appbar(), - body: 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: isVerifying.value - ? null - : () async { - final scaffoldMessenger = ScaffoldMessenger.of(context); - final snackbar = scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - "Attempting to verify with recovery key...", - ), - duration: Duration(days: 999), - ), - ); - - isVerifying.value = true; - - final error = await ref - .watch(ClientController.provider.notifier) - .verify(passphraseController.text); - - snackbar.close(); - if (error != null) { - isVerifying.value = false; - if (context.mounted) { - scaffoldMessenger.showSnackBar( - SnackBar( - backgroundColor: Theme.of( - context, - ).colorScheme.errorContainer, - content: Text( - "Verification failed. Is your passphrase correct?\nError: $error", - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onErrorContainer, - ), - ), - ), - ); - } - } - }, - child: Text("Verify"), + 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: isVerifying.value + ? null + : () async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final snackbar = scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + "Attempting to verify with recovery key...", + ), + duration: Duration(days: 999), + ), + ); + + isVerifying.value = true; + + final success = await ref + .watch(ClientController.provider.notifier) + .verify(passphraseController.text); + + snackbar.close(); + if (!success) { + isVerifying.value = false; + if (context.mounted) { + scaffoldMessenger.showSnackBar( + SnackBar( + backgroundColor: Theme.of( + context, + ).colorScheme.errorContainer, + content: Text( + "Verification failed. Is your passphrase correct?", + style: TextStyle( + color: Theme.of( + context, + ).colorScheme.onErrorContainer, + ), + ), + ), + ); + } + } + }, + child: Text("Verify"), + ), + ], ); } } diff --git a/lib/widgets/appbar.dart b/lib/widgets/appbar.dart index aae6c13..5b14244 100644 --- a/lib/widgets/appbar.dart +++ b/lib/widgets/appbar.dart @@ -35,14 +35,15 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget { } return GestureDetector( + behavior: HitTestBehavior.translucent, + onDoubleTap: maximize, onPanStart: (_) => windowManager.startDragging(), child: AppBar( leading: leading, backgroundColor: backgroundColor, scrolledUnderElevation: scrolledUnderElevation, actionsPadding: const EdgeInsets.symmetric(horizontal: 8), - title: IgnorePointer(child: title), - flexibleSpace: GestureDetector(onDoubleTap: maximize), + title: title, actions: [ ...actions, if (!(Platform.isAndroid || Platform.isIOS)) ...[ diff --git a/lib/widgets/chat_page/composer/chat_box.dart b/lib/widgets/chat_page/chat_box.dart similarity index 89% rename from lib/widgets/chat_page/composer/chat_box.dart rename to lib/widgets/chat_page/chat_box.dart index a688fa7..b9e7dbb 100644 --- a/lib/widgets/chat_page/composer/chat_box.dart +++ b/lib/widgets/chat_page/chat_box.dart @@ -1,3 +1,4 @@ +import "dart:io"; import "package:flutter/material.dart"; import "package:flutter/services.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; @@ -7,8 +8,8 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/room_chat_controller.dart"; import "package:nexus/models/relation_type.dart"; import "package:nexus/models/room.dart"; -import "package:nexus/widgets/chat_page/composer/mention_overlay.dart"; -import "package:nexus/widgets/chat_page/composer/relation_preview.dart"; +import "package:nexus/widgets/chat_page/mention_overlay.dart"; +import "package:nexus/widgets/chat_page/relation_preview.dart"; class ChatBox extends HookConsumerWidget { final Message? relatedMessage; @@ -54,15 +55,20 @@ class ChatBox extends HookConsumerWidget { final node = useFocusNode( onKeyEvent: (_, event) { - if (event is KeyDownEvent && - event.logicalKey == LogicalKeyboardKey.escape) { - onDismiss(); - return KeyEventResult.handled; + if (event is KeyDownEvent && !Platform.isAndroid && !Platform.isIOS) { + if (event.logicalKey == LogicalKeyboardKey.enter && + !HardwareKeyboard.instance.isShiftPressed) { + send(); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.escape) { + onDismiss(); + return KeyEventResult.handled; + } } return KeyEventResult.ignored; }, - ); + )..requestFocus(); final style = TextStyle( color: theme.colorScheme.primary, @@ -80,11 +86,10 @@ class ChatBox extends HookConsumerWidget { child: Column( children: [ RelationPreview( - relatedMessage, - room: room, shouldMention: shouldMention.value, toggleShouldMention: () => shouldMention.value = !shouldMention.value, + relatedMessage: relatedMessage, relationType: relationType, onDismiss: onDismiss, ), @@ -150,9 +155,7 @@ class ChatBox extends HookConsumerWidget { ), controller: controller.value, key: key, - // TODO: Setting for send on enter on / off - onFieldSubmitted: (_) => send(), - textInputAction: TextInputAction.done, + autofocus: true, focusNode: node, ), ), diff --git a/lib/widgets/chat_page/html/html.dart b/lib/widgets/chat_page/html/html.dart index dcc1d49..1e1ab82 100644 --- a/lib/widgets/chat_page/html/html.dart +++ b/lib/widgets/chat_page/html/html.dart @@ -22,10 +22,6 @@ class Html extends ConsumerWidget { html, textStyle: textStyle, customWidgetBuilder: (element) { - if (element.attributes.keys.contains("data-mx-profile-fallback")) { - return SizedBox.shrink(); - } - if (element.attributes.keys.contains("data-mx-spoiler")) { return InlineCustomWidget(child: SpoilerText(text: element.text)); } diff --git a/lib/widgets/chat_page/lazy_loading/message_avatar.dart b/lib/widgets/chat_page/lazy_loading/message_avatar.dart deleted file mode 100644 index 71fcf84..0000000 --- a/lib/widgets/chat_page/lazy_loading/message_avatar.dart +++ /dev/null @@ -1,30 +0,0 @@ -import "package:flutter/widgets.dart"; -import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/author_controller.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; -import "package:nexus/models/configs/author_config.dart"; -import "package:nexus/models/room.dart"; -import "package:nexus/widgets/avatar_or_hash.dart"; - -class MessageAvatar extends ConsumerWidget { - final Message message; - final Room room; - final double height; - const MessageAvatar(this.message, this.room, {this.height = 16, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) => ref - .watch( - AuthorController.provider(AuthorConfig(room: room, message: message)), - ) - .betterWhen( - data: (membership) => AvatarOrHash( - membership.avatarUrl, - membership.displayName, - height: height, - ), - loading: () => - AvatarOrHash(null, message.authorId.substring(1), height: height), - ); -} diff --git a/lib/widgets/chat_page/lazy_loading/message_displayname.dart b/lib/widgets/chat_page/lazy_loading/message_displayname.dart deleted file mode 100644 index 7c10df3..0000000 --- a/lib/widgets/chat_page/lazy_loading/message_displayname.dart +++ /dev/null @@ -1,28 +0,0 @@ -import "package:flutter/widgets.dart"; -import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/author_controller.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; -import "package:nexus/models/configs/author_config.dart"; -import "package:nexus/models/room.dart"; - -class MessageDisplayname extends ConsumerWidget { - final Message message; - final Room room; - final TextStyle? style; - const MessageDisplayname(this.message, this.room, {this.style, super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) => ref - .watch( - AuthorController.provider(AuthorConfig(room: room, message: message)), - ) - .betterWhen( - data: (membership) => Text( - "${membership.displayName} ${message.metadata?["pmp"] == null ? "" : "(via ${message.authorId})"}", - style: style, - overflow: TextOverflow.ellipsis, - ), - loading: () => Text(""), - ); -} diff --git a/lib/widgets/chat_page/member_list.dart b/lib/widgets/chat_page/member_list.dart index 8cdbbb9..24d22e4 100644 --- a/lib/widgets/chat_page/member_list.dart +++ b/lib/widgets/chat_page/member_list.dart @@ -1,7 +1,6 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/members_controller.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/models/room.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; @@ -11,17 +10,15 @@ class MemberList extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final membersProvider = ref.watch(MembersController.provider(room)); + final members = ref.watch(MembersController.provider(room)); return Drawer( shape: Border(), - child: Column( + child: ListView( children: [ AppBar( scrolledUnderElevation: 0, leading: Icon(Icons.people), - title: Text( - "Members ${membersProvider.when(data: (members) => "${members.length}", error: (_, _) => "", loading: () => "")}", - ), + title: Text("Members (${members.length})"), actionsPadding: EdgeInsets.only(right: 4), actions: [ if (Scaffold.of(context).hasEndDrawer) @@ -32,32 +29,24 @@ class MemberList extends ConsumerWidget { ), ], ), - membersProvider.betterWhen( - data: (members) => Expanded( - child: ListView( - children: members - .map( - (member) => ListTile( - onTap: () => showDialog( - context: context, - builder: (context) => - Dialog(child: Text("TODO: Open member popover")), - ), - leading: AvatarOrHash( - member.avatarUrl, - member.displayName, - ), - title: Text( - member.displayName, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - member.userId, - overflow: TextOverflow.ellipsis, - ), - ), - ) - .toList(), + ...members.map( + (member) => ListTile( + onTap: () => showDialog( + context: context, + builder: (context) => + Dialog(child: Text("TODO: Open member popover")), + ), + leading: AvatarOrHash( + Uri.tryParse(member.content["avatar_url"] ?? ""), + member.content["displayname"].toString(), + ), + title: Text( + member.content["displayname"].toString(), + overflow: TextOverflow.ellipsis, + ), + subtitle: Text( + member.stateKey ?? "Unknown User", + overflow: TextOverflow.ellipsis, ), ), ), diff --git a/lib/widgets/chat_page/composer/mention_overlay.dart b/lib/widgets/chat_page/mention_overlay.dart similarity index 56% rename from lib/widgets/chat_page/composer/mention_overlay.dart rename to lib/widgets/chat_page/mention_overlay.dart index d95253d..9858574 100644 --- a/lib/widgets/chat_page/composer/mention_overlay.dart +++ b/lib/widgets/chat_page/mention_overlay.dart @@ -2,7 +2,6 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/members_controller.dart"; import "package:nexus/controllers/rooms_controller.dart"; -import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/models/room.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/loading.dart"; @@ -32,47 +31,55 @@ class MentionOverlay extends ConsumerWidget { color: Theme.of(context).colorScheme.surfaceContainerHigh, padding: EdgeInsets.all(8), child: switch (triggerCharacter) { - "@" => - ref - .watch(MembersController.provider(room)) - .betterWhen( - data: (members) => ListView( - children: - (query.isEmpty - ? members - : members.where( - (member) => - member.userId.toLowerCase().contains( - query.toLowerCase(), - ) == - true || - member.displayName - .toLowerCase() - .contains( - query.toLowerCase(), - ) == - true, - )) - .map( - (member) => ListTile( - leading: AvatarOrHash( - member.avatarUrl, - member.displayName, - ), - title: Text(member.displayName), - subtitle: Text(member.userId), - onTap: () => addTag( - id: "[@${member.displayName}](https://matrix.to/#/${member.userId})", - name: member.userId - .substring(1) - .split(":") - .first, - ), + "@" => Consumer( + builder: (_, ref, _) { + final members = ref.watch(MembersController.provider(room)); + return ListView( + children: + (query.isEmpty + ? members + : members.where( + (member) => + member.stateKey?.toLowerCase().contains( + query.toLowerCase(), + ) == + true || + (member.content["displayname"] as String?) + ?.toLowerCase() + .contains(query.toLowerCase()) == + true, + )) + .map( + (member) => ListTile( + leading: AvatarOrHash( + Uri.tryParse( + member.content["avatar_url"] ?? "", ), - ) - .toList(), - ), - ), + member.content["displayname"] ?? "", + ), + title: Text( + member.content["displayname"] as String? ?? + member.stateKey ?? + "Unknown User", + ), + subtitle: member.stateKey != null + ? Text(member.stateKey!) + : null, + onTap: () => addTag( + id: "[@${member.content["displayname"]}](https://matrix.to/#/${member.stateKey})", + name: + member.stateKey + ?.substring(1) + .split(":") + .first ?? + "Unknown User", + ), + ), + ) + .toList(), + ); + }, + ), "#" => ListView( children: (query.isEmpty diff --git a/lib/widgets/chat_page/wrappers/message_wrapper.dart b/lib/widgets/chat_page/message_wrapper.dart similarity index 73% rename from lib/widgets/chat_page/wrappers/message_wrapper.dart rename to lib/widgets/chat_page/message_wrapper.dart index 1be6c2b..da53be0 100644 --- a/lib/widgets/chat_page/wrappers/message_wrapper.dart +++ b/lib/widgets/chat_page/message_wrapper.dart @@ -1,21 +1,12 @@ import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:nexus/models/room.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; class MessageWrapper extends StatelessWidget { final Message message; final Widget child; - final Room room; final MessageGroupStatus? groupStatus; - const MessageWrapper( - this.message, - this.child, - this.groupStatus, - this.room, { - super.key, - }); + const MessageWrapper(this.message, this.child, this.groupStatus, {super.key}); @override Widget build(BuildContext context) => ClipRRect( @@ -33,7 +24,11 @@ class MessageWrapper extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ groupStatus?.isFirst != false - ? MessageAvatar(message, room, height: 40) + ? AvatarOrHash( + Uri.parse(message.metadata?["avatarUrl"] ?? ""), + height: 40, + message.metadata?["displayName"] ?? "", + ) : SizedBox(width: 40), Expanded( child: Column( @@ -41,9 +36,9 @@ class MessageWrapper extends StatelessWidget { spacing: 4, children: [ if (groupStatus?.isFirst != false) - MessageDisplayname( - message, - room, + Text( + message.metadata?["displayName"] ?? message.authorId, + overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), diff --git a/lib/widgets/chat_page/composer/relation_preview.dart b/lib/widgets/chat_page/relation_preview.dart similarity index 83% rename from lib/widgets/chat_page/composer/relation_preview.dart rename to lib/widgets/chat_page/relation_preview.dart index 7fded20..7aa3ae8 100644 --- a/lib/widgets/chat_page/composer/relation_preview.dart +++ b/lib/widgets/chat_page/relation_preview.dart @@ -2,9 +2,7 @@ import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/models/relation_type.dart"; -import "package:nexus/models/room.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; class RelationPreview extends ConsumerWidget { final Message? relatedMessage; @@ -12,11 +10,8 @@ class RelationPreview extends ConsumerWidget { final VoidCallback onDismiss; final bool shouldMention; final VoidCallback toggleShouldMention; - final Room room; - - const RelationPreview( - this.relatedMessage, { - required this.room, + const RelationPreview({ + required this.relatedMessage, required this.relationType, required this.onDismiss, required this.shouldMention, @@ -41,10 +36,14 @@ class RelationPreview extends ConsumerWidget { "Editing message:", style: TextStyle(fontWeight: FontWeight.bold), ), - MessageAvatar(relatedMessage!, room), - MessageDisplayname( - relatedMessage!, - room, + AvatarOrHash( + Uri.tryParse(relatedMessage?.metadata?["avatarUrl"] ?? ""), + relatedMessage?.metadata?["displayName"]?.toString() ?? "", + height: 16, + ), + Text( + relatedMessage!.metadata?["displayName"] ?? + relatedMessage!.authorId, style: theme.textTheme.labelMedium?.copyWith( fontWeight: FontWeight.bold, ), diff --git a/lib/widgets/chat_page/reply_widget.dart b/lib/widgets/chat_page/reply_widget.dart index b9fa2e1..cd30acc 100644 --- a/lib/widgets/chat_page/reply_widget.dart +++ b/lib/widgets/chat_page/reply_widget.dart @@ -1,15 +1,15 @@ +import "dart:math"; import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/event_controller.dart"; import "package:nexus/controllers/message_controller.dart"; import "package:nexus/helpers/extensions/better_when.dart"; -import "package:nexus/models/configs/message_config.dart"; +import "package:nexus/models/message_config.dart"; import "package:nexus/models/requests/get_event_request.dart"; import "package:nexus/models/room.dart"; +import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/chat_page/html/quoted.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart"; -import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart"; typedef OnTapReply = void Function(Message message)?; @@ -61,28 +61,73 @@ class ReplyWidget extends ConsumerWidget { return SizedBox.shrink(); } + final smallerText = + message is TextMessage && + replyMessage.metadata?["body"] != null + ? replyMessage.metadata!["body"].substring( + 0, + min( + max( + max( + (message as TextMessage) + .text + .length - + (replyMessage + .metadata?["displayName"] + as String) + .length - + 5, + message + .metadata?["displayName"] + .length, + ), + 5, + ), + replyMessage.metadata!["body"].length, + ), + ) + : null; + final replyText = + (smallerText == null || + smallerText.length == + replyMessage + .metadata!["body"] + .length) + ? replyMessage.metadata!["body"] + : "$smallerText..."; + return InkWell( onTap: () => onTapReply?.call(replyMessage), child: Row( mainAxisSize: MainAxisSize.min, spacing: 8, children: [ - MessageAvatar(replyMessage, room), + AvatarOrHash( + Uri.tryParse( + replyMessage.metadata?["avatarUrl"] ?? + "", + ), + replyMessage.metadata?["displayName"] ?? + "", + height: 16, + ), Flexible( - child: MessageDisplayname( - replyMessage, - room, + child: Text( + replyMessage + .metadata?["displayName"] ?? + replyMessage.authorId, style: Theme.of(context) .textTheme .labelMedium ?.copyWith( fontWeight: FontWeight.bold, ), + overflow: TextOverflow.ellipsis, ), ), Flexible( child: Text( - replyMessage.metadata!["body"], + replyText, overflow: TextOverflow.ellipsis, style: Theme.of( context, diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index 6b3839a..839109f 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -13,14 +13,15 @@ import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/show_context_menu.dart"; import "package:nexus/models/relation_type.dart"; import "package:nexus/models/requests/report_request.dart"; -import "package:nexus/widgets/chat_page/composer/chat_box.dart"; +import "package:nexus/widgets/chat_page/chat_box.dart"; import "package:nexus/widgets/chat_page/image_message.dart"; import "package:nexus/widgets/chat_page/member_list.dart"; -import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart"; +import "package:nexus/widgets/chat_page/message_wrapper.dart"; import "package:nexus/widgets/chat_page/room_appbar.dart"; -import "package:nexus/widgets/chat_page/wrappers/text_message_wrapper.dart"; +import "package:nexus/widgets/chat_page/text_message_wrapper.dart"; import "package:nexus/widgets/chat_page/reply_widget.dart"; import "package:nexus/widgets/form_text_input.dart"; +import "package:nexus/widgets/loading.dart"; // import "package:dynamic_polls/dynamic_polls.dart"; class RoomChat extends HookConsumerWidget { @@ -232,7 +233,7 @@ class RoomChat extends HookConsumerWidget { children: getMessageOptions(message), ), builders: Builders( - loadMoreBuilder: (_) => SizedBox.shrink(), + loadMoreBuilder: (_) => Loading(), chatAnimatedListBuilder: (_, itemBuilder) => ChatAnimatedList( @@ -319,7 +320,6 @@ class RoomChat extends HookConsumerWidget { ), ), groupStatus, - room, ), systemMessageBuilder: diff --git a/lib/widgets/chat_page/sidebar.dart b/lib/widgets/chat_page/sidebar.dart index 771a7ae..4642a58 100644 --- a/lib/widgets/chat_page/sidebar.dart +++ b/lib/widgets/chat_page/sidebar.dart @@ -12,8 +12,7 @@ import "package:nexus/widgets/chat_page/room_menu.dart"; import "package:nexus/widgets/form_text_input.dart"; class Sidebar extends HookConsumerWidget { - final bool isDesktop; - const Sidebar({required this.isDesktop, super.key}); + const Sidebar({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -221,12 +220,9 @@ class Sidebar extends HookConsumerWidget { ), ) .toList(), - onDestinationSelected: (value) { - selectedRoomIdNotifier.set( - selectedSpace.children[value].metadata?.id, - ); - if (!isDesktop) Navigator.of(context).pop(); - }, + onDestinationSelected: (value) => selectedRoomIdNotifier.set( + selectedSpace.children[value].metadata?.id, + ), ), ), ), diff --git a/lib/widgets/chat_page/wrappers/text_message_wrapper.dart b/lib/widgets/chat_page/text_message_wrapper.dart similarity index 97% rename from lib/widgets/chat_page/wrappers/text_message_wrapper.dart rename to lib/widgets/chat_page/text_message_wrapper.dart index 41bc01e..9734a34 100644 --- a/lib/widgets/chat_page/wrappers/text_message_wrapper.dart +++ b/lib/widgets/chat_page/text_message_wrapper.dart @@ -3,7 +3,7 @@ import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_link_previewer/flutter_link_previewer.dart"; import "package:nexus/models/room.dart"; import "package:nexus/widgets/chat_page/html/html.dart"; -import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart"; +import "package:nexus/widgets/chat_page/message_wrapper.dart"; import "package:nexus/widgets/chat_page/reply_widget.dart"; class TextMessageWrapper extends StatelessWidget { @@ -109,7 +109,6 @@ class TextMessageWrapper extends StatelessWidget { ), ), groupStatus, - room, ); } } diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index fee47c5..2e0c766 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -7,7 +7,7 @@ project(runner LANGUAGES CXX) set(BINARY_NAME "nexus") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "nexus.federated.Nexus") +set(APPLICATION_ID "nexus.federated.nexus") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 5485b95..f70fb6e 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_system_colors_registrar = @@ -28,4 +29,7 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); + g_autoptr(FlPluginRegistrar) window_size_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin"); + window_size_plugin_register_with_registrar(window_size_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 13ef2de..78dcf40 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST screen_retriever_linux url_launcher_linux window_manager + window_size ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/linux/nexus.federated.Nexus.desktop b/linux/nexus.federated.Nexus.desktop deleted file mode 100644 index d3fa575..0000000 --- a/linux/nexus.federated.Nexus.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Name=Nexus -GenericName=Matrix Client -Comment=A simple and user-friendly Matrix client -Exec=nexus -Icon=nexus -Terminal=false -Type=Application -Categories=Chat;Network;InstantMessaging; \ No newline at end of file diff --git a/linux/nix/devshell.nix b/linux/nix/devshell.nix deleted file mode 100644 index 91ba95a..0000000 --- a/linux/nix/devshell.nix +++ /dev/null @@ -1,41 +0,0 @@ -{ pkgs, lib }: -let - android = pkgs.androidenv.composeAndroidPackages { - toolsVersion = "26.1.1"; - platformToolsVersion = "36.0.1"; - buildToolsVersions = [ - "35.0.0" - "36.0.0" - ]; - cmakeVersions = [ "3.22.1" ]; - platformVersions = [ "36" ]; - abiVersions = [ - "armeabi-v7a" - "arm64-v8a" - ]; - includeNDK = true; - ndkVersions = [ "28.2.13676358" ]; - }; -in -pkgs.mkShell { - packages = with pkgs; [ - go - git - jdk17 - flutter - android.platform-tools - ]; - - env = rec { - LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ]; - LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}"; - CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ]; - - ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk"; - ANDROID_SDK_ROOT = ANDROID_HOME; - JAVA_HOME = pkgs.jdk17; - - TOOLS = "${ANDROID_HOME}/build-tools/${"36.0.0"}"; - GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${TOOLS}/aapt2"; - }; -} diff --git a/linux/nix/pkg/default.nix b/linux/nix/pkg/default.nix deleted file mode 100644 index ae1ddeb..0000000 --- a/linux/nix/pkg/default.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ - lib, - callPackage, - libclang, - flutter, - src, -}: - -flutter.buildFlutterApplication { - pname = "nexus"; - version = "0.1.0"; - inherit src; - - preBuild = '' - cp ${callPackage ./gomuks.nix { inherit src; }}/lib/* . - packageRunCustom nexus generate source/scripts test - packageRun build_runner build - ''; - - env.LIBCLANG_PATH = lib.makeLibraryPath [ libclang ]; - - autoPubspecLock = src + "/pubspec.lock"; - - gitHashes = { - window_size = "sha256-XelNtp7tpZ91QCEcvewVphNUtgQX7xrp5QP0oFo6DgM="; - dynamic_system_colors = "sha256-es6rjMK1drkqZBKYUP77yw/q5+0uLwWOEDOXRawy3Dc="; - flutter_chat_ui = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk="; - flutter_link_previewer = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk="; - }; - - postInstall = '' - install -D assets/icon.svg $out/share/icons/hicolor/scalable/apps/nexus.svg - install -Dm755 linux/nexus.federated.Nexus.desktop -t $out/share/applications - wrapProgram $out/bin/nexus \ - --suffix LD_LIBRARY_PATH : $out/app/nexus/lib - ''; - - meta = { - description = "A simple and user-friendly Matrix client"; - mainProgram = "nexus"; - platforms = lib.platforms.linux; - maintainers = with lib.maintainers; [ quadradical ]; - }; -} diff --git a/linux/nix/pkg/gomuks.nix b/linux/nix/pkg/gomuks.nix deleted file mode 100644 index 1bc92bf..0000000 --- a/linux/nix/pkg/gomuks.nix +++ /dev/null @@ -1,31 +0,0 @@ -{ - src, - buildGoModule, -}: - -buildGoModule (finalAttrs: { - pname = "gomuks-ffi"; - version = "submodule"; - - doCheck = false; - - src = "${src}/gomuks"; - - vendorHash = "sha256-zBDfBZqUoHIfZ0AajZEvSBbskjpFB7yIsomt0KYDo7Y="; - - buildPhase = '' - runHook preBuild - - go build -buildmode=c-shared -o libgomuks.so -tags goolm,noheic ./pkg/ffi - - runHook postBuild - ''; - - installPhase = '' - runHook preInstall - - install -Dm0644 libgomuks.so -t $out/lib - - runHook postInstall - ''; -}) diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc index c702551..58cd859 100644 --- a/linux/runner/my_application.cc +++ b/linux/runner/my_application.cc @@ -43,7 +43,6 @@ static void my_application_activate(GApplication* application) { } } #endif - gtk_widget_set_size_request(GTK_WIDGET(window), 500, 500); if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); diff --git a/nix/android.nix b/nix/android.nix new file mode 100644 index 0000000..f373968 --- /dev/null +++ b/nix/android.nix @@ -0,0 +1,20 @@ +{ + androidenv, +}: +androidenv.composeAndroidPackages { + toolsVersion = "26.1.1"; + platformToolsVersion = "36.0.1"; + buildToolsVersions = [ + "35.0.0" + "36.0.0" + ]; + cmakeVersions = [ "3.22.1" ]; + platformVersions = [ "36" ]; + abiVersions = [ + "armeabi-v7a" + "arm64-v8a" + ]; + includeNDK = true; + ndkVersions = [ "27.0.12077973" ]; + +} diff --git a/pubspec.lock b/pubspec.lock index de807aa..da5de89 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: analyzer_buffer - sha256: "5fcd06b0715ebeee99f03e3f437b3412249969d8d12b191ea8a1d76e42a4e4a1" + sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033 url: "https://pub.dev" source: hosted - version: "0.3.1" + version: "0.1.11" analyzer_plugin: dependency: transitive description: @@ -348,11 +348,10 @@ packages: dynamic_system_colors: dependency: "direct main" description: - path: "." - ref: HEAD - resolved-ref: "3b61760d5e0ac1229eefde5b61247947eede4110" - url: "https://github.com/hasali19/flutter_dynamic_system_colors" - source: git + name: dynamic_system_colors + sha256: "43794e658fa88cbdec9f397dd1afd2eb69b6c9717e99b93b16ba37c3aa3b3a8c" + url: "https://pub.dev" + source: hosted version: "1.8.0" encrypt: dependency: transitive @@ -522,10 +521,10 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2" + sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.1.0" flutter_svg: dependency: "direct main" description: @@ -644,10 +643,10 @@ packages: dependency: "direct main" description: name: hooks_riverpod - sha256: "08527ec06aaef75e4b78694e045ef0cd8346594eaf9cc18b0f866398b07b93b1" + sha256: b880efcd17757af0aa242e5dceac2fb781a014c22a32435a5daa8f17e9d5d8a9 url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.1.0" html: dependency: transitive description: @@ -1052,26 +1051,26 @@ packages: dependency: transitive description: name: riverpod - sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83" + sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.1.0" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: e55bc08c084a424e1bbdc303fe8ea75daafe4269b68fd0e0f6f1678413715b66 + sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0" url: "https://pub.dev" source: hosted - version: "1.0.0-dev.9" + version: "1.0.0-dev.8" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "64e8debf5b719a37d48b9785dd595d34133fdcd84b8fd07157a621c54ab2156f" + sha256: "4d2eb0d19bbe7e3323bd0ce4553b2e6170d161a13914bfdd85a3612329edcb43" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.0" rxdart: dependency: transitive description: @@ -1549,6 +1548,15 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + window_size: + dependency: "direct main" + description: + path: "plugins/window_size" + ref: HEAD + resolved-ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601 + url: "https://github.com/google/flutter-desktop-embedding" + source: git + version: "0.1.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 80c3687..3c0198d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,8 +21,8 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - flutter_riverpod: ^3.3.1 - hooks_riverpod: ^3.3.1 + flutter_riverpod: ^3.0.3 + hooks_riverpod: ^3.0.3 intl: ^0.20.1 fast_immutable_collections: ^11.0.0 path_provider: ^2.1.3 @@ -31,11 +31,13 @@ dependencies: image_picker: ^1.1.2 file_picker: ^10.3.3 path: ^1.9.0 - dynamic_system_colors: - git: - url: https://github.com/hasali19/flutter_dynamic_system_colors + dynamic_system_colors: ^1.8.0 collection: ^1.19.1 window_manager: ^0.5.1 + window_size: + git: + url: https://github.com/google/flutter-desktop-embedding + path: plugins/window_size flutter_chat_core: ^2.0.0 flyer_chat_image_message: ^2.2.2 flyer_chat_system_message: ^2.1.13 @@ -67,7 +69,7 @@ dev_dependencies: custom_lint: ^0.8.0 flutter_lints: ^6.0.0 freezed: ^3.2.3 - riverpod_lint: ^3.1.3 + riverpod_lint: ^3.0.3 flutter_launcher_icons: ^0.14.1 json_serializable: ^6.11.1 @@ -75,7 +77,7 @@ flutter_launcher_icons: ios: true android: true image_path: assets/icon.png - adaptive_icon_background: assets/background.png + adaptive_icon_background: "#000000" adaptive_icon_foreground: assets/foreground.png remove_alpha_ios: true windows: diff --git a/scripts/generate.dart b/scripts/generate.dart index 446a469..b240d98 100644 --- a/scripts/generate.dart +++ b/scripts/generate.dart @@ -3,7 +3,26 @@ import "package:ffigen/ffigen.dart"; import "package:path/path.dart"; void main(List args) async { - final repoDir = Directory.fromUri(Platform.script.resolve("../gomuks")); + final repoDir = Directory.fromUri( + Platform.script.resolve("../src/gomuks/source"), + ); + if (await repoDir.exists()) await repoDir.delete(recursive: true); + await repoDir.create(recursive: true); + + print("Cloning Gomuks repository..."); + final cloneResult = await Process.run("git", [ + "clone", + "--depth", + "1", + "https://mau.dev/gomuks/gomuks", + repoDir.path, + ]); + + if (cloneResult.exitCode != 0) { + throw Exception( + "Failed to clone Gomuks repository: \n${cloneResult.stderr}", + ); + } print("Generating FFI Bindings..."); diff --git a/scripts/generate.sh b/scripts/generate.sh new file mode 100755 index 0000000..6076ab8 --- /dev/null +++ b/scripts/generate.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +pushd "$(dirname "$(readlink -f "$0")")"/.. > /dev/null || exit + +mkdir -p build +touch build/lock +dart scripts/generate.dart +rm build/lock + +popd > /dev/null || exit \ No newline at end of file diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index bde1c28..55fb066 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { DynamicColorPluginCApiRegisterWithRegistrar( @@ -23,4 +24,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); + WindowSizePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowSizePlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7b6b425..9333a2f 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST screen_retriever_windows url_launcher_windows window_manager + window_size ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 3583d23..24405eb 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -89,11 +89,11 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "nexus.federated.Nexus" "\0" + VALUE "CompanyName", "nexus.federated.nexus" "\0" VALUE "FileDescription", "nexus" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "nexus" "\0" - VALUE "LegalCopyright", "Copyright (C) 2025 nexus.federated.Nexus. All rights reserved." "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 nexus.federated.nexus. All rights reserved." "\0" VALUE "OriginalFilename", "nexus.exe" "\0" VALUE "ProductName", "nexus" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0"