Compare commits

..
Author SHA1 Message Date
7b6a4b4181
fix: timeline anchoring 2026-08-10 09:02:17 +02:00
2f3bd7a8ac
fix: timeline pagination 2026-08-10 09:02:17 +02:00
522 changed files with 6569 additions and 28137 deletions

View file

@ -1,28 +0,0 @@
name: "End-to-end tests"
on:
push:
branches: ["main"]
pull_request:
workflow_dispatch:
jobs:
e2e:
runs-on: ["large", "debian-13-x86_64"]
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: Generate code
run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build"
- name: Run end-to-end tests
run: nix run .#e2e-headless

View file

@ -1,39 +1,39 @@
name: "Build APK" name: "Build APK"
on: on:
push: push:
branches: ["main"] branches: ["main"]
tags: ["*"] tags: ["*"]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-apk: build-apk:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Lix GHA Installer Action - name: Lix GHA Installer Action
uses: samueldr/lix-gha-installer-action@v2026-02-22 uses: samueldr/lix-gha-installer-action@v2026-02-22
with: with:
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
- name: Decode keystore - name: Decode keystore
run: echo "$KEYSTORE_CONTENT" | base64 --decode > keystore.jks run: echo "$KEYSTORE_CONTENT" | base64 --decode > keystore.jks
env: env:
KEYSTORE_CONTENT: ${{ secrets.KEYSTORE_CONTENT }} KEYSTORE_CONTENT: ${{ secrets.KEYSTORE_CONTENT }}
- name: Build app - 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 }}" run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build && flutter build apk --release"
env: env:
KEYSTORE_PATH: ../../keystore.jks KEYSTORE_PATH: ../../keystore.jks
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
- name: Upload installer artifact - name: Upload installer artifact
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v6
with: with:
name: APK name: APK
path: build/app/outputs/flutter-apk/app-arm64-v8a-release.apk path: build/app/outputs/flutter-apk/app-release.apk

View file

@ -1,73 +1,37 @@
name: "Build Flatpaks" name: "Build Flatpaks"
on: on:
push: push:
branches: ["main"] branches: ["main"]
tags: ["*"] tags: ["*"]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-flatpak: build-flatpak:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- arch: x86_64 - arch: x86_64
runner: ubuntu-latest runner: ubuntu-latest
- arch: aarch64 - arch: aarch64
runner: ubuntu-24.04-arm runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Lix GHA Installer Action - name: Lix GHA Installer Action
uses: samueldr/lix-gha-installer-action@v2026-02-22 uses: samueldr/lix-gha-installer-action@v2026-02-22
with: with:
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
- name: Build app - name: Build app
run: nix build .#flatpak run: nix build .#flatpak
- name: Upload installer artifact - name: Upload installer artifact
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v6
with: with:
name: flatpak-${{ matrix.arch }} name: flatpak-${{ matrix.arch }}
path: result/nexus.federated.nexus.flatpak 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

View file

@ -19,7 +19,7 @@ jobs:
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: 3.47.4 flutter-version: 3.44.4
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
@ -31,7 +31,7 @@ jobs:
flutter pub get flutter pub get
dart scripts/generate.dart dart scripts/generate.dart
flutter pub run build_runner build 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 # 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. # user's own Apple ID at install time, so no certificate is needed here.

View file

@ -19,29 +19,22 @@ jobs:
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: 3.47.4 flutter-version: 3.44.4
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version-file: gomuks/go.mod go-version-file: gomuks/go.mod
- name: Build App - name: Build app
run: | run: |
flutter pub get flutter pub get
dart scripts/generate.dart dart scripts/generate.dart
flutter pub run build_runner build flutter pub run build_runner build
flutter build macos --dart-define=GIT_COMMIT=${{ github.sha }} flutter build macos --release
- name: Create DMG - name: Upload installer artifact
id: create-dmg
uses: L-Super/create-dmg-actions@28511e988b13ca34096d439159a66d468b8e724f
with:
dmg_name: nexus
src_dir: build/macos/Build/Products/Release/Nexus.app
- name: Upload DMG
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v6
with: with:
name: nexus.dmg name: Nexus.App
path: ${{ steps.create-dmg.outputs.dmg_path }} path: build/macos/Build/Products/Release/Nexus.app

View file

