forked from Nexus/nexus
Compare commits
59 commits
4c69831c54
...
a1401f20ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
a1401f20ea |
|||
|
f8d6dcead5 |
|||
|
02c79a6d7c |
|||
|
11aef9fc5a |
|||
|
2f39949a2e |
|||
|
166295fdb5 |
|||
|
607cd54e02 |
|||
|
67b96ae731 |
|||
|
ed81b4afa1 |
|||
|
345fa3b5ff |
|||
|
f50fb6ab09 |
|||
|
5601fb27c0 |
|||
|
0d1f7c1819 |
|||
|
4bbf694479 |
|||
|
dd9b9fdc62 |
|||
|
8b7f88cc0b |
|||
|
70793a2f77 |
|||
|
b2c763deef |
|||
|
5c66d35017 |
|||
|
a25f9d2e73 |
|||
|
4a3b7e9a14 |
|||
|
6974e5cc06 |
|||
|
42c32b1b1c |
|||
|
87466f9d05 |
|||
|
ea72654887 |
|||
|
f1af130a63 |
|||
|
e7b772ef66 |
|||
|
28dfe9e981 |
|||
|
d4c98a0cfb |
|||
|
01772b567a |
|||
|
f9927d1eb3 |
|||
|
0d44d10e05 |
|||
|
11ecec5ab3 |
|||
|
b407bbfdee |
|||
|
04b7ab8e2e |
|||
|
0cae2692bc |
|||
|
b387f0755a |
|||
|
840f2fe464 |
|||
|
e5062683e8 |
|||
|
ffe879680d |
|||
|
32dfba178a |
|||
|
fe845e6cd6 |
|||
|
d3e6340b28 |
|||
|
b6e7bb82da |
|||
|
eb503ba647 |
|||
|
cda971a335 |
|||
| e4f666b824 | |||
| cf2150466e | |||
| 23bbbb533e | |||
|
db9fc597f3 |
|||
|
6839f0bdae |
|||
|
6f4ad046b0 |
|||
|
4494705ef9 |
|||
|
237886971c |
|||
|
95a4e03f00 |
|||
|
9054b6b357 |
|||
|
8b056d8ed1 |
|||
|
edbc647a06 |
|||
| 91516c2f20 |
64 changed files with 948 additions and 465 deletions
39
.github/workflows/android.yml
vendored
Normal file
39
.github/workflows/android.yml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
name: "Build APK"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
tags: ["*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-apk:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Lix GHA Installer Action
|
||||||
|
uses: samueldr/lix-gha-installer-action@v2026-02-22
|
||||||
|
with:
|
||||||
|
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
|
||||||
|
|
||||||
|
- name: Decode keystore
|
||||||
|
run: echo "$KEYSTORE_CONTENT" | base64 --decode > keystore.jks
|
||||||
|
env:
|
||||||
|
KEYSTORE_CONTENT: ${{ secrets.KEYSTORE_CONTENT }}
|
||||||
|
|
||||||
|
- name: Build app
|
||||||
|
run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build && flutter build apk --release"
|
||||||
|
env:
|
||||||
|
KEYSTORE_PATH: ../../keystore.jks
|
||||||
|
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Upload installer artifact
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: APK
|
||||||
|
path: build/app/outputs/flutter-apk/app-release.apk
|
||||||
37
.github/workflows/flatpak.yml
vendored
Normal file
37
.github/workflows/flatpak.yml
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
name: "Build Flatpaks"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
tags: ["*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-flatpak:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: x86_64
|
||||||
|
runner: ubuntu-latest
|
||||||
|
- arch: aarch64
|
||||||
|
runner: ubuntu-24.04-arm
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Lix GHA Installer Action
|
||||||
|
uses: samueldr/lix-gha-installer-action@v2026-02-22
|
||||||
|
with:
|
||||||
|
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
|
||||||
|
|
||||||
|
- name: Build app
|
||||||
|
run: nix build .#flatpak
|
||||||
|
|
||||||
|
- name: Upload installer artifact
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: flatpak-${{ matrix.arch }}
|
||||||
|
path: result/nexus.federated.Nexus.flatpak
|
||||||
56
.github/workflows/windows.yml
vendored
56
.github/workflows/windows.yml
vendored
|
|
@ -1,46 +1,50 @@
|
||||||
name: "Build Windows Version"
|
name: "Build EXE"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
tags: ["*"]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-windows:
|
build-exe:
|
||||||
runs-on: "windows-latest"
|
runs-on: windows-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout repository"
|
- name: Checkout repository
|
||||||
uses: "actions/checkout@v4"
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: "Set up Flutter"
|
|
||||||
uses: "subosito/flutter-action@v2"
|
|
||||||
|
|
||||||
- name: "Set up Rust"
|
|
||||||
uses: "dtolnay/rust-toolchain@stable"
|
|
||||||
with:
|
with:
|
||||||
targets: "x86_64-pc-windows-msvc"
|
submodules: recursive
|
||||||
|
|
||||||
- name: "Install Flutter dependencies"
|
- name: Set up Flutter
|
||||||
run: flutter pub get
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: 3.41.5
|
||||||
|
|
||||||
- name: "Run build_runner & build Windows EXE"
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
|
||||||
|
- name: Build with Flutter
|
||||||
run: |
|
run: |
|
||||||
flutter pub run build_runner build --delete-conflicting-outputs
|
flutter pub get
|
||||||
|
dart scripts/generate.dart
|
||||||
|
flutter pub run build_runner build
|
||||||
flutter build windows --release
|
flutter build windows --release
|
||||||
|
|
||||||
- name: "Upload exe zip"
|
- name: Upload exe zip
|
||||||
uses: "actions/upload-artifact@v4"
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "windows-portable"
|
name: windows-portable
|
||||||
path: "build/windows/x64/runner/Release/"
|
path: build/windows/x64/runner/Release/
|
||||||
|
|
||||||
- name: "Install Inno Setup"
|
- name: Install Inno Setup
|
||||||
run: choco install innosetup -y
|
run: choco install innosetup -y
|
||||||
|
|
||||||
- name: "Build Inno Setup installer"
|
- name: Build Inno Setup installer
|
||||||
run: iscc windows/installer.iss
|
run: iscc windows/installer.iss
|
||||||
|
|
||||||
- name: "Upload installer artifact"
|
- name: Upload installer artifact
|
||||||
uses: "actions/upload-artifact@v4"
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: "windows-installer"
|
name: windows-installer
|
||||||
path: "windows/dist/Nexus-Setup.exe"
|
path: windows/dist/Nexus-Setup.exe
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -36,7 +36,9 @@ key.properties
|
||||||
# Generated Files
|
# Generated Files
|
||||||
*.g.dart
|
*.g.dart
|
||||||
*.freezed.dart
|
*.freezed.dart
|
||||||
src/
|
|
||||||
|
|
||||||
# Devel Password
|
# Devel Password
|
||||||
password.txt
|
password.txt
|
||||||
|
|
||||||
|
# Nix
|
||||||
|
/result
|
||||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[submodule "gomuks"]
|
||||||
|
path = gomuks
|
||||||
|
url = https://github.com/zachatrocity/gomuks
|
||||||
|
branch = init-root-dir
|
||||||
22
README.md
22
README.md
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
A simple and user-friendly Matrix client made with Flutter and the Matrix Dart SDK.
|
A simple and user-friendly Matrix client made with Flutter and a Gomuks backend.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
|
|
@ -17,8 +17,8 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S
|
||||||
|
|
||||||
- [ ] New logo
|
- [ ] New logo
|
||||||
- [ ] Make context menus appear as bottom sheets on mobile
|
- [ ] Make context menus appear as bottom sheets on mobile
|
||||||
- [x] Move from the Dart SDK to the Gomuks SDK with Dart bindings: https://git.federated.nexus/Henry-Hiles/nexus/pulls/2
|
- [x] Move from the Dart SDK to the Gomuks Backend with Dart bindings: https://git.federated.nexus/Henry-Hiles/nexus/pulls/2
|
||||||
- [ ] Allow using remote gomuks over websocket
|
- [ ] Allow using remote Gomuks over websocket
|
||||||
- [ ] Platform Support
|
- [ ] Platform Support
|
||||||
- [x] Linux
|
- [x] Linux
|
||||||
- [x] Windows
|
- [x] Windows
|
||||||
|
|
@ -54,6 +54,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S
|
||||||
- [x] HTML/Markdown
|
- [x] HTML/Markdown
|
||||||
- [x] Replies
|
- [x] Replies
|
||||||
- [x] Choose ping on/off
|
- [x] Choose ping on/off
|
||||||
|
- [ ] Per message profiles
|
||||||
- [ ] Attachments
|
- [ ] Attachments
|
||||||
- [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391)
|
- [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391)
|
||||||
- [x] Mentions
|
- [x] Mentions
|
||||||
|
|
@ -64,6 +65,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S
|
||||||
- [ ] GIFs using Gomuks' GIF proxies
|
- [ ] GIFs using Gomuks' GIF proxies
|
||||||
- [x] Recieving
|
- [x] Recieving
|
||||||
- [x] Plain text
|
- [x] Plain text
|
||||||
|
- [x] Per message profiles
|
||||||
- [x] HTML
|
- [x] HTML
|
||||||
- [x] Replies
|
- [x] Replies
|
||||||
- [x] Viewing
|
- [x] Viewing
|
||||||
|
|
@ -128,7 +130,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S
|
||||||
First, clone and open the repo:
|
First, clone and open the repo:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://git.federated.nexus/Henry-Hiles/nexus
|
git clone --recurse-submodules https://git.federated.nexus/Henry-Hiles/nexus
|
||||||
cd nexus
|
cd nexus
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -136,12 +138,12 @@ cd nexus
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
- With Nix: Either use direnv, or `nix flake develop`
|
- With Nix: Either use direnv and `direnv allow`, or `nix flake develop`
|
||||||
- Without Nix: Install Flutter, Go, Olm, Git, Clang, and GLibc.
|
- Without Nix: Install Flutter, Go, Olm, Git, Clang, and GLibc.
|
||||||
|
|
||||||
#### Windows / MacOS
|
#### Windows / MacOS
|
||||||
|
|
||||||
I don't really know. You will need Flutter, Git, Olm, Go, and Visual Studio tools, and otherwise I guess just keep installing stuff until there aren't any errors. I will look into this sometimeTM.
|
I don't really know. You will need Flutter, Git, Go, and Visual Studio tools, and otherwise I guess just keep installing stuff until there aren't any errors. I will look into this sometimeTM.
|
||||||
|
|
||||||
### Set up Flutter
|
### Set up Flutter
|
||||||
|
|
||||||
|
|
@ -151,13 +153,7 @@ Get dependencies:
|
||||||
flutter pub get
|
flutter pub get
|
||||||
```
|
```
|
||||||
|
|
||||||
Get dependencies:
|
Generate Gomuks bindings:
|
||||||
|
|
||||||
```sh
|
|
||||||
flutter pub get
|
|
||||||
```
|
|
||||||
|
|
||||||
Clone Gomuks and generate bindings:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
scripts/generate.sh
|
scripts/generate.sh
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ android {
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
// do we want to update.. eventually?
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "nexus.federated.Nexus"
|
applicationId = "nexus.federated.Nexus"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
|
|
@ -50,7 +55,8 @@ android {
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
keyAlias "key"
|
keyAlias "key"
|
||||||
storeFile keystoreProperties['path'] ? file(keystoreProperties['path']) : file(System.getenv("KEYSTORE_PATH"))
|
def storePath = keystoreProperties['path'] ?: System.getenv("KEYSTORE_PATH")
|
||||||
|
storeFile storePath ? file(storePath) : null
|
||||||
keyPassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD")
|
keyPassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD")
|
||||||
storePassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD")
|
storePassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
android:label="Nexus"
|
android:label="Nexus"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/nexus_round"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:fullBackupContent="false">
|
android:fullBackupContent="false">
|
||||||
<activity
|
<activity
|
||||||
|
|
|
||||||
BIN
assets/background.png
Normal file
BIN
assets/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
62
assets/background.svg
Normal file
62
assets/background.svg
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100mm"
|
||||||
|
height="100mm"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
|
sodipodi:docname="background.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="1.0847363"
|
||||||
|
inkscape:cx="57.156749"
|
||||||
|
inkscape:cy="214.33781"
|
||||||
|
inkscape:window-width="1904"
|
||||||
|
inkscape:window-height="971"
|
||||||
|
inkscape:window-x="35"
|
||||||
|
inkscape:window-y="32"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1" /><defs
|
||||||
|
id="defs1"><linearGradient
|
||||||
|
id="linearGradient10"
|
||||||
|
inkscape:collect="always"><stop
|
||||||
|
style="stop-color:#c7a312;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop10" /><stop
|
||||||
|
style="stop-color:#26a0b3;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop11" /></linearGradient><linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient10"
|
||||||
|
id="linearGradient11"
|
||||||
|
x1="20.031296"
|
||||||
|
y1="32.697563"
|
||||||
|
x2="90.709213"
|
||||||
|
y2="66.3423"
|
||||||
|
gradientUnits="userSpaceOnUse" /></defs><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"><rect
|
||||||
|
style="fill:url(#linearGradient11);fill-opacity:1;stroke:none;stroke-width:7.99999;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
|
||||||
|
id="rect10"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
ry="28.294127" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 MiB |
85
flake.lock
generated
85
flake.lock
generated
|
|
@ -18,17 +18,54 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767640445,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "nixos",
|
"owner": "numtide",
|
||||||
"repo": "nixpkgs",
|
"repo": "flake-utils",
|
||||||
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix2flatpak": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1774604963,
|
||||||
|
"narHash": "sha256-MtAW1FIdirSlUAAO7s1u9auv5y3I6t3uJ+GeEbqiqxI=",
|
||||||
|
"owner": "neobrain",
|
||||||
|
"repo": "nix2flatpak",
|
||||||
|
"rev": "3e04657fbcb49956ac301410b071a7f0b2ad5988",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "neobrain",
|
||||||
|
"repo": "nix2flatpak",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1773389992,
|
||||||
|
"narHash": "sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "c06b4ae3d6599a672a6210b7021d699c351eebda",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
|
|
@ -49,10 +86,42 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767640445,
|
||||||
|
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"nixpkgs": "nixpkgs"
|
"nix2flatpak": "nix2flatpak",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
42
flake.nix
42
flake.nix
|
|
@ -2,8 +2,10 @@
|
||||||
description = "Nexus Flutter Flake";
|
description = "Nexus Flutter Flake";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
|
self.submodules = true;
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
nix2flatpak.url = "github:neobrain/nix2flatpak";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
|
@ -33,36 +35,42 @@
|
||||||
_module.args.pkgs = import nixpkgs {
|
_module.args.pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
config = {
|
config = {
|
||||||
permittedInsecurePackages = [ "olm-3.2.16" ];
|
|
||||||
android_sdk.accept_license = true;
|
android_sdk.accept_license = true;
|
||||||
allowUnfree = true;
|
allowUnfree = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells =
|
packages =
|
||||||
let
|
let
|
||||||
packages = with pkgs; [
|
default = pkgs.callPackage ./linux/nix/pkg {
|
||||||
go
|
src = self;
|
||||||
olm
|
|
||||||
git
|
|
||||||
];
|
|
||||||
|
|
||||||
env = {
|
|
||||||
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ];
|
|
||||||
LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}";
|
|
||||||
CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ];
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = pkgs.mkShell {
|
inherit default;
|
||||||
inherit env;
|
|
||||||
packages = packages ++ [
|
flatpak = inputs.nix2flatpak.lib.${system}.mkFlatpak {
|
||||||
pkgs.flutter
|
appName = "Nexus";
|
||||||
|
developer = "QuadRadical";
|
||||||
|
appId = "nexus.federated.Nexus";
|
||||||
|
package = default;
|
||||||
|
runtime = "org.gnome.Platform/49";
|
||||||
|
permissions = {
|
||||||
|
share = [ "network" ];
|
||||||
|
sockets = [
|
||||||
|
"fallback-x11"
|
||||||
|
"wayland"
|
||||||
];
|
];
|
||||||
|
devices = [ "dri" ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nix = pkgs.mkShell { inherit packages env; };
|
gomuks = pkgs.callPackage ./linux/nix/pkg/gomuks.nix {
|
||||||
|
src = self;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
devShells.default = pkgs.callPackage ./linux/nix/devshell.nix { };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
gomuks
Submodule
1
gomuks
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit daa0ba028e7d89ba9fc7580fc8099348e6145cb3
|
||||||
103
hook/build.dart
103
hook/build.dart
|
|
@ -3,11 +3,12 @@ import "package:hooks/hooks.dart";
|
||||||
import "package:code_assets/code_assets.dart";
|
import "package:code_assets/code_assets.dart";
|
||||||
|
|
||||||
Future<void> main(List<String> args) => build(args, (input, output) async {
|
Future<void> main(List<String> args) => build(args, (input, output) async {
|
||||||
final buildDir = input.packageRoot.resolve("src/");
|
final codeConfig = input.config.code;
|
||||||
if (await File(buildDir.resolve("lock").toFilePath()).exists()) return;
|
final targetOS = codeConfig.targetOS;
|
||||||
|
final targetArch = codeConfig.targetArchitecture;
|
||||||
|
|
||||||
final targetOS = input.config.code.targetOS;
|
|
||||||
String libFileName;
|
String libFileName;
|
||||||
|
Map<String, String> env = {};
|
||||||
switch (targetOS) {
|
switch (targetOS) {
|
||||||
case OS.linux:
|
case OS.linux:
|
||||||
libFileName = "libgomuks.so";
|
libFileName = "libgomuks.so";
|
||||||
|
|
@ -17,24 +18,60 @@ Future<void> main(List<String> args) => build(args, (input, output) async {
|
||||||
break;
|
break;
|
||||||
case OS.windows:
|
case OS.windows:
|
||||||
libFileName = "libgomuks.dll";
|
libFileName = "libgomuks.dll";
|
||||||
|
env = {"GOCACHE": r"C:\Users\runneradmin\AppData\Local\go-build"};
|
||||||
|
break;
|
||||||
|
case OS.android:
|
||||||
|
libFileName = "libgomuks.so";
|
||||||
|
|
||||||
|
final targetNdkApi = codeConfig.android.targetNdkApi;
|
||||||
|
|
||||||
|
final ndkHome =
|
||||||
|
Platform.environment["ANDROID_NDK_HOME"] ??
|
||||||
|
Platform.environment["ANDROID_NDK_ROOT"] ??
|
||||||
|
Platform.environment["NDK_HOME"] ??
|
||||||
|
await _findNdkFromSdk();
|
||||||
|
if (ndkHome == null) {
|
||||||
|
throw Exception(
|
||||||
|
"Could not find Android NDK. Set ANDROID_NDK_HOME or install via sdkmanager.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final hostTag = _ndkHostTag();
|
||||||
|
final (goArch, ccTriple) = _androidArch(targetArch);
|
||||||
|
final cc =
|
||||||
|
"$ndkHome/toolchains/llvm/prebuilt/$hostTag/bin/$ccTriple$targetNdkApi-clang";
|
||||||
|
|
||||||
|
env = {"CGO_ENABLED": "1", "GOOS": "android", "GOARCH": goArch, "CC": cc};
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw UnsupportedError("Unsupported OS: $targetOS");
|
throw UnsupportedError("Unsupported OS: $targetOS");
|
||||||
}
|
}
|
||||||
|
|
||||||
final gomuksBuildDir = buildDir.resolve("gomuks/");
|
var libFile = input.packageRoot.resolve(libFileName);
|
||||||
final libFile = gomuksBuildDir.resolve(libFileName);
|
final gomuksBuildDir = input.packageRoot.resolve("gomuks/");
|
||||||
|
|
||||||
print("Building Gomuks shared library $libFileName from source...");
|
if (!(await File.fromUri(libFile).exists())) {
|
||||||
final result = await Process.run("go", [
|
final buildDir = input.packageRoot.resolve("build/");
|
||||||
"build",
|
libFile = buildDir.resolve("${targetArch.name}/$libFileName");
|
||||||
"-o",
|
|
||||||
libFile.path,
|
// goheif/dav1d supported on Android would need to fix upstream
|
||||||
"-buildmode=c-shared",
|
final tags = targetOS == OS.android ? "goolm,noheic" : "goolm";
|
||||||
], workingDirectory: gomuksBuildDir.resolve("source/pkg/ffi/").toFilePath());
|
|
||||||
|
print(
|
||||||
|
"Building Gomuks shared library $libFileName (${targetOS.name}/${targetArch.name}) from source...",
|
||||||
|
);
|
||||||
|
final result = await Process.run(
|
||||||
|
"go",
|
||||||
|
["build", "-tags", tags, "-o", libFile.path, "-buildmode=c-shared"],
|
||||||
|
workingDirectory: gomuksBuildDir.resolve("pkg/ffi/").toFilePath(),
|
||||||
|
environment: env.isNotEmpty ? env : null,
|
||||||
|
);
|
||||||
|
|
||||||
if (result.exitCode != 0) {
|
if (result.exitCode != 0) {
|
||||||
throw Exception("Failed to build Gomuks shared library\n${result.stderr}");
|
throw Exception(
|
||||||
|
"Failed to build Gomuks shared library\n${result.stderr}",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final generatedFile = "src/third_party/gomuks.g.dart";
|
final generatedFile = "src/third_party/gomuks.g.dart";
|
||||||
|
|
@ -52,3 +89,43 @@ Future<void> main(List<String> args) => build(args, (input, output) async {
|
||||||
..dependencies.add(gomuksBuildDir);
|
..dependencies.add(gomuksBuildDir);
|
||||||
print("Done!");
|
print("Done!");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<String?> _findNdkFromSdk() async {
|
||||||
|
// pretty sure this wont be needed with nix, i'll get this removed
|
||||||
|
final androidHome =
|
||||||
|
Platform.environment["ANDROID_HOME"] ??
|
||||||
|
Platform.environment["ANDROID_SDK_ROOT"];
|
||||||
|
if (androidHome == null) return null;
|
||||||
|
final ndkDir = Directory("$androidHome/ndk");
|
||||||
|
if (!await ndkDir.exists()) return null;
|
||||||
|
final versions = await ndkDir.list().toList();
|
||||||
|
if (versions.isEmpty) return null;
|
||||||
|
versions.sort((a, b) => a.path.compareTo(b.path));
|
||||||
|
return versions.last.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _ndkHostTag() {
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
return "darwin-x86_64";
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
return "linux-x86_64";
|
||||||
|
} else if (Platform.isWindows) {
|
||||||
|
return "windows-x86_64";
|
||||||
|
}
|
||||||
|
throw UnsupportedError("Unsupported host platform for Android NDK");
|
||||||
|
}
|
||||||
|
|
||||||
|
(String goArch, String ccTriple) _androidArch(Architecture arch) {
|
||||||
|
switch (arch) {
|
||||||
|
case Architecture.arm64:
|
||||||
|
return ("arm64", "aarch64-linux-android");
|
||||||
|
case Architecture.arm:
|
||||||
|
return ("arm", "armv7a-linux-androideabi");
|
||||||
|
case Architecture.x64:
|
||||||
|
return ("amd64", "x86_64-linux-android");
|
||||||
|
case Architecture.ia32:
|
||||||
|
return ("386", "i686-linux-android");
|
||||||
|
default:
|
||||||
|
throw UnsupportedError("Unsupported Android architecture: $arch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -387,7 +387,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus;
|
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
@ -519,7 +519,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus;
|
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
|
@ -545,7 +545,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus;
|
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
|
||||||
44
lib/controllers/author_controller.dart
Normal file
44
lib/controllers/author_controller.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import "dart:async";
|
||||||
|
import "package:collection/collection.dart";
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/members_controller.dart";
|
||||||
|
import "package:nexus/models/configs/author_config.dart";
|
||||||
|
import "package:nexus/models/membership.dart";
|
||||||
|
|
||||||
|
class AuthorController extends AsyncNotifier<Membership> {
|
||||||
|
final AuthorConfig config;
|
||||||
|
AuthorController(this.config);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Membership> build() async {
|
||||||
|
var member = await ref.watch(
|
||||||
|
MembersController.provider(config.room).selectAsync(
|
||||||
|
(value) => value.firstWhereOrNull(
|
||||||
|
(membership) => membership.userId == config.message.authorId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final pmp = config.message.metadata?["pmp"] == null
|
||||||
|
? null
|
||||||
|
: Membership.fromContent(
|
||||||
|
IMap(config.message.metadata?["pmp"]),
|
||||||
|
config.message.authorId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Membership(
|
||||||
|
avatarUrl: pmp?.avatarUrl ?? member?.avatarUrl,
|
||||||
|
displayName:
|
||||||
|
pmp?.displayName ??
|
||||||
|
member?.displayName ??
|
||||||
|
config.message.authorId.substring(1).split(":").first,
|
||||||
|
userId: config.message.authorId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider = AsyncNotifierProvider.family
|
||||||
|
.autoDispose<AuthorController, Membership, AuthorConfig>(
|
||||||
|
AuthorController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import "dart:developer";
|
import "dart:developer";
|
||||||
import "dart:ffi";
|
import "dart:ffi";
|
||||||
|
import "dart:io";
|
||||||
import "dart:isolate";
|
import "dart:isolate";
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
|
@ -31,11 +32,20 @@ import "package:nexus/models/sync_data.dart";
|
||||||
import "package:nexus/models/sync_status.dart";
|
import "package:nexus/models/sync_status.dart";
|
||||||
import "package:nexus/src/third_party/gomuks.g.dart";
|
import "package:nexus/src/third_party/gomuks.g.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:path_provider/path_provider.dart";
|
||||||
|
|
||||||
class ClientController extends AsyncNotifier<int> {
|
class ClientController extends AsyncNotifier<int> {
|
||||||
@override
|
@override
|
||||||
Future<int> build() async {
|
Future<int> build() async {
|
||||||
final handle = await Isolate.run(GomuksInit);
|
final Pointer<Char> root;
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
final dir = await getApplicationSupportDirectory();
|
||||||
|
root = "${dir.path}/gomuks".toNativeUtf8().cast();
|
||||||
|
} else {
|
||||||
|
root = nullptr.cast();
|
||||||
|
}
|
||||||
|
|
||||||
|
final handle = GomuksInit(root);
|
||||||
|
|
||||||
final callable =
|
final callable =
|
||||||
NativeCallable<
|
NativeCallable<
|
||||||
|
|
@ -143,12 +153,12 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
Future<void> sendMessage(SendMessageRequest request) =>
|
Future<void> sendMessage(SendMessageRequest request) =>
|
||||||
_sendCommand("send_message", request.toJson());
|
_sendCommand("send_message", request.toJson());
|
||||||
|
|
||||||
Future<bool> verify(String recoveryKey) async {
|
Future<String?> verify(String recoveryKey) async {
|
||||||
try {
|
try {
|
||||||
await _sendCommand("verify", {"recovery_key": recoveryKey});
|
await _sendCommand("verify", {"recovery_key": recoveryKey});
|
||||||
return true;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return error.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,12 +228,12 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> login(LoginRequest login) async {
|
Future<String?> login(LoginRequest login) async {
|
||||||
try {
|
try {
|
||||||
await _sendCommand("login", login.toJson());
|
await _sendCommand("login", login.toJson());
|
||||||
return true;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return error.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,39 @@
|
||||||
import "package:collection/collection.dart";
|
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/models/event.dart";
|
import "package:nexus/controllers/client_controller.dart";
|
||||||
|
import "package:nexus/models/membership.dart";
|
||||||
|
import "package:nexus/models/requests/get_room_state_request.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
|
|
||||||
class MembersController extends Notifier<IList<Event>> {
|
class MembersController extends AsyncNotifier<IList<Membership>> {
|
||||||
final Room room;
|
final Room room;
|
||||||
MembersController(this.room);
|
MembersController(this.room);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
IList<Event> build() => (room.state["m.room.member"]?.values ?? [])
|
Future<IList<Membership>> build() async {
|
||||||
.map(
|
if (room.metadata == null) return const IList.empty();
|
||||||
(eventRowId) =>
|
|
||||||
room.events.firstWhereOrNull((event) => event.rowId == eventRowId),
|
|
||||||
)
|
|
||||||
.nonNulls
|
|
||||||
.where((member) => member.content["membership"] == "join")
|
|
||||||
.toIList();
|
|
||||||
|
|
||||||
static final provider = NotifierProvider.family
|
final state = await ref
|
||||||
.autoDispose<MembersController, IList<Event>, Room>(
|
.watch(ClientController.provider.notifier)
|
||||||
|
.getRoomState(
|
||||||
|
GetRoomStateRequest(
|
||||||
|
roomId: room.metadata!.id,
|
||||||
|
fetchMembers: room.metadata!.hasMemberList == false,
|
||||||
|
includeMembers: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return state.nonNulls
|
||||||
|
.where((member) => member.content["membership"] == "join")
|
||||||
|
.map(
|
||||||
|
(membership) =>
|
||||||
|
Membership.fromContent(membership.content, membership.stateKey!),
|
||||||
|
)
|
||||||
|
.toIList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
AsyncNotifierProvider.family<MembersController, IList<Membership>, Room>(
|
||||||
MembersController.new,
|
MembersController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@ import "package:collection/collection.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/client_state_controller.dart";
|
import "package:nexus/controllers/client_state_controller.dart";
|
||||||
import "package:nexus/controllers/members_controller.dart";
|
|
||||||
import "package:nexus/helpers/extensions/mxc_to_https.dart";
|
import "package:nexus/helpers/extensions/mxc_to_https.dart";
|
||||||
import "package:nexus/models/message_config.dart";
|
import "package:nexus/models/configs/message_config.dart";
|
||||||
|
|
||||||
class MessageController extends AsyncNotifier<Message?> {
|
class MessageController extends AsyncNotifier<Message?> {
|
||||||
final MessageConfig config;
|
final MessageConfig config;
|
||||||
|
|
@ -27,12 +26,6 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
|
|
||||||
if (!ref.mounted) return null;
|
if (!ref.mounted) return null;
|
||||||
|
|
||||||
final members = ref.read(MembersController.provider(config.room));
|
|
||||||
final author = members.firstWhereOrNull(
|
|
||||||
(member) => member.stateKey == event.authorId,
|
|
||||||
);
|
|
||||||
if (!ref.mounted) return null;
|
|
||||||
|
|
||||||
final content = (event.decrypted ?? event.content);
|
final content = (event.decrypted ?? event.content);
|
||||||
final type = (config.event.decryptedType ?? config.event.type);
|
final type = (config.event.decryptedType ?? config.event.type);
|
||||||
final newContent = content["m.new_content"] as Map?;
|
final newContent = content["m.new_content"] as Map?;
|
||||||
|
|
@ -52,14 +45,11 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
"timelineId": event.timelineRowId,
|
"timelineId": event.timelineRowId,
|
||||||
"big": event.localContent?.bigEmoji == true,
|
"big": event.localContent?.bigEmoji == true,
|
||||||
"eventType": type,
|
"eventType": type,
|
||||||
"avatarUrl": author?.content["avatar_url"],
|
"pmp": event.content["com.beeper.per_message_profile"],
|
||||||
"editSource":
|
"editSource":
|
||||||
event.localContent?.editSource ??
|
event.localContent?.editSource ??
|
||||||
newContent?["body"] ??
|
newContent?["body"] ??
|
||||||
content["body"],
|
content["body"],
|
||||||
"displayName": author?.content["displayname"]?.isNotEmpty == true
|
|
||||||
? author?.content["displayname"]
|
|
||||||
: event.authorId.substring(1).split(":")[0],
|
|
||||||
"txnId": config.event.transactionId,
|
"txnId": config.event.transactionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/message_controller.dart";
|
import "package:nexus/controllers/message_controller.dart";
|
||||||
import "package:nexus/models/message_config.dart";
|
import "package:nexus/models/configs/message_config.dart";
|
||||||
import "package:nexus/models/messages_config.dart";
|
import "package:nexus/models/configs/messages_config.dart";
|
||||||
|
|
||||||
class MessagesController extends AsyncNotifier<IList<Message>> {
|
class MessagesController extends AsyncNotifier<IList<Message>> {
|
||||||
final MessagesConfig config;
|
final MessagesConfig config;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import "dart:async";
|
import "dart:async";
|
||||||
|
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
|
@ -11,8 +10,8 @@ import "package:nexus/controllers/message_controller.dart";
|
||||||
import "package:nexus/controllers/messages_controller.dart";
|
import "package:nexus/controllers/messages_controller.dart";
|
||||||
import "package:nexus/controllers/new_events_controller.dart";
|
import "package:nexus/controllers/new_events_controller.dart";
|
||||||
import "package:nexus/controllers/rooms_controller.dart";
|
import "package:nexus/controllers/rooms_controller.dart";
|
||||||
import "package:nexus/models/message_config.dart";
|
import "package:nexus/models/configs/messages_config.dart";
|
||||||
import "package:nexus/models/messages_config.dart";
|
import "package:nexus/models/configs/message_config.dart";
|
||||||
import "package:nexus/models/requests/get_room_state_request.dart";
|
import "package:nexus/models/requests/get_room_state_request.dart";
|
||||||
import "package:nexus/models/requests/paginate_request.dart";
|
import "package:nexus/models/requests/paginate_request.dart";
|
||||||
import "package:nexus/models/requests/redact_event_request.dart";
|
import "package:nexus/models/requests/redact_event_request.dart";
|
||||||
|
|
@ -31,11 +30,7 @@ class RoomChatController extends AsyncNotifier<InMemoryChatController> {
|
||||||
if (room == null) return InMemoryChatController();
|
if (room == null) return InMemoryChatController();
|
||||||
|
|
||||||
final state = await client.getRoomState(
|
final state = await client.getRoomState(
|
||||||
GetRoomStateRequest(
|
GetRoomStateRequest(roomId: roomId),
|
||||||
roomId: roomId,
|
|
||||||
fetchMembers: room.metadata?.hasMemberList == false,
|
|
||||||
includeMembers: true,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ref
|
ref
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ class RoomsController extends Notifier<IMap<String, Room>> {
|
||||||
return acc.add(
|
return acc.add(
|
||||||
roomId,
|
roomId,
|
||||||
existing?.copyWith(
|
existing?.copyWith(
|
||||||
|
hasMore: incoming.hasMore,
|
||||||
metadata: incoming.metadata ?? existing.metadata,
|
metadata: incoming.metadata ?? existing.metadata,
|
||||||
events: events!,
|
events: events!,
|
||||||
state: incoming.state.entries.fold(
|
state: incoming.state.entries.fold(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ extension JoinRoomWithSnackbars on ClientController {
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
) async {
|
) async {
|
||||||
final roomIdOrAlias = roomAlias.mention ?? roomAlias;
|
final roomIdOrAlias = roomAlias.mention ?? roomAlias;
|
||||||
|
// TODO: Parse vias properly
|
||||||
|
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import "package:nexus/widgets/loading.dart";
|
||||||
import "package:window_manager/window_manager.dart";
|
import "package:window_manager/window_manager.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:dynamic_system_colors/dynamic_system_colors.dart";
|
import "package:dynamic_system_colors/dynamic_system_colors.dart";
|
||||||
import "package:window_size/window_size.dart";
|
|
||||||
|
|
||||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
|
@ -59,14 +58,11 @@ void showError(Object error, [StackTrace? stackTrace]) {
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||||
await windowManager.ensureInitialized();
|
await windowManager.ensureInitialized();
|
||||||
await windowManager.waitUntilReadyToShow(
|
await windowManager.waitUntilReadyToShow(
|
||||||
WindowOptions(titleBarStyle: TitleBarStyle.hidden),
|
WindowOptions(titleBarStyle: TitleBarStyle.hidden),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Platform.isLinux) {
|
|
||||||
setWindowMinSize(const Size.square(500));
|
|
||||||
} else {
|
|
||||||
await windowManager.setMinimumSize(Size.square(500));
|
await windowManager.setMinimumSize(Size.square(500));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
14
lib/models/configs/author_config.dart
Normal file
14
lib/models/configs/author_config.dart
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
import "package:nexus/models/room.dart";
|
||||||
|
part "author_config.freezed.dart";
|
||||||
|
part "author_config.g.dart";
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class AuthorConfig with _$AuthorConfig {
|
||||||
|
const factory AuthorConfig({required Message message, required Room room}) =
|
||||||
|
_AuthorConfig;
|
||||||
|
|
||||||
|
factory AuthorConfig.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$AuthorConfigFromJson(json);
|
||||||
|
}
|
||||||
22
lib/models/membership.dart
Normal file
22
lib/models/membership.dart
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
part "membership.freezed.dart";
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class Membership with _$Membership {
|
||||||
|
const Membership._();
|
||||||
|
const factory Membership({
|
||||||
|
required Uri? avatarUrl,
|
||||||
|
required String displayName,
|
||||||
|
required String userId,
|
||||||
|
}) = _Membership;
|
||||||
|
|
||||||
|
factory Membership.fromContent(
|
||||||
|
IMap<String, dynamic> content,
|
||||||
|
String userId,
|
||||||
|
) => Membership(
|
||||||
|
avatarUrl: Uri.tryParse(content["avatar_url"] ?? ""),
|
||||||
|
userId: userId,
|
||||||
|
displayName: content["displayname"] ?? userId.substring(1).split(":").first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ part "get_room_state_request.g.dart";
|
||||||
abstract class GetRoomStateRequest with _$GetRoomStateRequest {
|
abstract class GetRoomStateRequest with _$GetRoomStateRequest {
|
||||||
const factory GetRoomStateRequest({
|
const factory GetRoomStateRequest({
|
||||||
required String roomId,
|
required String roomId,
|
||||||
required bool fetchMembers,
|
@Default(false) bool fetchMembers,
|
||||||
@Default(false) bool includeMembers,
|
@Default(false) bool includeMembers,
|
||||||
}) = _GetRoomStateRequest;
|
}) = _GetRoomStateRequest;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class ChatPage extends ConsumerWidget {
|
||||||
body: Builder(
|
body: Builder(
|
||||||
builder: (context) => Row(
|
builder: (context) => Row(
|
||||||
children: [
|
children: [
|
||||||
if (isDesktop) Sidebar(),
|
if (isDesktop) Sidebar(isDesktop: isDesktop),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RoomChat(
|
child: RoomChat(
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
|
|
@ -26,7 +26,7 @@ class ChatPage extends ConsumerWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
drawer: isDesktop ? null : Sidebar(),
|
drawer: isDesktop ? null : Sidebar(isDesktop: isDesktop),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ class LoginPage extends HookConsumerWidget {
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
final succeeded = await client.login(
|
final error = await client.login(
|
||||||
LoginRequest(
|
LoginRequest(
|
||||||
username: username.text,
|
username: username.text,
|
||||||
password: password.text,
|
password: password.text,
|
||||||
|
|
@ -183,11 +183,11 @@ class LoginPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!succeeded && context.mounted) {
|
if (error != null && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
"Login failed. Is your password right?",
|
"Login failed. Is your password right?\nError: $error",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.onErrorContainer,
|
color: theme.colorScheme.onErrorContainer,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import "package:flutter/material.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:nexus/controllers/client_controller.dart";
|
import "package:nexus/controllers/client_controller.dart";
|
||||||
|
import "package:nexus/widgets/appbar.dart";
|
||||||
import "package:nexus/widgets/form_text_input.dart";
|
import "package:nexus/widgets/form_text_input.dart";
|
||||||
|
|
||||||
class VerifyPage extends HookConsumerWidget {
|
class VerifyPage extends HookConsumerWidget {
|
||||||
|
|
@ -11,7 +12,9 @@ class VerifyPage extends HookConsumerWidget {
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final passphraseController = useTextEditingController();
|
final passphraseController = useTextEditingController();
|
||||||
final isVerifying = useState(false);
|
final isVerifying = useState(false);
|
||||||
return AlertDialog(
|
return Scaffold(
|
||||||
|
appBar: Appbar(),
|
||||||
|
body: AlertDialog(
|
||||||
title: Text("Verify"),
|
title: Text("Verify"),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -48,12 +51,12 @@ class VerifyPage extends HookConsumerWidget {
|
||||||
|
|
||||||
isVerifying.value = true;
|
isVerifying.value = true;
|
||||||
|
|
||||||
final success = await ref
|
final error = await ref
|
||||||
.watch(ClientController.provider.notifier)
|
.watch(ClientController.provider.notifier)
|
||||||
.verify(passphraseController.text);
|
.verify(passphraseController.text);
|
||||||
|
|
||||||
snackbar.close();
|
snackbar.close();
|
||||||
if (!success) {
|
if (error != null) {
|
||||||
isVerifying.value = false;
|
isVerifying.value = false;
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
scaffoldMessenger.showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
|
|
@ -62,7 +65,7 @@ class VerifyPage extends HookConsumerWidget {
|
||||||
context,
|
context,
|
||||||
).colorScheme.errorContainer,
|
).colorScheme.errorContainer,
|
||||||
content: Text(
|
content: Text(
|
||||||
"Verification failed. Is your passphrase correct?",
|
"Verification failed. Is your passphrase correct?\nError: $error",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
|
|
@ -77,6 +80,7 @@ class VerifyPage extends HookConsumerWidget {
|
||||||
child: Text("Verify"),
|
child: Text("Verify"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,14 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onDoubleTap: maximize,
|
|
||||||
onPanStart: (_) => windowManager.startDragging(),
|
onPanStart: (_) => windowManager.startDragging(),
|
||||||
child: AppBar(
|
child: AppBar(
|
||||||
leading: leading,
|
leading: leading,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
scrolledUnderElevation: scrolledUnderElevation,
|
scrolledUnderElevation: scrolledUnderElevation,
|
||||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
|
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
title: title,
|
title: IgnorePointer(child: title),
|
||||||
|
flexibleSpace: GestureDetector(onDoubleTap: maximize),
|
||||||
actions: [
|
actions: [
|
||||||
...actions,
|
...actions,
|
||||||
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
if (!(Platform.isAndroid || Platform.isIOS)) ...[
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import "dart:io";
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter/services.dart";
|
import "package:flutter/services.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
|
@ -8,8 +7,8 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:nexus/controllers/room_chat_controller.dart";
|
import "package:nexus/controllers/room_chat_controller.dart";
|
||||||
import "package:nexus/models/relation_type.dart";
|
import "package:nexus/models/relation_type.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
import "package:nexus/widgets/chat_page/mention_overlay.dart";
|
import "package:nexus/widgets/chat_page/composer/mention_overlay.dart";
|
||||||
import "package:nexus/widgets/chat_page/relation_preview.dart";
|
import "package:nexus/widgets/chat_page/composer/relation_preview.dart";
|
||||||
|
|
||||||
class ChatBox extends HookConsumerWidget {
|
class ChatBox extends HookConsumerWidget {
|
||||||
final Message? relatedMessage;
|
final Message? relatedMessage;
|
||||||
|
|
@ -55,20 +54,15 @@ class ChatBox extends HookConsumerWidget {
|
||||||
|
|
||||||
final node = useFocusNode(
|
final node = useFocusNode(
|
||||||
onKeyEvent: (_, event) {
|
onKeyEvent: (_, event) {
|
||||||
if (event is KeyDownEvent && !Platform.isAndroid && !Platform.isIOS) {
|
if (event is KeyDownEvent &&
|
||||||
if (event.logicalKey == LogicalKeyboardKey.enter &&
|
event.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
!HardwareKeyboard.instance.isShiftPressed) {
|
|
||||||
send();
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
|
||||||
onDismiss();
|
onDismiss();
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
},
|
},
|
||||||
)..requestFocus();
|
);
|
||||||
|
|
||||||
final style = TextStyle(
|
final style = TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
|
@ -86,10 +80,11 @@ class ChatBox extends HookConsumerWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
RelationPreview(
|
RelationPreview(
|
||||||
|
relatedMessage,
|
||||||
|
room: room,
|
||||||
shouldMention: shouldMention.value,
|
shouldMention: shouldMention.value,
|
||||||
toggleShouldMention: () =>
|
toggleShouldMention: () =>
|
||||||
shouldMention.value = !shouldMention.value,
|
shouldMention.value = !shouldMention.value,
|
||||||
relatedMessage: relatedMessage,
|
|
||||||
relationType: relationType,
|
relationType: relationType,
|
||||||
onDismiss: onDismiss,
|
onDismiss: onDismiss,
|
||||||
),
|
),
|
||||||
|
|
@ -155,7 +150,9 @@ class ChatBox extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
controller: controller.value,
|
controller: controller.value,
|
||||||
key: key,
|
key: key,
|
||||||
autofocus: true,
|
// TODO: Setting for send on enter on / off
|
||||||
|
onFieldSubmitted: (_) => send(),
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
focusNode: node,
|
focusNode: node,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -2,6 +2,7 @@ import "package:flutter/material.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:nexus/controllers/members_controller.dart";
|
import "package:nexus/controllers/members_controller.dart";
|
||||||
import "package:nexus/controllers/rooms_controller.dart";
|
import "package:nexus/controllers/rooms_controller.dart";
|
||||||
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||||
import "package:nexus/widgets/loading.dart";
|
import "package:nexus/widgets/loading.dart";
|
||||||
|
|
@ -31,54 +32,46 @@ class MentionOverlay extends ConsumerWidget {
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.all(8),
|
||||||
child: switch (triggerCharacter) {
|
child: switch (triggerCharacter) {
|
||||||
"@" => Consumer(
|
"@" =>
|
||||||
builder: (_, ref, _) {
|
ref
|
||||||
final members = ref.watch(MembersController.provider(room));
|
.watch(MembersController.provider(room))
|
||||||
return ListView(
|
.betterWhen(
|
||||||
|
data: (members) => ListView(
|
||||||
children:
|
children:
|
||||||
(query.isEmpty
|
(query.isEmpty
|
||||||
? members
|
? members
|
||||||
: members.where(
|
: members.where(
|
||||||
(member) =>
|
(member) =>
|
||||||
member.stateKey?.toLowerCase().contains(
|
member.userId.toLowerCase().contains(
|
||||||
query.toLowerCase(),
|
query.toLowerCase(),
|
||||||
) ==
|
) ==
|
||||||
true ||
|
true ||
|
||||||
(member.content["displayname"] as String?)
|
member.displayName
|
||||||
?.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(query.toLowerCase()) ==
|
.contains(
|
||||||
|
query.toLowerCase(),
|
||||||
|
) ==
|
||||||
true,
|
true,
|
||||||
))
|
))
|
||||||
.map(
|
.map(
|
||||||
(member) => ListTile(
|
(member) => ListTile(
|
||||||
leading: AvatarOrHash(
|
leading: AvatarOrHash(
|
||||||
Uri.tryParse(
|
member.avatarUrl,
|
||||||
member.content["avatar_url"] ?? "",
|
member.displayName,
|
||||||
),
|
),
|
||||||
member.content["displayname"] ?? "",
|
title: Text(member.displayName),
|
||||||
),
|
subtitle: Text(member.userId),
|
||||||
title: Text(
|
|
||||||
member.content["displayname"] as String? ??
|
|
||||||
member.stateKey ??
|
|
||||||
"Unknown User",
|
|
||||||
),
|
|
||||||
subtitle: member.stateKey != null
|
|
||||||
? Text(member.stateKey!)
|
|
||||||
: null,
|
|
||||||
onTap: () => addTag(
|
onTap: () => addTag(
|
||||||
id: "[@${member.content["displayname"]}](https://matrix.to/#/${member.stateKey})",
|
id: "[@${member.displayName}](https://matrix.to/#/${member.userId})",
|
||||||
name:
|
name: member.userId
|
||||||
member.stateKey
|
.substring(1)
|
||||||
?.substring(1)
|
|
||||||
.split(":")
|
.split(":")
|
||||||
.first ??
|
.first,
|
||||||
"Unknown User",
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
"#" => ListView(
|
"#" => ListView(
|
||||||
children:
|
children:
|
||||||
|
|
@ -2,7 +2,9 @@ import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:nexus/models/relation_type.dart";
|
import "package:nexus/models/relation_type.dart";
|
||||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
import "package:nexus/models/room.dart";
|
||||||
|
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
|
||||||
|
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
|
||||||
|
|
||||||
class RelationPreview extends ConsumerWidget {
|
class RelationPreview extends ConsumerWidget {
|
||||||
final Message? relatedMessage;
|
final Message? relatedMessage;
|
||||||
|
|
@ -10,8 +12,11 @@ class RelationPreview extends ConsumerWidget {
|
||||||
final VoidCallback onDismiss;
|
final VoidCallback onDismiss;
|
||||||
final bool shouldMention;
|
final bool shouldMention;
|
||||||
final VoidCallback toggleShouldMention;
|
final VoidCallback toggleShouldMention;
|
||||||
const RelationPreview({
|
final Room room;
|
||||||
required this.relatedMessage,
|
|
||||||
|
const RelationPreview(
|
||||||
|
this.relatedMessage, {
|
||||||
|
required this.room,
|
||||||
required this.relationType,
|
required this.relationType,
|
||||||
required this.onDismiss,
|
required this.onDismiss,
|
||||||
required this.shouldMention,
|
required this.shouldMention,
|
||||||
|
|
@ -36,14 +41,10 @@ class RelationPreview extends ConsumerWidget {
|
||||||
"Editing message:",
|
"Editing message:",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
AvatarOrHash(
|
MessageAvatar(relatedMessage!, room),
|
||||||
Uri.tryParse(relatedMessage?.metadata?["avatarUrl"] ?? ""),
|
MessageDisplayname(
|
||||||
relatedMessage?.metadata?["displayName"]?.toString() ?? "",
|
relatedMessage!,
|
||||||
height: 16,
|
room,
|
||||||
),
|
|
||||||
Text(
|
|
||||||
relatedMessage!.metadata?["displayName"] ??
|
|
||||||
relatedMessage!.authorId,
|
|
||||||
style: theme.textTheme.labelMedium?.copyWith(
|
style: theme.textTheme.labelMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
|
@ -22,6 +22,10 @@ class Html extends ConsumerWidget {
|
||||||
html,
|
html,
|
||||||
textStyle: textStyle,
|
textStyle: textStyle,
|
||||||
customWidgetBuilder: (element) {
|
customWidgetBuilder: (element) {
|
||||||
|
if (element.attributes.keys.contains("data-mx-profile-fallback")) {
|
||||||
|
return SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
if (element.attributes.keys.contains("data-mx-spoiler")) {
|
if (element.attributes.keys.contains("data-mx-spoiler")) {
|
||||||
return InlineCustomWidget(child: SpoilerText(text: element.text));
|
return InlineCustomWidget(child: SpoilerText(text: element.text));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
lib/widgets/chat_page/lazy_loading/message_avatar.dart
Normal file
30
lib/widgets/chat_page/lazy_loading/message_avatar.dart
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import "package:flutter/widgets.dart";
|
||||||
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/author_controller.dart";
|
||||||
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
|
import "package:nexus/models/configs/author_config.dart";
|
||||||
|
import "package:nexus/models/room.dart";
|
||||||
|
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||||
|
|
||||||
|
class MessageAvatar extends ConsumerWidget {
|
||||||
|
final Message message;
|
||||||
|
final Room room;
|
||||||
|
final double height;
|
||||||
|
const MessageAvatar(this.message, this.room, {this.height = 16, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) => ref
|
||||||
|
.watch(
|
||||||
|
AuthorController.provider(AuthorConfig(room: room, message: message)),
|
||||||
|
)
|
||||||
|
.betterWhen(
|
||||||
|
data: (membership) => AvatarOrHash(
|
||||||
|
membership.avatarUrl,
|
||||||
|
membership.displayName,
|
||||||
|
height: height,
|
||||||
|
),
|
||||||
|
loading: () =>
|
||||||
|
AvatarOrHash(null, message.authorId.substring(1), height: height),
|
||||||
|
);
|
||||||
|
}
|
||||||
28
lib/widgets/chat_page/lazy_loading/message_displayname.dart
Normal file
28
lib/widgets/chat_page/lazy_loading/message_displayname.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import "package:flutter/widgets.dart";
|
||||||
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/author_controller.dart";
|
||||||
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
|
import "package:nexus/models/configs/author_config.dart";
|
||||||
|
import "package:nexus/models/room.dart";
|
||||||
|
|
||||||
|
class MessageDisplayname extends ConsumerWidget {
|
||||||
|
final Message message;
|
||||||
|
final Room room;
|
||||||
|
final TextStyle? style;
|
||||||
|
const MessageDisplayname(this.message, this.room, {this.style, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) => ref
|
||||||
|
.watch(
|
||||||
|
AuthorController.provider(AuthorConfig(room: room, message: message)),
|
||||||
|
)
|
||||||
|
.betterWhen(
|
||||||
|
data: (membership) => Text(
|
||||||
|
"${membership.displayName} ${message.metadata?["pmp"] == null ? "" : "(via ${message.authorId})"}",
|
||||||
|
style: style,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
loading: () => Text(""),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:nexus/controllers/members_controller.dart";
|
import "package:nexus/controllers/members_controller.dart";
|
||||||
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
import "package:nexus/widgets/avatar_or_hash.dart";
|
||||||
|
|
||||||
|
|
@ -10,15 +11,17 @@ class MemberList extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final members = ref.watch(MembersController.provider(room));
|
final membersProvider = ref.watch(MembersController.provider(room));
|
||||||
return Drawer(
|
return Drawer(
|
||||||
shape: Border(),
|
shape: Border(),
|
||||||
child: ListView(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
AppBar(
|
AppBar(
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
leading: Icon(Icons.people),
|
leading: Icon(Icons.people),
|
||||||
title: Text("Members (${members.length})"),
|
title: Text(
|
||||||
|
"Members ${membersProvider.when(data: (members) => "${members.length}", error: (_, _) => "", loading: () => "")}",
|
||||||
|
),
|
||||||
actionsPadding: EdgeInsets.only(right: 4),
|
actionsPadding: EdgeInsets.only(right: 4),
|
||||||
actions: [
|
actions: [
|
||||||
if (Scaffold.of(context).hasEndDrawer)
|
if (Scaffold.of(context).hasEndDrawer)
|
||||||
|
|
@ -29,7 +32,11 @@ class MemberList extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...members.map(
|
membersProvider.betterWhen(
|
||||||
|
data: (members) => Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: members
|
||||||
|
.map(
|
||||||
(member) => ListTile(
|
(member) => ListTile(
|
||||||
onTap: () => showDialog(
|
onTap: () => showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -37,18 +44,22 @@ class MemberList extends ConsumerWidget {
|
||||||
Dialog(child: Text("TODO: Open member popover")),
|
Dialog(child: Text("TODO: Open member popover")),
|
||||||
),
|
),
|
||||||
leading: AvatarOrHash(
|
leading: AvatarOrHash(
|
||||||
Uri.tryParse(member.content["avatar_url"] ?? ""),
|
member.avatarUrl,
|
||||||
member.content["displayname"].toString(),
|
member.displayName,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
member.content["displayname"].toString(),
|
member.displayName,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
member.stateKey ?? "Unknown User",
|
member.userId,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import "dart:math";
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/event_controller.dart";
|
import "package:nexus/controllers/event_controller.dart";
|
||||||
import "package:nexus/controllers/message_controller.dart";
|
import "package:nexus/controllers/message_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/better_when.dart";
|
import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
import "package:nexus/models/message_config.dart";
|
import "package:nexus/models/configs/message_config.dart";
|
||||||
import "package:nexus/models/requests/get_event_request.dart";
|
import "package:nexus/models/requests/get_event_request.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
|
||||||
import "package:nexus/widgets/chat_page/html/quoted.dart";
|
import "package:nexus/widgets/chat_page/html/quoted.dart";
|
||||||
|
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
|
||||||
|
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
|
||||||
|
|
||||||
typedef OnTapReply = void Function(Message message)?;
|
typedef OnTapReply = void Function(Message message)?;
|
||||||
|
|
||||||
|
|
@ -61,73 +61,28 @@ class ReplyWidget extends ConsumerWidget {
|
||||||
return SizedBox.shrink();
|
return SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final smallerText =
|
|
||||||
message is TextMessage &&
|
|
||||||
replyMessage.metadata?["body"] != null
|
|
||||||
? replyMessage.metadata!["body"].substring(
|
|
||||||
0,
|
|
||||||
min(
|
|
||||||
max(
|
|
||||||
max(
|
|
||||||
(message as TextMessage)
|
|
||||||
.text
|
|
||||||
.length -
|
|
||||||
(replyMessage
|
|
||||||
.metadata?["displayName"]
|
|
||||||
as String)
|
|
||||||
.length -
|
|
||||||
5,
|
|
||||||
message
|
|
||||||
.metadata?["displayName"]
|
|
||||||
.length,
|
|
||||||
),
|
|
||||||
5,
|
|
||||||
),
|
|
||||||
replyMessage.metadata!["body"].length,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
final replyText =
|
|
||||||
(smallerText == null ||
|
|
||||||
smallerText.length ==
|
|
||||||
replyMessage
|
|
||||||
.metadata!["body"]
|
|
||||||
.length)
|
|
||||||
? replyMessage.metadata!["body"]
|
|
||||||
: "$smallerText...";
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => onTapReply?.call(replyMessage),
|
onTap: () => onTapReply?.call(replyMessage),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
AvatarOrHash(
|
MessageAvatar(replyMessage, room),
|
||||||
Uri.tryParse(
|
|
||||||
replyMessage.metadata?["avatarUrl"] ??
|
|
||||||
"",
|
|
||||||
),
|
|
||||||
replyMessage.metadata?["displayName"] ??
|
|
||||||
"",
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: MessageDisplayname(
|
||||||
replyMessage
|
replyMessage,
|
||||||
.metadata?["displayName"] ??
|
room,
|
||||||
replyMessage.authorId,
|
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelMedium
|
.labelMedium
|
||||||
?.copyWith(
|
?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
replyText,
|
replyMessage.metadata!["body"],
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,14 @@ import "package:nexus/helpers/extensions/better_when.dart";
|
||||||
import "package:nexus/helpers/extensions/show_context_menu.dart";
|
import "package:nexus/helpers/extensions/show_context_menu.dart";
|
||||||
import "package:nexus/models/relation_type.dart";
|
import "package:nexus/models/relation_type.dart";
|
||||||
import "package:nexus/models/requests/report_request.dart";
|
import "package:nexus/models/requests/report_request.dart";
|
||||||
import "package:nexus/widgets/chat_page/chat_box.dart";
|
import "package:nexus/widgets/chat_page/composer/chat_box.dart";
|
||||||
import "package:nexus/widgets/chat_page/image_message.dart";
|
import "package:nexus/widgets/chat_page/image_message.dart";
|
||||||
import "package:nexus/widgets/chat_page/member_list.dart";
|
import "package:nexus/widgets/chat_page/member_list.dart";
|
||||||
import "package:nexus/widgets/chat_page/message_wrapper.dart";
|
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
|
||||||
import "package:nexus/widgets/chat_page/room_appbar.dart";
|
import "package:nexus/widgets/chat_page/room_appbar.dart";
|
||||||
import "package:nexus/widgets/chat_page/text_message_wrapper.dart";
|
import "package:nexus/widgets/chat_page/wrappers/text_message_wrapper.dart";
|
||||||
import "package:nexus/widgets/chat_page/reply_widget.dart";
|
import "package:nexus/widgets/chat_page/reply_widget.dart";
|
||||||
import "package:nexus/widgets/form_text_input.dart";
|
import "package:nexus/widgets/form_text_input.dart";
|
||||||
import "package:nexus/widgets/loading.dart";
|
|
||||||
// import "package:dynamic_polls/dynamic_polls.dart";
|
// import "package:dynamic_polls/dynamic_polls.dart";
|
||||||
|
|
||||||
class RoomChat extends HookConsumerWidget {
|
class RoomChat extends HookConsumerWidget {
|
||||||
|
|
@ -233,7 +232,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
children: getMessageOptions(message),
|
children: getMessageOptions(message),
|
||||||
),
|
),
|
||||||
builders: Builders(
|
builders: Builders(
|
||||||
loadMoreBuilder: (_) => Loading(),
|
loadMoreBuilder: (_) => SizedBox.shrink(),
|
||||||
|
|
||||||
chatAnimatedListBuilder: (_, itemBuilder) =>
|
chatAnimatedListBuilder: (_, itemBuilder) =>
|
||||||
ChatAnimatedList(
|
ChatAnimatedList(
|
||||||
|
|
@ -320,6 +319,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
groupStatus,
|
groupStatus,
|
||||||
|
room,
|
||||||
),
|
),
|
||||||
|
|
||||||
systemMessageBuilder:
|
systemMessageBuilder:
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ import "package:nexus/widgets/chat_page/room_menu.dart";
|
||||||
import "package:nexus/widgets/form_text_input.dart";
|
import "package:nexus/widgets/form_text_input.dart";
|
||||||
|
|
||||||
class Sidebar extends HookConsumerWidget {
|
class Sidebar extends HookConsumerWidget {
|
||||||
const Sidebar({super.key});
|
final bool isDesktop;
|
||||||
|
const Sidebar({required this.isDesktop, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|
@ -220,9 +221,12 @@ class Sidebar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onDestinationSelected: (value) => selectedRoomIdNotifier.set(
|
onDestinationSelected: (value) {
|
||||||
|
selectedRoomIdNotifier.set(
|
||||||
selectedSpace.children[value].metadata?.id,
|
selectedSpace.children[value].metadata?.id,
|
||||||
),
|
);
|
||||||
|
if (!isDesktop) Navigator.of(context).pop();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:nexus/widgets/avatar_or_hash.dart";
|
import "package:nexus/models/room.dart";
|
||||||
|
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
|
||||||
|
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
|
||||||
|
|
||||||
class MessageWrapper extends StatelessWidget {
|
class MessageWrapper extends StatelessWidget {
|
||||||
final Message message;
|
final Message message;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
final Room room;
|
||||||
final MessageGroupStatus? groupStatus;
|
final MessageGroupStatus? groupStatus;
|
||||||
const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
|
const MessageWrapper(
|
||||||
|
this.message,
|
||||||
|
this.child,
|
||||||
|
this.groupStatus,
|
||||||
|
this.room, {
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ClipRRect(
|
Widget build(BuildContext context) => ClipRRect(
|
||||||
|
|
@ -24,11 +33,7 @@ class MessageWrapper extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
groupStatus?.isFirst != false
|
groupStatus?.isFirst != false
|
||||||
? AvatarOrHash(
|
? MessageAvatar(message, room, height: 40)
|
||||||
Uri.parse(message.metadata?["avatarUrl"] ?? ""),
|
|
||||||
height: 40,
|
|
||||||
message.metadata?["displayName"] ?? "",
|
|
||||||
)
|
|
||||||
: SizedBox(width: 40),
|
: SizedBox(width: 40),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -36,9 +41,9 @@ class MessageWrapper extends StatelessWidget {
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
if (groupStatus?.isFirst != false)
|
if (groupStatus?.isFirst != false)
|
||||||
Text(
|
MessageDisplayname(
|
||||||
message.metadata?["displayName"] ?? message.authorId,
|
message,
|
||||||
overflow: TextOverflow.ellipsis,
|
room,
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
|
|
@ -3,7 +3,7 @@ import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_link_previewer/flutter_link_previewer.dart";
|
import "package:flutter_link_previewer/flutter_link_previewer.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
import "package:nexus/widgets/chat_page/html/html.dart";
|
import "package:nexus/widgets/chat_page/html/html.dart";
|
||||||
import "package:nexus/widgets/chat_page/message_wrapper.dart";
|
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
|
||||||
import "package:nexus/widgets/chat_page/reply_widget.dart";
|
import "package:nexus/widgets/chat_page/reply_widget.dart";
|
||||||
|
|
||||||
class TextMessageWrapper extends StatelessWidget {
|
class TextMessageWrapper extends StatelessWidget {
|
||||||
|
|
@ -109,6 +109,7 @@ class TextMessageWrapper extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
groupStatus,
|
groupStatus,
|
||||||
|
room,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
|
||||||
set(BINARY_NAME "nexus")
|
set(BINARY_NAME "nexus")
|
||||||
# The unique GTK application identifier for this application. See:
|
# The unique GTK application identifier for this application. See:
|
||||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
set(APPLICATION_ID "nexus.federated.nexus")
|
set(APPLICATION_ID "nexus.federated.Nexus")
|
||||||
|
|
||||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
# versions of CMake.
|
# versions of CMake.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
#include <window_size/window_size_plugin.h>
|
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) dynamic_system_colors_registrar =
|
g_autoptr(FlPluginRegistrar) dynamic_system_colors_registrar =
|
||||||
|
|
@ -29,7 +28,4 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) window_manager_registrar =
|
g_autoptr(FlPluginRegistrar) window_manager_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
|
||||||
window_manager_plugin_register_with_registrar(window_manager_registrar);
|
window_manager_plugin_register_with_registrar(window_manager_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) window_size_registrar =
|
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
|
|
||||||
window_size_plugin_register_with_registrar(window_size_registrar);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
screen_retriever_linux
|
screen_retriever_linux
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
window_manager
|
window_manager
|
||||||
window_size
|
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
|
||||||
9
linux/nexus.federated.Nexus.desktop
Normal file
9
linux/nexus.federated.Nexus.desktop
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Nexus
|
||||||
|
GenericName=Matrix Client
|
||||||
|
Comment=A simple and user-friendly Matrix client
|
||||||
|
Exec=nexus
|
||||||
|
Icon=nexus
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Categories=Chat;Network;InstantMessaging;
|
||||||
41
linux/nix/devshell.nix
Normal file
41
linux/nix/devshell.nix
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
{ pkgs, lib }:
|
||||||
|
let
|
||||||
|
android = pkgs.androidenv.composeAndroidPackages {
|
||||||
|
toolsVersion = "26.1.1";
|
||||||
|
platformToolsVersion = "36.0.1";
|
||||||
|
buildToolsVersions = [
|
||||||
|
"35.0.0"
|
||||||
|
"36.0.0"
|
||||||
|
];
|
||||||
|
cmakeVersions = [ "3.22.1" ];
|
||||||
|
platformVersions = [ "36" ];
|
||||||
|
abiVersions = [
|
||||||
|
"armeabi-v7a"
|
||||||
|
"arm64-v8a"
|
||||||
|
];
|
||||||
|
includeNDK = true;
|
||||||
|
ndkVersions = [ "28.2.13676358" ];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
go
|
||||||
|
git
|
||||||
|
jdk17
|
||||||
|
flutter
|
||||||
|
android.platform-tools
|
||||||
|
];
|
||||||
|
|
||||||
|
env = rec {
|
||||||
|
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ];
|
||||||
|
LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}";
|
||||||
|
CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ];
|
||||||
|
|
||||||
|
ANDROID_HOME = "${android.androidsdk}/libexec/android-sdk";
|
||||||
|
ANDROID_SDK_ROOT = ANDROID_HOME;
|
||||||
|
JAVA_HOME = pkgs.jdk17;
|
||||||
|
|
||||||
|
TOOLS = "${ANDROID_HOME}/build-tools/${"36.0.0"}";
|
||||||
|
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${TOOLS}/aapt2";
|
||||||
|
};
|
||||||
|
}
|
||||||
44
linux/nix/pkg/default.nix
Normal file
44
linux/nix/pkg/default.nix
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
callPackage,
|
||||||
|
libclang,
|
||||||
|
flutter,
|
||||||
|
src,
|
||||||
|
}:
|
||||||
|
|
||||||
|
flutter.buildFlutterApplication {
|
||||||
|
pname = "nexus";
|
||||||
|
version = "0.1.0";
|
||||||
|
inherit src;
|
||||||
|
|
||||||
|
preBuild = ''
|
||||||
|
cp ${callPackage ./gomuks.nix { inherit src; }}/lib/* .
|
||||||
|
packageRunCustom nexus generate source/scripts test
|
||||||
|
packageRun build_runner build
|
||||||
|
'';
|
||||||
|
|
||||||
|
env.LIBCLANG_PATH = lib.makeLibraryPath [ libclang ];
|
||||||
|
|
||||||
|
autoPubspecLock = src + "/pubspec.lock";
|
||||||
|
|
||||||
|
gitHashes = {
|
||||||
|
window_size = "sha256-XelNtp7tpZ91QCEcvewVphNUtgQX7xrp5QP0oFo6DgM=";
|
||||||
|
dynamic_system_colors = "sha256-es6rjMK1drkqZBKYUP77yw/q5+0uLwWOEDOXRawy3Dc=";
|
||||||
|
flutter_chat_ui = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk=";
|
||||||
|
flutter_link_previewer = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk=";
|
||||||
|
};
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
install -D assets/icon.svg $out/share/icons/hicolor/scalable/apps/nexus.svg
|
||||||
|
install -Dm755 linux/nexus.federated.Nexus.desktop -t $out/share/applications
|
||||||
|
wrapProgram $out/bin/nexus \
|
||||||
|
--suffix LD_LIBRARY_PATH : $out/app/nexus/lib
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "A simple and user-friendly Matrix client";
|
||||||
|
mainProgram = "nexus";
|
||||||
|
platforms = lib.platforms.linux;
|
||||||
|
maintainers = with lib.maintainers; [ quadradical ];
|
||||||
|
};
|
||||||
|
}
|
||||||
31
linux/nix/pkg/gomuks.nix
Normal file
31
linux/nix/pkg/gomuks.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
src,
|
||||||
|
buildGoModule,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildGoModule (finalAttrs: {
|
||||||
|
pname = "gomuks-ffi";
|
||||||
|
version = "submodule";
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
src = "${src}/gomuks";
|
||||||
|
|
||||||
|
vendorHash = "sha256-zBDfBZqUoHIfZ0AajZEvSBbskjpFB7yIsomt0KYDo7Y=";
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
go build -buildmode=c-shared -o libgomuks.so -tags goolm,noheic ./pkg/ffi
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
install -Dm0644 libgomuks.so -t $out/lib
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
|
@ -43,6 +43,7 @@ static void my_application_activate(GApplication* application) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
gtk_widget_set_size_request(GTK_WIDGET(window), 500, 500);
|
||||||
if (use_header_bar) {
|
if (use_header_bar) {
|
||||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
androidenv,
|
|
||||||
}:
|
|
||||||
androidenv.composeAndroidPackages {
|
|
||||||
toolsVersion = "26.1.1";
|
|
||||||
platformToolsVersion = "36.0.1";
|
|
||||||
buildToolsVersions = [
|
|
||||||
"35.0.0"
|
|
||||||
"36.0.0"
|
|
||||||
];
|
|
||||||
cmakeVersions = [ "3.22.1" ];
|
|
||||||
platformVersions = [ "36" ];
|
|
||||||
abiVersions = [
|
|
||||||
"armeabi-v7a"
|
|
||||||
"arm64-v8a"
|
|
||||||
];
|
|
||||||
includeNDK = true;
|
|
||||||
ndkVersions = [ "27.0.12077973" ];
|
|
||||||
|
|
||||||
}
|
|
||||||
42
pubspec.lock
42
pubspec.lock
|
|
@ -29,10 +29,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer_buffer
|
name: analyzer_buffer
|
||||||
sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033
|
sha256: "5fcd06b0715ebeee99f03e3f437b3412249969d8d12b191ea8a1d76e42a4e4a1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.11"
|
version: "0.3.1"
|
||||||
analyzer_plugin:
|
analyzer_plugin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -348,10 +348,11 @@ packages:
|
||||||
dynamic_system_colors:
|
dynamic_system_colors:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dynamic_system_colors
|
path: "."
|
||||||
sha256: "43794e658fa88cbdec9f397dd1afd2eb69b6c9717e99b93b16ba37c3aa3b3a8c"
|
ref: HEAD
|
||||||
url: "https://pub.dev"
|
resolved-ref: "3b61760d5e0ac1229eefde5b61247947eede4110"
|
||||||
source: hosted
|
url: "https://github.com/hasali19/flutter_dynamic_system_colors"
|
||||||
|
source: git
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
encrypt:
|
encrypt:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
|
|
@ -521,10 +522,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_riverpod
|
name: flutter_riverpod
|
||||||
sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02"
|
sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.3.1"
|
||||||
flutter_svg:
|
flutter_svg:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -643,10 +644,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: hooks_riverpod
|
name: hooks_riverpod
|
||||||
sha256: b880efcd17757af0aa242e5dceac2fb781a014c22a32435a5daa8f17e9d5d8a9
|
sha256: "08527ec06aaef75e4b78694e045ef0cd8346594eaf9cc18b0f866398b07b93b1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.3.1"
|
||||||
html:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1051,26 +1052,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: riverpod
|
name: riverpod
|
||||||
sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a"
|
sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.2.1"
|
||||||
riverpod_analyzer_utils:
|
riverpod_analyzer_utils:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: riverpod_analyzer_utils
|
name: riverpod_analyzer_utils
|
||||||
sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0"
|
sha256: e55bc08c084a424e1bbdc303fe8ea75daafe4269b68fd0e0f6f1678413715b66
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0-dev.8"
|
version: "1.0.0-dev.9"
|
||||||
riverpod_lint:
|
riverpod_lint:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: riverpod_lint
|
name: riverpod_lint
|
||||||
sha256: "4d2eb0d19bbe7e3323bd0ce4553b2e6170d161a13914bfdd85a3612329edcb43"
|
sha256: "64e8debf5b719a37d48b9785dd595d34133fdcd84b8fd07157a621c54ab2156f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.3"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1548,15 +1549,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.5.1"
|
||||||
window_size:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "plugins/window_size"
|
|
||||||
ref: HEAD
|
|
||||||
resolved-ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
|
|
||||||
url: "https://github.com/google/flutter-desktop-embedding"
|
|
||||||
source: git
|
|
||||||
version: "0.1.0"
|
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
16
pubspec.yaml
16
pubspec.yaml
|
|
@ -21,8 +21,8 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_riverpod: ^3.0.3
|
flutter_riverpod: ^3.3.1
|
||||||
hooks_riverpod: ^3.0.3
|
hooks_riverpod: ^3.3.1
|
||||||
intl: ^0.20.1
|
intl: ^0.20.1
|
||||||
fast_immutable_collections: ^11.0.0
|
fast_immutable_collections: ^11.0.0
|
||||||
path_provider: ^2.1.3
|
path_provider: ^2.1.3
|
||||||
|
|
@ -31,13 +31,11 @@ dependencies:
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
file_picker: ^10.3.3
|
file_picker: ^10.3.3
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
dynamic_system_colors: ^1.8.0
|
dynamic_system_colors:
|
||||||
|
git:
|
||||||
|
url: https://github.com/hasali19/flutter_dynamic_system_colors
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
window_manager: ^0.5.1
|
window_manager: ^0.5.1
|
||||||
window_size:
|
|
||||||
git:
|
|
||||||
url: https://github.com/google/flutter-desktop-embedding
|
|
||||||
path: plugins/window_size
|
|
||||||
flutter_chat_core: ^2.0.0
|
flutter_chat_core: ^2.0.0
|
||||||
flyer_chat_image_message: ^2.2.2
|
flyer_chat_image_message: ^2.2.2
|
||||||
flyer_chat_system_message: ^2.1.13
|
flyer_chat_system_message: ^2.1.13
|
||||||
|
|
@ -69,7 +67,7 @@ dev_dependencies:
|
||||||
custom_lint: ^0.8.0
|
custom_lint: ^0.8.0
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
freezed: ^3.2.3
|
freezed: ^3.2.3
|
||||||
riverpod_lint: ^3.0.3
|
riverpod_lint: ^3.1.3
|
||||||
flutter_launcher_icons: ^0.14.1
|
flutter_launcher_icons: ^0.14.1
|
||||||
json_serializable: ^6.11.1
|
json_serializable: ^6.11.1
|
||||||
|
|
||||||
|
|
@ -77,7 +75,7 @@ flutter_launcher_icons:
|
||||||
ios: true
|
ios: true
|
||||||
android: true
|
android: true
|
||||||
image_path: assets/icon.png
|
image_path: assets/icon.png
|
||||||
adaptive_icon_background: "#000000"
|
adaptive_icon_background: assets/background.png
|
||||||
adaptive_icon_foreground: assets/foreground.png
|
adaptive_icon_foreground: assets/foreground.png
|
||||||
remove_alpha_ios: true
|
remove_alpha_ios: true
|
||||||
windows:
|
windows:
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,7 @@ import "package:ffigen/ffigen.dart";
|
||||||
import "package:path/path.dart";
|
import "package:path/path.dart";
|
||||||
|
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
final repoDir = Directory.fromUri(
|
final repoDir = Directory.fromUri(Platform.script.resolve("../gomuks"));
|
||||||
Platform.script.resolve("../src/gomuks/source"),
|
|
||||||
);
|
|
||||||
if (await repoDir.exists()) await repoDir.delete(recursive: true);
|
|
||||||
await repoDir.create(recursive: true);
|
|
||||||
|
|
||||||
print("Cloning Gomuks repository...");
|
|
||||||
final cloneResult = await Process.run("git", [
|
|
||||||
"clone",
|
|
||||||
"--depth",
|
|
||||||
"1",
|
|
||||||
"https://mau.dev/gomuks/gomuks",
|
|
||||||
repoDir.path,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (cloneResult.exitCode != 0) {
|
|
||||||
throw Exception(
|
|
||||||
"Failed to clone Gomuks repository: \n${cloneResult.stderr}",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Generating FFI Bindings...");
|
print("Generating FFI Bindings...");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
pushd "$(dirname "$(readlink -f "$0")")"/.. > /dev/null || exit
|
|
||||||
|
|
||||||
mkdir -p build
|
|
||||||
touch build/lock
|
|
||||||
dart scripts/generate.dart
|
|
||||||
rm build/lock
|
|
||||||
|
|
||||||
popd > /dev/null || exit
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
#include <window_size/window_size_plugin.h>
|
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
DynamicColorPluginCApiRegisterWithRegistrar(
|
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||||
|
|
@ -24,6 +23,4 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
WindowManagerPluginRegisterWithRegistrar(
|
WindowManagerPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
|
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
|
||||||
WindowSizePluginRegisterWithRegistrar(
|
|
||||||
registry->GetRegistrarForPlugin("WindowSizePlugin"));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
screen_retriever_windows
|
screen_retriever_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
window_manager
|
window_manager
|
||||||
window_size
|
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,11 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "040904e4"
|
BLOCK "040904e4"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "nexus.federated.nexus" "\0"
|
VALUE "CompanyName", "nexus.federated.Nexus" "\0"
|
||||||
VALUE "FileDescription", "nexus" "\0"
|
VALUE "FileDescription", "nexus" "\0"
|
||||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||||
VALUE "InternalName", "nexus" "\0"
|
VALUE "InternalName", "nexus" "\0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2025 nexus.federated.nexus. All rights reserved." "\0"
|
VALUE "LegalCopyright", "Copyright (C) 2025 nexus.federated.Nexus. All rights reserved." "\0"
|
||||||
VALUE "OriginalFilename", "nexus.exe" "\0"
|
VALUE "OriginalFilename", "nexus.exe" "\0"
|
||||||
VALUE "ProductName", "nexus" "\0"
|
VALUE "ProductName", "nexus" "\0"
|
||||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue