Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
12d552e4a6 |
|||
|
3019449e0b |
|||
|
98c3bb1c8e |
|||
|
fbd7551003 |
|||
| d25d24a692 | |||
| 90a5579e7c | |||
| f3787d7f83 | |||
| f3ea9d67a6 | |||
| 606bf78152 |
522 changed files with 6646 additions and 28262 deletions
|
|
@ -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
|
|
||||||
4
.github/workflows/android.yml
vendored
4
.github/workflows/android.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
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 }}
|
||||||
|
|
@ -36,4 +36,4 @@ jobs:
|
||||||
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
|
||||||
36
.github/workflows/flatpak.yml
vendored
36
.github/workflows/flatpak.yml
vendored
|
|
@ -35,39 +35,3 @@ jobs:
|
||||||
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
|
|
||||||
|
|
|
||||||
22
.github/workflows/ios.yml
vendored
22
.github/workflows/ios.yml
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
name: "Build iOS App"
|
name: "Build IPA"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
@ -16,22 +16,24 @@ jobs:
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set up Flutter
|
- name: Lix GHA Installer Action
|
||||||
uses: subosito/flutter-action@v2
|
uses: samueldr/lix-gha-installer-action@v2026-02-22
|
||||||
with:
|
with:
|
||||||
flutter-version: 3.47.4
|
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version-file: gomuks/go.mod
|
|
||||||
|
|
||||||
- name: Build app
|
- name: Build app
|
||||||
run: |
|
run: |
|
||||||
|
nix develop --command bash -c '
|
||||||
|
set -e
|
||||||
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_ROOT="$(dirname "$(dirname "$(readlink -f "$(command -v flutter)")")")"
|
||||||
|
sudo find -L "$FLUTTER_ROOT" -type d -iname "*.framework" -exec chmod -R u+w {} +
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
||||||
33
.github/workflows/macos.yml
vendored
33
.github/workflows/macos.yml
vendored
|
|
@ -16,32 +16,27 @@ jobs:
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set up Flutter
|
- name: Lix GHA Installer Action
|
||||||
uses: subosito/flutter-action@v2
|
uses: samueldr/lix-gha-installer-action@v2026-02-22
|
||||||
with:
|
with:
|
||||||
flutter-version: 3.47.4
|
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Build app
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version-file: gomuks/go.mod
|
|
||||||
|
|
||||||
- name: Build App
|
|
||||||
run: |
|
run: |
|
||||||
|
nix develop --command bash -c '
|
||||||
|
set -e
|
||||||
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 }}
|
|
||||||
|
|
||||||
- name: Create DMG
|
FLUTTER_ROOT="$(dirname "$(dirname "$(readlink -f "$(command -v flutter)")")")"
|
||||||
id: create-dmg
|
sudo find -L "$FLUTTER_ROOT" -type d -iname "*.framework" -exec chmod -R u+w {} +
|
||||||
uses: L-Super/create-dmg-actions@28511e988b13ca34096d439159a66d468b8e724f
|
|
||||||
with:
|
|
||||||
dmg_name: nexus
|
|
||||||
src_dir: build/macos/Build/Products/Release/Nexus.app
|
|
||||||
|
|
||||||
- name: Upload DMG
|
flutter build macos --release
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: Upload installer artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: nexus.dmg
|
name: App
|
||||||
path: ${{ steps.create-dmg.outputs.dmg_path }}
|
path: build/macos/Build/Products/Release/Nexus.app
|
||||||
|
|
|
||||||
15
.github/workflows/windows.yml
vendored
15
.github/workflows/windows.yml
vendored
|
|
@ -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
|
||||||
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
- 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
|
||||||
|
|
||||||
|
|
@ -43,7 +43,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 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}
|
||||||
|
|
@ -52,15 +52,6 @@ jobs:
|
||||||
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
|
|
||||||
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
|
- name: Install Inno Setup
|
||||||
run: choco install innosetup -y
|
run: choco install innosetup -y
|
||||||
|
|
||||||
|
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -42,5 +42,6 @@ password.txt
|
||||||
|
|
||||||
# Nix
|
# Nix
|
||||||
/result
|
/result
|
||||||
# Widget Preview related
|
|
||||||
.widget_preview/
|
# Local IPA builds
|
||||||
|
*.ipa
|
||||||
2
.gitmodules
vendored
2
.gitmodules
vendored
|
|
@ -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
17
.vscode/launch.json
vendored
|
|
@ -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" }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
|
|
@ -10,11 +10,7 @@
|
||||||
"msgtype",
|
"msgtype",
|
||||||
"muks",
|
"muks",
|
||||||
"prefs",
|
"prefs",
|
||||||
"recents",
|
|
||||||
"unban",
|
"unban",
|
||||||
"underride",
|
"unredact"
|
||||||
"unifiedpush",
|
|
||||||
"unredact",
|
|
||||||
"webpush"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,16 @@ 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;
|
||||||
|
|
@ -36,17 +38,11 @@ class MyController(final SomeInputType input)
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
||||||
162
README.md
162
README.md
|
|
@ -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,112 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend.
|
||||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------: |
|
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------: |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
- [ ] Platform Support
|
||||||
|
- [x] Linux
|
||||||
|
- [x] Windows
|
||||||
|
- [x] Android
|
||||||
|
- [ ] MacOS ([#50](https://git.federated.nexus/Nexus/nexus/issues/50))
|
||||||
|
- [ ] iOS
|
||||||
|
- [ ] 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)
|
||||||
|
- Flatpak
|
||||||
Or, use one of the below methods:
|
- [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)
|
||||||
### 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.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,8 +126,8 @@ This installs Nexus system-wide. With `enableNotifications`, it also installs KU
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
- On NixOS: Either use direnv and `direnv allow`, or `nix develop`
|
- With Nix: 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.
|
- Without Nix: Install Flutter, Go, Git, Libclang, Libass, MPV, and Glibc. Do not use any Snap packages, they cause various compilation issues.
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
|
|
@ -153,7 +205,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
22
altstore-source.json
Normal 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": []
|
||||||
|
}
|
||||||
|
|
@ -6,12 +6,6 @@ analyzer:
|
||||||
- "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:
|
||||||
|
|
|
||||||
77
android/app/build.gradle
Normal file
77
android/app/build.gradle
Normal 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 = "../.."
|
||||||
|
}
|
||||||
|
|
@ -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 = "../.."
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package nexus.federated.nexus
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity()
|
||||||
|
|
@ -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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
Loading…
Reference in a new issue