@ -1,74 +1,65 @@
name: "Build EXE" name: "Build EXE"
on: on:
push: push:
branches: ["main"] branches: ["main"]
tags: ["*"] tags: ["*"]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-exe: build-exe:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Set up Flutter - name: Set up Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: 3.47.4 flutter-version: 3.44.4
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version-file: gomuks/go.mod go-version-file: gomuks/go.mod
- name: Setup MSYS2 - name: Setup MSYS2
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
with: with:
msystem: CLANG64 msystem: MINGW64
install: >- install: >-
mingw-w64-x86_64-gcc mingw-w64-x86_64-gcc
- name: Go build - name: Go build
run: | run: |
cd gomuks/pkg/ffi cd gomuks/pkg/ffi
go build -tags goolm,sqlite_fts5 -o ../../../libgomuks.dll -buildmode=c-shared go build -tags goolm,sqlite_fts5 -o ../../../libgomuks.dll -buildmode=c-shared
- name: Build with Flutter - name: Build with Flutter
run: | run: |
flutter pub get flutter pub get
dart scripts/generate.dart dart scripts/generate.dart
flutter pub run build_runner build flutter pub run build_runner build
flutter build windows --dart-define=GIT_COMMIT=${{ github.sha }} flutter build windows --release
- name: Copy MinGW runtime DLLs - name: Copy MinGW runtime DLLs
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cp /mingw64/bin/libgcc_s_seh-1.dll build/windows/x64/runner/Release/ cp /mingw64/bin/libgcc_s_seh-1.dll build/windows/x64/runner/Release/
cp /mingw64/bin/libwinpthread-1.dll build/windows/x64/runner/Release/ cp /mingw64/bin/libwinpthread-1.dll build/windows/x64/runner/Release/
cp /mingw64/bin/libstdc++-6.dll build/windows/x64/runner/Release/ cp /mingw64/bin/libstdc++-6.dll build/windows/x64/runner/Release/
- name: Copy MSVC runtime DLLs - name: Install Inno Setup
shell: powershell run: choco install innosetup -y
run: |
$release = "build/windows/x64/runner/Release"
Copy-Item C:\Windows\System32\MSVCP140.dll $release -Force - name: Build Inno Setup installer
Copy-Item C:\Windows\System32\VCRUNTIME140.dll $release -Force run: iscc windows/installer.iss
Copy-Item C:\Windows\System32\VCRUNTIME140_1.dll $release -Force
- name: Install Inno Setup - name: Upload installer artifact
run: choco install innosetup -y uses: actions/upload-artifact@v6
with:
- name: Build Inno Setup installer name: windows-installer
run: iscc windows/installer.iss path: windows/dist/Nexus-Setup.exe
- name: Upload installer artifact
uses: actions/upload-artifact@v6
with:
name: windows-installer
path: windows/dist/Nexus-Setup.exe

2
.gitignore vendored
View file

@ -42,5 +42,3 @@ password.txt
# Nix # Nix
/result /result
# Widget Preview related
.widget_preview/

2
.gitmodules vendored
View file

@ -1,4 +1,4 @@
[submodule "gomuks"] [submodule "gomuks"]
path = gomuks path = gomuks
url = https://github.com/gomuks/gomuks url = https://github.com/gomuks/gomuks
branch = quad/nexus branch = main

17
.vscode/launch.json vendored
View file

@ -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" }
}
]
}

View file

@ -10,11 +10,7 @@
"msgtype", "msgtype",
"muks", "muks",
"prefs", "prefs",
"recents",
"unban", "unban",
"underride", "unredact"
"unifiedpush",
"unredact",
"webpush"
] ]
} }

View file

@ -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: 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 ```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. We use an object oriented style for controllers, where `provider` is a static member on the controller class. E.g.
```dart ```dart
class MyController(final SomeInputType input) class MyController extends AsyncNotifier<DataThisControllerExposes> {
extends AsyncNotifier<DataThisControllerExposes> { final SomeInputType input;
MyController(this.input);
@override @override
Future<DataThisControllerExposes> build() async { Future<DataThisControllerExposes> build() async {
return input.foo; return input.foo;
} }
static final provider = static final provider =
AsyncNotifierProvider.family<MyController, DataThisControllerExposes, SomeInputType>( AsyncNotifierProvider.family<MyController, DataThisControllerExposes, SomeInputType>(
MyController.new, 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/`. 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)) ### 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. 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/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. 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.

163
README.md
View file

