diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index cf64422..a3abae1 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,39 +1,39 @@ name: "Build APK" on: - push: - branches: ["main"] - tags: ["*"] - workflow_dispatch: + push: + branches: ["main"] + tags: ["*"] + workflow_dispatch: jobs: - build-apk: - runs-on: ubuntu-latest + build-apk: + runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - submodules: recursive + 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: 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: 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 --target-platform android-arm64" + env: + KEYSTORE_PATH: ../../keystore.jks + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - - 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 --target-platform android-arm64 --split-per-abi --dart-define=GIT_COMMIT=${{ github.sha }}" - 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-arm64-v8a-release.apk + - 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 index c255c8d..d280bc6 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -1,73 +1,37 @@ name: "Build Flatpaks" on: - push: - branches: ["main"] - tags: ["*"] - workflow_dispatch: + 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 }} + 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 + 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: 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: 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 - - publish: - needs: build-flatpak - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Install flatpak tooling - run: sudo apt-get update && sudo apt-get install -y flatpak ostree - - - name: Download bundles - uses: actions/download-artifact@v7 - with: - pattern: flatpak-* - path: bundles - - - name: Build OSTree repo - run: | - ostree init --repo=repo --mode=archive-z2 - for bundle in bundles/*/nexus.federated.nexus.flatpak; do - flatpak build-import-bundle repo "$bundle" - done - flatpak build-update-repo --prune repo - - - uses: actions/upload-pages-artifact@v4 - with: - path: repo - - - name: Deploy to Pages - id: deployment - uses: actions/deploy-pages@v4 + - 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/ios.yml b/.github/workflows/ios.yml index 28344ea..e14f1bb 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: 3.47.4 + flutter-version: 3.47.0 - name: Set up Go uses: actions/setup-go@v6 @@ -31,7 +31,7 @@ jobs: flutter pub get dart scripts/generate.dart flutter pub run build_runner build - flutter build ios --no-codesign --dart-define=GIT_COMMIT=${{ github.sha }} + flutter build ios --release --no-codesign # Unsigned IPA: SideStore/AltStore re-sign this on-device with the # user's own Apple ID at install time, so no certificate is needed here. diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index b56f16f..53c6746 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: 3.47.4 + flutter-version: 3.47.0 - name: Set up Go uses: actions/setup-go@v6 @@ -31,7 +31,7 @@ jobs: flutter pub get dart scripts/generate.dart flutter pub run build_runner build - flutter build macos --dart-define=GIT_COMMIT=${{ github.sha }} + flutter build macos --release - name: Create DMG id: create-dmg diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index db339b9..3ef3552 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: 3.47.4 + flutter-version: 3.47.0 - name: Set up Go uses: actions/setup-go@v6 @@ -29,7 +29,7 @@ jobs: - name: Setup MSYS2 uses: msys2/setup-msys2@v2 with: - msystem: CLANG64 + msystem: MINGW64 install: >- mingw-w64-x86_64-gcc @@ -43,7 +43,7 @@ jobs: flutter pub get dart scripts/generate.dart flutter pub run build_runner build - flutter build windows --dart-define=GIT_COMMIT=${{ github.sha }} + flutter build windows --release - name: Copy MinGW runtime DLLs shell: msys2 {0} @@ -52,15 +52,6 @@ jobs: cp /mingw64/bin/libwinpthread-1.dll build/windows/x64/runner/Release/ cp /mingw64/bin/libstdc++-6.dll build/windows/x64/runner/Release/ - - name: Copy MSVC runtime DLLs - shell: powershell - run: | - $release = "build/windows/x64/runner/Release" - - Copy-Item C:\Windows\System32\MSVCP140.dll $release -Force - Copy-Item C:\Windows\System32\VCRUNTIME140.dll $release -Force - Copy-Item C:\Windows\System32\VCRUNTIME140_1.dll $release -Force - - name: Install Inno Setup run: choco install innosetup -y diff --git a/.gitignore b/.gitignore index 912ced3..2bec583 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,4 @@ key.properties password.txt # Nix -/result -# Widget Preview related -.widget_preview/ +/result \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 06dd51a..145276a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "gomuks"] path = gomuks url = https://github.com/gomuks/gomuks + branch = main diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index b83039c..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "configurations": [ - { - "name": "Flutter", - "type": "dart", - "request": "launch", - "program": "lib/main.dart" - }, - { - "name": "Flutter (Headless)", - "type": "dart", - "request": "launch", - "program": "lib/main.dart", - "env": { "FLUTTER_HEADLESS": "1" } - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index a5cac8a..2ff533e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,10 +10,7 @@ "msgtype", "muks", "prefs", - "recents", "unban", - "unifiedpush", - "unredact", - "webpush" + "unredact" ] } diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index fa4a0a1..fb69a56 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -21,32 +21,28 @@ See [Effective Dart: Style](https://dart.dev/effective-dart/style) for general r Controllers live in `lib/controllers/` and provide a source that exposes data and logic via Riverpod providers, allowing other parts of the code to watch state changes with ref.watch (`ref.watch(MyController.provider)`), access the current value with ref.read (`ref.read(MyController.provider)`), and run helper methods on those classes using the notifier: ```dart -ref.read(MyController.provider.notifier).helperMethod() +ref.watch(MyController.provider.notifier).helperMethod() ``` We use an object oriented style for controllers, where `provider` is a static member on the controller class. E.g. ```dart -class MyController(final SomeInputType input) - extends AsyncNotifier { +class MyController extends AsyncNotifier { + final SomeInputType input; + MyController(this.input); + @override Future build() async { return input.foo; } static final provider = - AsyncNotifierProvider.family( - MyController.new, - ); + AsyncNotifierProvider.family( + AuthorController.new, + ); } ``` -#### `watch` vs `read` - -Only use `ref.watch` in a `build` method, where you want to rebuild when the value changes. Everywhere else, like callbacks, `useEffect`, and methods on a controller, use `ref.read`. - -Use `.select` to narrow down what you depend on, e.g. `ref.watch(RoomsController.provider.select((rooms) => rooms[roomId]))`. - Providers which are not controllers, e.g. they expose no data, only methods, should instead live in `lib/helpers/`. For an example, see `lib/helpers/launch_helper.dart`. Other, non-provider helpers, like extensions or helper methods can also go in `lib/helpers/`. ### Don't use StatefulWidgets ([Flutter Hooks](https://pub.dev/packages/flutter_hooks)) @@ -65,36 +61,6 @@ When possible, use immutable collections instead of the mutable equivalent. For When possible, we prefer not to create global variables or methods. You can usually replace a global variable with a Riverpod controller, and a global method with an extension method. -I'm also not a big fan of unneeded private methods or variables, but this is contextual. - -### Modern Dart syntax - -We try to use newer Dart language features where they make code shorter: - -- Primary constructors for classes, including widgets, e.g. `class const MemberList(final String roomId, {super.key}) extends HookConsumerWidget`. -- Dot shorthands wherever the type can be inferred, e.g. `.new(...)`, `padding: .all(8)`, `relationType == .reply`. -- Patterns and switch expressions over null checks and casts, e.g. `if (event.content case MessageContent(:final body?))` rather than checking `is` then using `!`. - -### Prefer `map` over inline `for` loops - -When building a list (or set, or map) from another collection, use `.map`, `.expand` and friends rather than a `for` inside the collection literal: - -```dart -// Prefer -children: rooms.map((room) => RoomTile(room)).toList(), - -// Over -children: [for (final room in rooms) RoomTile(room)], -``` - -Regular `for` statements are fine for logic that doesn't just build a collection. - -### Async values and errors - -When rendering an `AsyncValue`, use the `betterWhen` extension, which shows our standard loading and error widgets, or a switch expression if you need more control (e.g. `AsyncData(:final value) || AsyncLoading(:final value?)` to keep showing old data while reloading). - -For futures started from the UI that nothing awaits, like button presses, add `.onError(showError)` so failures are shown to the user instead of being lost. - ## LLM/AI Assisted Contributions LLM generated code is NOT allowed. All contributions should be written by humans. Using LLMs for interacting with others, e.g. for Comments, PRs, etc, is also not allowed. diff --git a/README.md b/README.md index a0c111d..0c18acf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Nexus Client +> [!WARNING] +> Nexus Client is still in development, and doesn't support everything needed for daily use. + ## Description A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. @@ -10,63 +13,115 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend. | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------: | | ![Screenshot of Nexus Client in dark mode, showing users talking, with a sidebar showing rooms and spaces, and another sidebar showing members](./assets/screenshotDark.png) | ![The same screenshot as above, but in light mode](./assets/screenshotLight.png) | +## Progress + +- [x] Platform Support + - [x] Linux + - [x] Windows - Currently has some bugs, due to us not having any developers on Windows. If you want to fix these, get in contact with us though the [Nexus Client Matrix room](https://matrix.to/#/#nexus:federated.nexus). + - [x] Android + - [x] MacOS - Unsigned .dmg only + - [x] iOS - Unsigned .ipa only + - [ ] Web (may not be possible) +- [x] Login (via OAuth) +- [x] Rooms / Spaces + - [x] Displaying and choosing + - [x] Reading, showing unread + - [x] Mark as read button on rooms and spaces + - [ ] Searching + - [ ] Creating (Rooms, Spaces, and DMs) + - [x] Joining + - [x] Parse vias + - [x] Using a text/uri/link + - [x] Plain text + - [x] `matrix:` Uri + - [x] Matrix.to link + - [ ] From space + - [ ] From directory + - [x] Leaving + - [x] Subspaces +- [x] Messages + - [x] Encryption + - [x] Restoring crypto identity from a recovery passphrase/key + - [x] Sending + - [x] Plain text + - [x] HTML/Markdown + - [x] Replies + - [x] Choose ping on/off + - [x] Per message profiles + - [x] Attachments + - [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391) + - [x] Mentions + - [x] Users + - [x] Rooms + - [ ] Inline emoji picker (Putting this here since it'll be implemented the same way as mentions) + - [ ] Custom emojis/stickers + - [ ] GIFs using Gomuks' GIF proxies + - [x] Receiving + - [x] Plain text + - [x] Per message profiles + - [x] HTML + - [x] URL Previews + - [x] Replies + - [x] Viewing + - [ ] Jump to original message + - [x] In loaded timeline + - [ ] Out of loaded timeline + - [x] Edits + - [x] Attachments + - [x] Unencrypted + - [x] Encrypted + - [x] Blurhashing + - [ ] Downloading attachments + - [x] Opening attachments in their own view + - [ ] Polls + - [x] Mentions + - [x] Users + - [x] Clickable + - [x] Rooms + - [ ] Clickable + - [x] Matrix URIs + - [x] Matrix.to links + - [x] Events + - [ ] Render more nicely + - [ ] Clickable + - [x] Custom emojis/stickers + - [x] History loading + - [x] Editing + - [x] Deleting +- [x] Reactions +- [x] Pins + - [x] Displaying + - [x] Pinning/Unpinning +- [ ] Threads +- [x] Profile popouts + - [x] Working actions +- [x] Copy link to: + - [x] Room + - [x] Space + - [x] Message +- [ ] Reporting + - [x] Events + - [ ] Rooms +- [x] Member list + - [x] Sort by power level + - [ ] Colors based off of power level +- [ ] Notifications using UnifiedPush ([#35](https://git.federated.nexus/Nexus/nexus/issues/35)) +- [ ] Group calls using [MSC4195](https://github.com/matrix-org/matrix-spec-proposals/pull/4195) +- [ ] Invites +- [x] Settings + ## Try it out -If you want to try out Nexus, download the [latest release](https://git.federated.nexus/Nexus/nexus/releases/latest). - -### Nightly Builds - -For the newest changes, grab one of the following nightly artifacts, built by CI from the latest commit on `main`. +If you want to try out Nexus, grab one of the following artifacts from CI: - [Android APK](https://nightly.link/Henry-Hiles/nexus/workflows/android/main/APK.zip) +- [Windows EXE](https://nightly.link/Henry-Hiles/nexus/workflows/windows/main/windows-installer.zip) - [Unsigned iOS IPA](https://nightly.link/Henry-Hiles/nexus/workflows/ios/main/Nexus.ipa.zip) - [Unsigned MacOS DMG](https://nightly.link/Henry-Hiles/nexus/workflows/macos/main/nexus.dmg.zip) - [Windows EXE](https://nightly.link/Henry-Hiles/nexus/workflows/windows/main/windows-installer.zip) - -Or, use one of the below methods: - -### Nightly Flatpak Repo - -Make sure Flatpak is set up on your system, then run: - -```bash -flatpak install --user https://nexus.federated.nexus/flatpak/nexus.flatpakref -``` - -This adds the Nexus remote and installs the app for your user only, so no root access is needed (x86_64 and aarch64 are supported). To install system-wide instead, use `--system` in place of `--user`. Updates arrive through `flatpak update` or your software center. - -### NixOS Module - -Add Nexus as a flake input, following `nixpkgs` for less fetches: - -```nix -nexus = { - url = "git+https://git.federated.nexus/Nexus/nexus"; - inputs.nixpkgs.follows = "nixpkgs"; -}; -``` - -This tracks the latest commit on `main`. To stay on a release instead, pin the input to its tag: - -```nix -url = "git+https://git.federated.nexus/Nexus/nexus?ref=refs/tags/v1.0.0"; -``` - -Then import the module and enable it in your NixOS configuration: - -```nix -{ inputs, ... }: -{ - imports = [ inputs.nexus.nixosModules.default ]; - - programs.nexus = { - enable = true; - enableNotifications = true; # optional: push notifications via UnifiedPush - }; -} -``` - -This installs Nexus system-wide. With `enableNotifications`, it also installs KUnifiedPush and registers its systemd and D-Bus services so notifications work. The `package` option lets you override the package if you need to. +- Flatpak + - [AArch64/Arm64](https://nightly.link/Henry-Hiles/nexus/workflows/flatpak/main/flatpak-aarch64.zip) + - [x86_64/AMD64](https://nightly.link/Henry-Hiles/nexus/workflows/flatpak/main/flatpak-x86_64.zip) ## Build it yourself @@ -74,7 +129,7 @@ This installs Nexus system-wide. With `enableNotifications`, it also installs KU #### Linux -- On NixOS: Either use direnv and `direnv allow`, or `nix develop` +- On NixOS: Either use direnv and `direnv allow`, or `nix flake develop` - On other distros: Install Flutter, Go, Git, Libclang, Libass, MPV, and Glibc. Do not use any Snap packages, they cause various compilation issues. #### Windows @@ -153,7 +208,7 @@ Development instructions can be found in [DEVELOPMENT.md](./DEVELOPMENT.md). Join the [Nexus Client Matrix room](https://matrix.to/#/#nexus:federated.nexus) for questions or help with developing or using Nexus Client. -## Credits +# Credits Thank you Hylke Bons (https://planetpeanut.studio) for making the amazing icon for Nexus! Thank you Tulir Asokan for making [Gomuks](https://github.com/gomuks/gomuks), and helping us integrate it into Nexus! diff --git a/altstore-source.json b/altstore-source.json new file mode 100644 index 0000000..4471fb6 --- /dev/null +++ b/altstore-source.json @@ -0,0 +1,22 @@ +{ + "name": "Nexus", + "identifier": "nexus.federated.nexus.source", + "subtitle": "Nexus for iOS", + "description": "Nexus iOS builds for AltStore and SideStore.", + "iconURL": "https://git.federated.nexus/Nexus/nexus/raw/branch/main/assets/icon.png", + "website": "https://git.federated.nexus/Nexus/nexus", + "apps": [ + { + "name": "Nexus", + "bundleIdentifier": "nexus.federated.nexus", + "developerName": "Nexus", + "subtitle": "A Matrix client", + "localizedDescription": "Nexus for iOS. Unsigned builds, resigned at install by AltStore/SideStore.", + "iconURL": "https://git.federated.nexus/Nexus/nexus/raw/branch/main/assets/icon.png", + "category": "social", + "screenshots": [], + "versions": [] + } + ], + "news": [] +} diff --git a/analysis_options.yaml b/analysis_options.yaml index 00df0c4..17adc10 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,6 +2,7 @@ analyzer: errors: invalid_annotation_target: ignore avoid_print: ignore + annotate_overrides: ignore exclude: - "build/**" - "**/*.g.dart" diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 9c0d96c..8a23bb6 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -20,21 +20,16 @@ android { ndkVersion = flutter.ndkVersion compileOptions { - isCoreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } defaultConfig { applicationId = "nexus.federated.nexus" - - minSdk = 29 + minSdk = 29 targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode versionName = flutter.versionName - - multiDexEnabled = true } signingConfigs { @@ -68,11 +63,6 @@ android { } } -dependencies { - implementation("org.unifiedpush.android:embedded-fcm-distributor:3.1.0") - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") -} - kotlin { compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b471fb4..a0a0bcc 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,9 +6,7 @@ - - - - -