@ -1,5 +1,8 @@
# Nexus Client # Nexus Client
> [!WARNING]
> Nexus Client is still in development, and doesn't support everything needed for daily use.
## Description ## 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 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) | | ![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 .App 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 ## Try it out
If you want to try out Nexus, download the [latest release](https://git.federated.nexus/Nexus/nexus/releases/latest). If you want to try out Nexus, grab one of the following artifacts from CI:
### Nightly Builds
For the newest changes, grab one of the following nightly artifacts, built by CI from the latest commit on `main`.
- [Android APK](https://nightly.link/Henry-Hiles/nexus/workflows/android/main/APK.zip) - [Android APK](https://nightly.link/Henry-Hiles/nexus/workflows/android/main/APK.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) - [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)
Or, use one of the below methods: - [Unsigned MacOS App](https://nightly.link/Henry-Hiles/nexus/workflows/macos/main/Nexus.App.zip)
- [Windows EXE](https://nightly.link/Henry-Hiles/nexus/workflows/windows/main/windows-installer.zip)
### Nightly Flatpak Repo - Flatpak
- [AArch64/Arm64](https://nightly.link/Henry-Hiles/nexus/workflows/flatpak/main/flatpak-aarch64.zip)
Make sure Flatpak is set up on your system, then run: - [x86_64/AMD64](https://nightly.link/Henry-Hiles/nexus/workflows/flatpak/main/flatpak-x86_64.zip)
```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.1";
```
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.
## Build it yourself ## Build it yourself
@ -74,7 +129,7 @@ This installs Nexus system-wide. With `enableNotifications`, it also installs KU
#### Linux #### 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. - On other distros: Install Flutter, Go, Git, Libclang, Libass, MPV, and Glibc. Do not use any Snap packages, they cause various compilation issues.
#### Windows #### 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. 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 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! Thank you Tulir Asokan for making [Gomuks](https://github.com/gomuks/gomuks), and helping us integrate it into Nexus!

22
altstore-source.json Normal file
View file

@ -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": []
}

View file

@ -1,18 +1,12 @@
analyzer: analyzer:
errors: errors:
invalid_annotation_target: ignore invalid_annotation_target: ignore
avoid_print: ignore avoid_print: ignore
exclude: exclude:
- "build/**" - "build/**"
- "**/*.g.dart" - "**/*.g.dart"
- "**/*.freezed.dart" - "**/*.freezed.dart"
- android/**
- ios/**
- web/**
- windows/**
- macos/**
- linux/**
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:
rules: rules:
prefer_double_quotes: true prefer_double_quotes: true

77
android/app/build.gradle Normal file
View file

@ -0,0 +1,77 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = "1"
}
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = "1.0"
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
namespace = "nexus.federated.nexus"
ndkVersion = flutter.ndkVersion
compileSdk = 34
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
defaultConfig {
applicationId = "nexus.federated.nexus"
minSdk = 29
targetSdkVersion flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
signingConfigs {
release {
keyAlias "key"
def storePath = keystoreProperties['path'] ?: System.getenv("KEYSTORE_PATH")
storeFile storePath ? file(storePath) : null
keyPassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD")
storePassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD")
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
debug {
applicationIdSuffix = ".debug"
}
}
}
flutter {
source = "../.."
}

View file

@ -1,84 +0,0 @@
import java.util.Properties
plugins {
id("com.android.application")
id("dev.flutter.flutter-gradle-plugin")
}
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystorePropertiesFile.inputStream().use {
keystoreProperties.load(it)
}
}
android {
namespace = "nexus.federated.nexus"
compileSdk = 36
ndkVersion = flutter.ndkVersion
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
defaultConfig {
applicationId = "nexus.federated.nexus"
minSdk = 29
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
multiDexEnabled = true
}
signingConfigs {
create("release") {
keyAlias = "key"
val storePath =
keystoreProperties["path"]?.toString()
?: System.getenv("KEYSTORE_PATH")
storeFile = storePath?.let { file(it) }
keyPassword =
keystoreProperties["password"]?.toString()
?: System.getenv("KEYSTORE_PASSWORD")
storePassword =
keystoreProperties["password"]?.toString()
?: System.getenv("KEYSTORE_PASSWORD")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
debug {
applicationIdSuffix = ".debug"
}
}
}
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
}
}
flutter {
source = "../.."
}

View file

@ -1,16 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission <uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> android:maxSdkVersion="32" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" /> android:maxSdkVersion="29" />
<!-- Until https://github.com/flutter/flutter/issues/145290 is fixed, we need enableOnBackInvokedCallback as false -->
<application <application
android:enableOnBackInvokedCallback="false"
android:label="Nexus" android:label="Nexus"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -34,13 +30,6 @@
<data android:scheme="nexus.federated.nexus" /> <data android:scheme="nexus.federated.nexus" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="matrix" />
</intent-filter>
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"
@ -60,16 +49,6 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<!-- Needed by pasteboard, for pasting files from the clipboard -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and

View file

@ -0,0 +1,5 @@
package nexus.federated.nexus
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View file

@ -1,40 +0,0 @@
package nexus.federated.nexus
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsAnimation
import io.flutter.embedding.android.FlutterActivity
// TODO: Revert this commit once https://github.com/flutter/flutter/issues/191156 is fixed
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return
window.decorView.setWindowInsetsAnimationCallback(
object : WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
override fun onProgress(
insets: WindowInsets,
runningAnimations: List<WindowInsetsAnimation>,
) = insets
override fun onEnd(animation: WindowInsetsAnimation) {
if ((animation.typeMask and WindowInsets.Type.ime()) != 0) reapplyInsets()
}
},
)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) reapplyInsets()
}
// Posted so it runs after Flutter has applied any insets it deferred.
private fun reapplyInsets() {
val view = findViewById<View>(FLUTTER_VIEW_ID) ?: return
view.post { view.rootWindowInsets?.let(view::onApplyWindowInsets) }
}
}

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>

Some files were not shown because too many files have changed in this diff Show more