1
0
Fork 0
forked from Nexus/nexus

Compare commits

...

59 commits

Author SHA1 Message Date
a1401f20ea
fix icons for flatpak build 2026-03-27 10:36:04 -04:00
f8d6dcead5
show appbar on verify page 2026-03-26 20:35:04 -04:00
02c79a6d7c
add hardcoded GOCACHE
This will probably need to be changed if we get any windows developers
2026-03-26 20:15:05 -04:00
11aef9fc5a
add GOENV to windows build 2026-03-26 18:45:45 -04:00
2f39949a2e
use a relative dir for caching on windows 2026-03-26 17:37:42 -04:00
166295fdb5
disable caching for go action 2026-03-26 17:03:43 -04:00
607cd54e02
Clone with submodules for windows script 2026-03-26 16:55:06 -04:00
67b96ae731
fix no hash provided for dynamic_system_colors 2026-03-26 16:51:24 -04:00
ed81b4afa1
change dynamic_system_colors bound 2026-03-26 16:47:45 -04:00
345fa3b5ff
pin flutter version for windows [skip ci] 2026-03-26 16:38:10 -04:00
f50fb6ab09
simplify windows script [skip ci] 2026-03-26 16:33:57 -04:00
5601fb27c0
test new windows build [skip ci] 2026-03-26 16:31:08 -04:00
0d1f7c1819
more verbose errors for login and verify 2026-03-26 15:53:42 -04:00
4bbf694479
remove dep on window_size 2026-03-26 15:49:28 -04:00
dd9b9fdc62
rename app id to match desktop file 2026-03-26 12:22:07 -04:00
8b7f88cc0b
rename desktop file 2026-03-26 12:09:20 -04:00
70793a2f77
test commented out sizes 2026-03-26 10:54:43 -04:00
b2c763deef
change artifact names 2026-03-26 10:28:22 -04:00
5c66d35017
add keystore to android build 2026-03-26 10:13:31 -04:00
a25f9d2e73
check out submodules for android build 2026-03-26 09:51:30 -04:00
4a3b7e9a14
add some more info to the flatpak 2026-03-25 23:31:57 -04:00
6974e5cc06
allow dri permission for flatpak 2026-03-25 23:18:52 -04:00
42c32b1b1c
fix android build 2026-03-25 23:06:53 -04:00
87466f9d05
Add apk build workflow 2026-03-25 22:50:33 -04:00
ea72654887
run flatpak build on both x86_64 and aarch64 2026-03-25 22:38:52 -04:00
f1af130a63
simplify flatpak action, run on push 2026-03-25 22:33:05 -04:00
e7b772ef66
Add flatpak build 2026-03-25 22:29:24 -04:00
28dfe9e981
Working flatpak builds 2026-03-25 22:06:30 -04:00
d4c98a0cfb
Add GenericName to desktop file 2026-03-25 21:31:14 -04:00
01772b567a
Shorten nix install 2026-03-25 13:09:50 -04:00
f9927d1eb3
Wrap binary to fix libgomuks load 2026-03-25 12:11:38 -04:00
0d44d10e05
add icon and desktop file to nix package 2026-03-25 11:52:04 -04:00
11ecec5ab3
add nix package 2026-03-25 11:40:31 -04:00
b407bbfdee
add background for android icon 2026-03-25 11:40:25 -04:00
04b7ab8e2e
WIP nix builds
Not working, needs a separate gomuks build
2026-03-24 21:02:23 -04:00
0cae2692bc
Add a todo to parse vias correctly 2026-03-24 16:51:30 -04:00
b387f0755a
make sidebar auto collapse when selecting a room on the mobile layout 2026-03-24 16:45:25 -04:00
840f2fe464
remove now un-needed lock 2026-03-24 16:29:42 -04:00
e5062683e8
Fix send on enter on mobile 2026-03-24 16:14:42 -04:00
ffe879680d
Don't wait for potential double tap for appbar actions, makes button taps more responsive in appbar 2026-03-24 15:34:26 -04:00
32dfba178a
Update readme instructions 2026-03-24 12:43:38 -04:00
fe845e6cd6
Use a submodule for gomuks source 2026-03-24 12:37:00 -04:00
d3e6340b28
Fix readme to no longer show the Dart SDK. 2026-03-24 10:33:43 -04:00
b6e7bb82da
Don't autofocus chat box due to OSK issues for now
I can fix this later by stopping the text input from rerenderring so often
2026-03-24 09:41:24 -04:00
eb503ba647
Fix android builds on nix, might need further cleanup later 2026-03-24 09:29:49 -04:00
cda971a335
lock gomuks src to a commit 2026-03-23 23:04:59 -04:00
e4f666b824 Merge pull request 'Initial flutter android build' (#4) from zaaach/nexus:android into main
Reviewed-on: Henry-Hiles/nexus#4
Reviewed-by: Henry Hiles <henry@henryhiles.com>
2026-03-23 22:56:39 -04:00
cf2150466e Merge branch 'main' into android 2026-03-23 22:52:57 -04:00
23bbbb533e set root on init 2026-03-23 19:00:02 -06:00
db9fc597f3
Use empty text for displayname loading state rather than a SizedBox.shrink to help with jumping messages 2026-03-23 19:33:22 -04:00
6839f0bdae
remove loading animation for mark read and load more for smoother scrolling 2026-03-23 15:08:18 -04:00
6f4ad046b0
bump riverpod, should fix unmounted error 2026-03-23 11:35:27 -04:00
4494705ef9
remove unused imports 2026-03-22 16:54:16 -04:00
237886971c
make members controller an asyncnotifier
makes loading smoother and more responsive
2026-03-22 16:46:48 -04:00
95a4e03f00
reorganize 2026-03-22 16:36:49 -04:00
9054b6b357
lazy load memberships 2026-03-22 16:35:15 -04:00
8b056d8ed1
dont fetch members on load 2026-03-22 14:30:46 -04:00
edbc647a06
remove screenshots for twim 2026-03-21 16:23:03 -04:00
91516c2f20 initial flutter android build 2026-03-20 12:15:10 -06:00
64 changed files with 948 additions and 465 deletions

39
.github/workflows/android.yml vendored Normal file
View 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
View 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

View file

@ -1,46 +1,50 @@
name: "Build Windows Version"
name: "Build EXE"
on:
push:
branches: ["main"]
tags: ["*"]
workflow_dispatch:
jobs:
build-windows:
runs-on: "windows-latest"
build-exe:
runs-on: windows-latest
steps:
- name: "Checkout repository"
uses: "actions/checkout@v4"
- name: "Set up Flutter"
uses: "subosito/flutter-action@v2"
- name: "Set up Rust"
uses: "dtolnay/rust-toolchain@stable"
- name: Checkout repository
uses: actions/checkout@v6
with:
targets: "x86_64-pc-windows-msvc"
submodules: recursive
- name: "Install Flutter dependencies"
run: flutter pub get
- name: Set up Flutter
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: |
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
- name: "Upload exe zip"
uses: "actions/upload-artifact@v4"
- name: Upload exe zip
uses: actions/upload-artifact@v6
with:
name: "windows-portable"
path: "build/windows/x64/runner/Release/"
name: windows-portable
path: build/windows/x64/runner/Release/
- name: "Install Inno Setup"
- name: Install Inno Setup
run: choco install innosetup -y
- name: "Build Inno Setup installer"
- name: Build Inno Setup installer
run: iscc windows/installer.iss
- name: "Upload installer artifact"
uses: "actions/upload-artifact@v4"
- name: Upload installer artifact
uses: actions/upload-artifact@v6
with:
name: "windows-installer"
path: "windows/dist/Nexus-Setup.exe"
name: windows-installer
path: windows/dist/Nexus-Setup.exe

4
.gitignore vendored
View file

@ -36,7 +36,9 @@ key.properties
# Generated Files
*.g.dart
*.freezed.dart
src/
# Devel Password
password.txt
# Nix
/result

4
.gitmodules vendored Normal file
View file

@ -0,0 +1,4 @@
[submodule "gomuks"]
path = gomuks
url = https://github.com/zachatrocity/gomuks
branch = init-root-dir

View file

@ -5,7 +5,7 @@
## 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
@ -17,8 +17,8 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S
- [ ] New logo
- [ ] 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
- [ ] Allow using remote gomuks over websocket
- [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
- [ ] Platform Support
- [x] Linux
- [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] Replies
- [x] Choose ping on/off
- [ ] Per message profiles
- [ ] Attachments
- [ ] Commands with [MSC4391](https://github.com/matrix-org/matrix-spec-proposals/pull/4391)
- [x] Mentions
@ -64,6 +65,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S
- [ ] GIFs using Gomuks' GIF proxies
- [x] Recieving
- [x] Plain text
- [x] Per message profiles
- [x] HTML
- [x] Replies
- [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:
```sh
git clone https://git.federated.nexus/Henry-Hiles/nexus
git clone --recurse-submodules https://git.federated.nexus/Henry-Hiles/nexus
cd nexus
```
@ -136,12 +138,12 @@ cd nexus
#### 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.
#### 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
@ -151,13 +153,7 @@ Get dependencies:
flutter pub get
```
Get dependencies:
```sh
flutter pub get
```
Clone Gomuks and generate bindings:
Generate Gomuks bindings:
```sh
scripts/generate.sh

View file

@ -39,6 +39,11 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
// do we want to update.. eventually?
jvmTarget = "17"
}
defaultConfig {
applicationId = "nexus.federated.Nexus"
minSdk = 29
@ -50,7 +55,8 @@ android {
signingConfigs {
release {
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")
storePassword keystoreProperties['password'] ?: System.getenv("KEYSTORE_PASSWORD")
}

View file

@ -10,7 +10,7 @@
android:label="Nexus"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/nexus_round"
android:roundIcon="@mipmap/ic_launcher"
android:allowBackup="false"
android:fullBackupContent="false">
<activity

BIN
assets/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

62
assets/background.svg Normal file
View 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

Before After
Before After

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
View file

@ -18,17 +18,54 @@
"type": "github"
}
},
"nixpkgs": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1767640445,
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"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",
"repo": "nixpkgs",
"type": "github"
@ -49,10 +86,42 @@
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1767640445,
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"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"
}
}
},

View file

@ -2,8 +2,10 @@
description = "Nexus Flutter Flake";
inputs = {
self.submodules = true;
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
nix2flatpak.url = "github:neobrain/nix2flatpak";
};
outputs =
@ -33,36 +35,42 @@
_module.args.pkgs = import nixpkgs {
inherit system;
config = {
permittedInsecurePackages = [ "olm-3.2.16" ];
android_sdk.accept_license = true;
allowUnfree = true;
};
};
devShells =
packages =
let
packages = with pkgs; [
go
olm
git
];
env = {
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ];
LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}";
CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ];
default = pkgs.callPackage ./linux/nix/pkg {
src = self;
};
in
{
default = pkgs.mkShell {
inherit env;
packages = packages ++ [
pkgs.flutter
];
inherit default;
flatpak = inputs.nix2flatpak.lib.${system}.mkFlatpak {
appName = "Nexus";
developer = "QuadRadical";
appId = "nexus.federated.Nexus";
package = default;
runtime = "org.gnome.Platform/49";
permissions = {
share = [ "network" ];
sockets = [
"fallback-x11"
"wayland"
];
devices = [ "dri" ];
};
};
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

@ -0,0 +1 @@
Subproject commit daa0ba028e7d89ba9fc7580fc8099348e6145cb3

View file

@ -3,11 +3,12 @@ import "package:hooks/hooks.dart";
import "package:code_assets/code_assets.dart";
Future<void> main(List<String> args) => build(args, (input, output) async {
final buildDir = input.packageRoot.resolve("src/");
if (await File(buildDir.resolve("lock").toFilePath()).exists()) return;
final codeConfig = input.config.code;
final targetOS = codeConfig.targetOS;
final targetArch = codeConfig.targetArchitecture;
final targetOS = input.config.code.targetOS;
String libFileName;
Map<String, String> env = {};
switch (targetOS) {
case OS.linux:
libFileName = "libgomuks.so";
@ -17,24 +18,60 @@ Future<void> main(List<String> args) => build(args, (input, output) async {
break;
case OS.windows:
libFileName = "libgomuks.dll";
env = {"GOCACHE": r"C:\Users\runneradmin\AppData\Local\go-build"};
break;
case OS.android:
libFileName = "libgomuks.so";
final targetNdkApi = codeConfig.android.targetNdkApi;
final ndkHome =
Platform.environment["ANDROID_NDK_HOME"] ??
Platform.environment["ANDROID_NDK_ROOT"] ??
Platform.environment["NDK_HOME"] ??
await _findNdkFromSdk();
if (ndkHome == null) {
throw Exception(
"Could not find Android NDK. Set ANDROID_NDK_HOME or install via sdkmanager.",
);
}
final hostTag = _ndkHostTag();
final (goArch, ccTriple) = _androidArch(targetArch);
final cc =
"$ndkHome/toolchains/llvm/prebuilt/$hostTag/bin/$ccTriple$targetNdkApi-clang";
env = {"CGO_ENABLED": "1", "GOOS": "android", "GOARCH": goArch, "CC": cc};
break;
default:
throw UnsupportedError("Unsupported OS: $targetOS");
}
final gomuksBuildDir = buildDir.resolve("gomuks/");
final libFile = gomuksBuildDir.resolve(libFileName);
var libFile = input.packageRoot.resolve(libFileName);
final gomuksBuildDir = input.packageRoot.resolve("gomuks/");
print("Building Gomuks shared library $libFileName from source...");
final result = await Process.run("go", [
"build",
"-o",
libFile.path,
"-buildmode=c-shared",
], workingDirectory: gomuksBuildDir.resolve("source/pkg/ffi/").toFilePath());
if (!(await File.fromUri(libFile).exists())) {
final buildDir = input.packageRoot.resolve("build/");
libFile = buildDir.resolve("${targetArch.name}/$libFileName");
if (result.exitCode != 0) {
throw Exception("Failed to build Gomuks shared library\n${result.stderr}");
// goheif/dav1d supported on Android would need to fix upstream
final tags = targetOS == OS.android ? "goolm,noheic" : "goolm";
print(
"Building Gomuks shared library $libFileName (${targetOS.name}/${targetArch.name}) from source...",
);
final result = await Process.run(
"go",
["build", "-tags", tags, "-o", libFile.path, "-buildmode=c-shared"],
workingDirectory: gomuksBuildDir.resolve("pkg/ffi/").toFilePath(),
environment: env.isNotEmpty ? env : null,
);
if (result.exitCode != 0) {
throw Exception(
"Failed to build Gomuks shared library\n${result.stderr}",
);
}
}
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);
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");
}
}

View file

@ -387,7 +387,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus;
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -519,7 +519,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus;
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -545,7 +545,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.nexus;
PRODUCT_BUNDLE_IDENTIFIER = nexus.federated.Nexus;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View 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,
);
}

View file

@ -1,5 +1,6 @@
import "dart:developer";
import "dart:ffi";
import "dart:io";
import "dart:isolate";
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
@ -31,11 +32,20 @@ import "package:nexus/models/sync_data.dart";
import "package:nexus/models/sync_status.dart";
import "package:nexus/src/third_party/gomuks.g.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:path_provider/path_provider.dart";
class ClientController extends AsyncNotifier<int> {
@override
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 =
NativeCallable<
@ -143,12 +153,12 @@ class ClientController extends AsyncNotifier<int> {
Future<void> sendMessage(SendMessageRequest request) =>
_sendCommand("send_message", request.toJson());
Future<bool> verify(String recoveryKey) async {
Future<String?> verify(String recoveryKey) async {
try {
await _sendCommand("verify", {"recovery_key": recoveryKey});
return true;
return null;
} 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 {
await _sendCommand("login", login.toJson());
return true;
return null;
} catch (error) {
return false;
return error.toString();
}
}

View file

@ -1,25 +1,39 @@
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.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";
class MembersController extends Notifier<IList<Event>> {
class MembersController extends AsyncNotifier<IList<Membership>> {
final Room room;
MembersController(this.room);
@override
IList<Event> build() => (room.state["m.room.member"]?.values ?? [])
.map(
(eventRowId) =>
room.events.firstWhereOrNull((event) => event.rowId == eventRowId),
)
.nonNulls
.where((member) => member.content["membership"] == "join")
.toIList();
Future<IList<Membership>> build() async {
if (room.metadata == null) return const IList.empty();
static final provider = NotifierProvider.family
.autoDispose<MembersController, IList<Event>, Room>(
final state = await ref
.watch(ClientController.provider.notifier)
.getRoomState(
GetRoomStateRequest(
roomId: room.metadata!.id,
fetchMembers: room.metadata!.hasMemberList == false,
includeMembers: true,
),
);
return state.nonNulls
.where((member) => member.content["membership"] == "join")
.map(
(membership) =>
Membership.fromContent(membership.content, membership.stateKey!),
)
.toIList();
}
static final provider =
AsyncNotifierProvider.family<MembersController, IList<Membership>, Room>(
MembersController.new,
);
}

View file

@ -2,9 +2,8 @@ import "package:collection/collection.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_state_controller.dart";
import "package:nexus/controllers/members_controller.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/models/message_config.dart";
import "package:nexus/models/configs/message_config.dart";
class MessageController extends AsyncNotifier<Message?> {
final MessageConfig config;
@ -27,12 +26,6 @@ class MessageController extends AsyncNotifier<Message?> {
if (!ref.mounted) return null;
final members = ref.read(MembersController.provider(config.room));
final author = members.firstWhereOrNull(
(member) => member.stateKey == event.authorId,
);
if (!ref.mounted) return null;
final content = (event.decrypted ?? event.content);
final type = (config.event.decryptedType ?? config.event.type);
final newContent = content["m.new_content"] as Map?;
@ -52,14 +45,11 @@ class MessageController extends AsyncNotifier<Message?> {
"timelineId": event.timelineRowId,
"big": event.localContent?.bigEmoji == true,
"eventType": type,
"avatarUrl": author?.content["avatar_url"],
"pmp": event.content["com.beeper.per_message_profile"],
"editSource":
event.localContent?.editSource ??
newContent?["body"] ??
content["body"],
"displayName": author?.content["displayname"]?.isNotEmpty == true
? author?.content["displayname"]
: event.authorId.substring(1).split(":")[0],
"txnId": config.event.transactionId,
};

View file

@ -2,8 +2,8 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/message_controller.dart";
import "package:nexus/models/message_config.dart";
import "package:nexus/models/messages_config.dart";
import "package:nexus/models/configs/message_config.dart";
import "package:nexus/models/configs/messages_config.dart";
class MessagesController extends AsyncNotifier<IList<Message>> {
final MessagesConfig config;

View file

@ -1,5 +1,4 @@
import "dart:async";
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
@ -11,8 +10,8 @@ import "package:nexus/controllers/message_controller.dart";
import "package:nexus/controllers/messages_controller.dart";
import "package:nexus/controllers/new_events_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/models/message_config.dart";
import "package:nexus/models/messages_config.dart";
import "package:nexus/models/configs/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/paginate_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();
final state = await client.getRoomState(
GetRoomStateRequest(
roomId: roomId,
fetchMembers: room.metadata?.hasMemberList == false,
includeMembers: true,
),
GetRoomStateRequest(roomId: roomId),
);
ref

View file

@ -36,6 +36,7 @@ class RoomsController extends Notifier<IMap<String, Room>> {
return acc.add(
roomId,
existing?.copyWith(
hasMore: incoming.hasMore,
metadata: incoming.metadata ?? existing.metadata,
events: events!,
state: incoming.state.entries.fold(

View file

@ -15,6 +15,7 @@ extension JoinRoomWithSnackbars on ClientController {
WidgetRef ref,
) async {
final roomIdOrAlias = roomAlias.mention ?? roomAlias;
// TODO: Parse vias properly
final scaffoldMessenger = ScaffoldMessenger.of(context);

View file

@ -18,7 +18,6 @@ import "package:nexus/widgets/loading.dart";
import "package:window_manager/window_manager.dart";
import "package:flutter/material.dart";
import "package:dynamic_system_colors/dynamic_system_colors.dart";
import "package:window_size/window_size.dart";
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@ -59,14 +58,11 @@ void showError(Object error, [StackTrace? stackTrace]) {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await windowManager.ensureInitialized();
await windowManager.waitUntilReadyToShow(
WindowOptions(titleBarStyle: TitleBarStyle.hidden),
);
if (Platform.isLinux) {
setWindowMinSize(const Size.square(500));
} else {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
await windowManager.ensureInitialized();
await windowManager.waitUntilReadyToShow(
WindowOptions(titleBarStyle: TitleBarStyle.hidden),
);
await windowManager.setMinimumSize(Size.square(500));
}

View 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);
}

View 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,
);
}

View file

@ -6,7 +6,7 @@ part "get_room_state_request.g.dart";
abstract class GetRoomStateRequest with _$GetRoomStateRequest {
const factory GetRoomStateRequest({
required String roomId,
required bool fetchMembers,
@Default(false) bool fetchMembers,
@Default(false) bool includeMembers,
}) = _GetRoomStateRequest;

View file

@ -16,7 +16,7 @@ class ChatPage extends ConsumerWidget {
body: Builder(
builder: (context) => Row(
children: [
if (isDesktop) Sidebar(),
if (isDesktop) Sidebar(isDesktop: isDesktop),
Expanded(
child: RoomChat(
isDesktop: isDesktop,
@ -26,7 +26,7 @@ class ChatPage extends ConsumerWidget {
],
),
),
drawer: isDesktop ? null : Sidebar(),
drawer: isDesktop ? null : Sidebar(isDesktop: isDesktop),
);
},
);

View file

@ -175,7 +175,7 @@ class LoginPage extends HookConsumerWidget {
ElevatedButton(
onPressed: () async {
isLoading.value = true;
final succeeded = await client.login(
final error = await client.login(
LoginRequest(
username: username.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(
SnackBar(
content: Text(
"Login failed. Is your password right?",
"Login failed. Is your password right?\nError: $error",
style: TextStyle(
color: theme.colorScheme.onErrorContainer,
),

View file

@ -2,6 +2,7 @@ import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/form_text_input.dart";
class VerifyPage extends HookConsumerWidget {
@ -11,72 +12,75 @@ class VerifyPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final passphraseController = useTextEditingController();
final isVerifying = useState(false);
return AlertDialog(
title: Text("Verify"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Enter your recovery key or passphrase below to unlock encrypted messages.\nYour passphrase is usually not the same as your password.",
),
SizedBox(height: 12),
FormTextInput(
required: false,
autofocus: true,
capitalize: true,
controller: passphraseController,
obscure: true,
title: "Recovery Key or Passphrase",
return Scaffold(
appBar: Appbar(),
body: AlertDialog(
title: Text("Verify"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Enter your recovery key or passphrase below to unlock encrypted messages.\nYour passphrase is usually not the same as your password.",
),
SizedBox(height: 12),
FormTextInput(
required: false,
autofocus: true,
capitalize: true,
controller: passphraseController,
obscure: true,
title: "Recovery Key or Passphrase",
),
],
),
actions: [
TextButton(
onPressed: isVerifying.value
? null
: () async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackbar = scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(
"Attempting to verify with recovery key...",
),
duration: Duration(days: 999),
),
);
isVerifying.value = true;
final error = await ref
.watch(ClientController.provider.notifier)
.verify(passphraseController.text);
snackbar.close();
if (error != null) {
isVerifying.value = false;
if (context.mounted) {
scaffoldMessenger.showSnackBar(
SnackBar(
backgroundColor: Theme.of(
context,
).colorScheme.errorContainer,
content: Text(
"Verification failed. Is your passphrase correct?\nError: $error",
style: TextStyle(
color: Theme.of(
context,
).colorScheme.onErrorContainer,
),
),
),
);
}
}
},
child: Text("Verify"),
),
],
),
actions: [
TextButton(
onPressed: isVerifying.value
? null
: () async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackbar = scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(
"Attempting to verify with recovery key...",
),
duration: Duration(days: 999),
),
);
isVerifying.value = true;
final success = await ref
.watch(ClientController.provider.notifier)
.verify(passphraseController.text);
snackbar.close();
if (!success) {
isVerifying.value = false;
if (context.mounted) {
scaffoldMessenger.showSnackBar(
SnackBar(
backgroundColor: Theme.of(
context,
).colorScheme.errorContainer,
content: Text(
"Verification failed. Is your passphrase correct?",
style: TextStyle(
color: Theme.of(
context,
).colorScheme.onErrorContainer,
),
),
),
);
}
}
},
child: Text("Verify"),
),
],
);
}
}

View file

@ -35,15 +35,14 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
}
return GestureDetector(
behavior: HitTestBehavior.translucent,
onDoubleTap: maximize,
onPanStart: (_) => windowManager.startDragging(),
child: AppBar(
leading: leading,
backgroundColor: backgroundColor,
scrolledUnderElevation: scrolledUnderElevation,
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
title: title,
title: IgnorePointer(child: title),
flexibleSpace: GestureDetector(onDoubleTap: maximize),
actions: [
...actions,
if (!(Platform.isAndroid || Platform.isIOS)) ...[

View file

@ -1,4 +1,3 @@
import "dart:io";
import "package:flutter/material.dart";
import "package:flutter/services.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/models/relation_type.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/chat_page/mention_overlay.dart";
import "package:nexus/widgets/chat_page/relation_preview.dart";
import "package:nexus/widgets/chat_page/composer/mention_overlay.dart";
import "package:nexus/widgets/chat_page/composer/relation_preview.dart";
class ChatBox extends HookConsumerWidget {
final Message? relatedMessage;
@ -55,20 +54,15 @@ class ChatBox extends HookConsumerWidget {
final node = useFocusNode(
onKeyEvent: (_, event) {
if (event is KeyDownEvent && !Platform.isAndroid && !Platform.isIOS) {
if (event.logicalKey == LogicalKeyboardKey.enter &&
!HardwareKeyboard.instance.isShiftPressed) {
send();
return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
onDismiss();
return KeyEventResult.handled;
}
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.escape) {
onDismiss();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
)..requestFocus();
);
final style = TextStyle(
color: theme.colorScheme.primary,
@ -86,10 +80,11 @@ class ChatBox extends HookConsumerWidget {
child: Column(
children: [
RelationPreview(
relatedMessage,
room: room,
shouldMention: shouldMention.value,
toggleShouldMention: () =>
shouldMention.value = !shouldMention.value,
relatedMessage: relatedMessage,
relationType: relationType,
onDismiss: onDismiss,
),
@ -155,7 +150,9 @@ class ChatBox extends HookConsumerWidget {
),
controller: controller.value,
key: key,
autofocus: true,
// TODO: Setting for send on enter on / off
onFieldSubmitted: (_) => send(),
textInputAction: TextInputAction.done,
focusNode: node,
),
),

View file

@ -2,6 +2,7 @@ import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/members_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/loading.dart";
@ -31,55 +32,47 @@ class MentionOverlay extends ConsumerWidget {
color: Theme.of(context).colorScheme.surfaceContainerHigh,
padding: EdgeInsets.all(8),
child: switch (triggerCharacter) {
"@" => Consumer(
builder: (_, ref, _) {
final members = ref.watch(MembersController.provider(room));
return ListView(
children:
(query.isEmpty
? members
: members.where(
(member) =>
member.stateKey?.toLowerCase().contains(
query.toLowerCase(),
) ==
true ||
(member.content["displayname"] as String?)
?.toLowerCase()
.contains(query.toLowerCase()) ==
true,
))
.map(
(member) => ListTile(
leading: AvatarOrHash(
Uri.tryParse(
member.content["avatar_url"] ?? "",
),
member.content["displayname"] ?? "",
),
title: Text(
member.content["displayname"] as String? ??
member.stateKey ??
"Unknown User",
),
subtitle: member.stateKey != null
? Text(member.stateKey!)
: null,
onTap: () => addTag(
id: "[@${member.content["displayname"]}](https://matrix.to/#/${member.stateKey})",
name:
member.stateKey
?.substring(1)
"@" =>
ref
.watch(MembersController.provider(room))
.betterWhen(
data: (members) => ListView(
children:
(query.isEmpty
? members
: members.where(
(member) =>
member.userId.toLowerCase().contains(
query.toLowerCase(),
) ==
true ||
member.displayName
.toLowerCase()
.contains(
query.toLowerCase(),
) ==
true,
))
.map(
(member) => ListTile(
leading: AvatarOrHash(
member.avatarUrl,
member.displayName,
),
title: Text(member.displayName),
subtitle: Text(member.userId),
onTap: () => addTag(
id: "[@${member.displayName}](https://matrix.to/#/${member.userId})",
name: member.userId
.substring(1)
.split(":")
.first ??
"Unknown User",
),
),
)
.toList(),
);
},
),
.first,
),
),
)
.toList(),
),
),
"#" => ListView(
children:
(query.isEmpty

View file

@ -2,7 +2,9 @@ import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/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 {
final Message? relatedMessage;
@ -10,8 +12,11 @@ class RelationPreview extends ConsumerWidget {
final VoidCallback onDismiss;
final bool shouldMention;
final VoidCallback toggleShouldMention;
const RelationPreview({
required this.relatedMessage,
final Room room;
const RelationPreview(
this.relatedMessage, {
required this.room,
required this.relationType,
required this.onDismiss,
required this.shouldMention,
@ -36,14 +41,10 @@ class RelationPreview extends ConsumerWidget {
"Editing message:",
style: TextStyle(fontWeight: FontWeight.bold),
),
AvatarOrHash(
Uri.tryParse(relatedMessage?.metadata?["avatarUrl"] ?? ""),
relatedMessage?.metadata?["displayName"]?.toString() ?? "",
height: 16,
),
Text(
relatedMessage!.metadata?["displayName"] ??
relatedMessage!.authorId,
MessageAvatar(relatedMessage!, room),
MessageDisplayname(
relatedMessage!,
room,
style: theme.textTheme.labelMedium?.copyWith(
fontWeight: FontWeight.bold,
),

View file

@ -22,6 +22,10 @@ class Html extends ConsumerWidget {
html,
textStyle: textStyle,
customWidgetBuilder: (element) {
if (element.attributes.keys.contains("data-mx-profile-fallback")) {
return SizedBox.shrink();
}
if (element.attributes.keys.contains("data-mx-spoiler")) {
return InlineCustomWidget(child: SpoilerText(text: element.text));
}

View 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),
);
}

View 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(""),
);
}

View file

@ -1,6 +1,7 @@
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/members_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
@ -10,15 +11,17 @@ class MemberList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final members = ref.watch(MembersController.provider(room));
final membersProvider = ref.watch(MembersController.provider(room));
return Drawer(
shape: Border(),
child: ListView(
child: Column(
children: [
AppBar(
scrolledUnderElevation: 0,
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),
actions: [
if (Scaffold.of(context).hasEndDrawer)
@ -29,24 +32,32 @@ class MemberList extends ConsumerWidget {
),
],
),
...members.map(
(member) => ListTile(
onTap: () => showDialog(
context: context,
builder: (context) =>
Dialog(child: Text("TODO: Open member popover")),
),
leading: AvatarOrHash(
Uri.tryParse(member.content["avatar_url"] ?? ""),
member.content["displayname"].toString(),
),
title: Text(
member.content["displayname"].toString(),
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
member.stateKey ?? "Unknown User",
overflow: TextOverflow.ellipsis,
membersProvider.betterWhen(
data: (members) => Expanded(
child: ListView(
children: members
.map(
(member) => ListTile(
onTap: () => showDialog(
context: context,
builder: (context) =>
Dialog(child: Text("TODO: Open member popover")),
),
leading: AvatarOrHash(
member.avatarUrl,
member.displayName,
),
title: Text(
member.displayName,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
member.userId,
overflow: TextOverflow.ellipsis,
),
),
)
.toList(),
),
),
),

View file

@ -1,15 +1,15 @@
import "dart:math";
import "package:flutter/material.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/event_controller.dart";
import "package:nexus/controllers/message_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/message_config.dart";
import "package:nexus/models/configs/message_config.dart";
import "package:nexus/models/requests/get_event_request.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/chat_page/html/quoted.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
typedef OnTapReply = void Function(Message message)?;
@ -61,73 +61,28 @@ class ReplyWidget extends ConsumerWidget {
return SizedBox.shrink();
}
final smallerText =
message is TextMessage &&
replyMessage.metadata?["body"] != null
? replyMessage.metadata!["body"].substring(
0,
min(
max(
max(
(message as TextMessage)
.text
.length -
(replyMessage
.metadata?["displayName"]
as String)
.length -
5,
message
.metadata?["displayName"]
.length,
),
5,
),
replyMessage.metadata!["body"].length,
),
)
: null;
final replyText =
(smallerText == null ||
smallerText.length ==
replyMessage
.metadata!["body"]
.length)
? replyMessage.metadata!["body"]
: "$smallerText...";
return InkWell(
onTap: () => onTapReply?.call(replyMessage),
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
AvatarOrHash(
Uri.tryParse(
replyMessage.metadata?["avatarUrl"] ??
"",
),
replyMessage.metadata?["displayName"] ??
"",
height: 16,
),
MessageAvatar(replyMessage, room),
Flexible(
child: Text(
replyMessage
.metadata?["displayName"] ??
replyMessage.authorId,
child: MessageDisplayname(
replyMessage,
room,
style: Theme.of(context)
.textTheme
.labelMedium
?.copyWith(
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
),
Flexible(
child: Text(
replyText,
replyMessage.metadata!["body"],
overflow: TextOverflow.ellipsis,
style: Theme.of(
context,

View file

@ -13,15 +13,14 @@ import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/show_context_menu.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/models/requests/report_request.dart";
import "package:nexus/widgets/chat_page/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/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/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/form_text_input.dart";
import "package:nexus/widgets/loading.dart";
// import "package:dynamic_polls/dynamic_polls.dart";
class RoomChat extends HookConsumerWidget {
@ -233,7 +232,7 @@ class RoomChat extends HookConsumerWidget {
children: getMessageOptions(message),
),
builders: Builders(
loadMoreBuilder: (_) => Loading(),
loadMoreBuilder: (_) => SizedBox.shrink(),
chatAnimatedListBuilder: (_, itemBuilder) =>
ChatAnimatedList(
@ -320,6 +319,7 @@ class RoomChat extends HookConsumerWidget {
),
),
groupStatus,
room,
),
systemMessageBuilder:

View file

@ -12,7 +12,8 @@ import "package:nexus/widgets/chat_page/room_menu.dart";
import "package:nexus/widgets/form_text_input.dart";
class Sidebar extends HookConsumerWidget {
const Sidebar({super.key});
final bool isDesktop;
const Sidebar({required this.isDesktop, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -220,9 +221,12 @@ class Sidebar extends HookConsumerWidget {
),
)
.toList(),
onDestinationSelected: (value) => selectedRoomIdNotifier.set(
selectedSpace.children[value].metadata?.id,
),
onDestinationSelected: (value) {
selectedRoomIdNotifier.set(
selectedSpace.children[value].metadata?.id,
);
if (!isDesktop) Navigator.of(context).pop();
},
),
),
),

View file

@ -1,12 +1,21 @@
import "package:flutter/material.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 {
final Message message;
final Widget child;
final Room room;
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
Widget build(BuildContext context) => ClipRRect(
@ -24,11 +33,7 @@ class MessageWrapper extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
groupStatus?.isFirst != false
? AvatarOrHash(
Uri.parse(message.metadata?["avatarUrl"] ?? ""),
height: 40,
message.metadata?["displayName"] ?? "",
)
? MessageAvatar(message, room, height: 40)
: SizedBox(width: 40),
Expanded(
child: Column(
@ -36,9 +41,9 @@ class MessageWrapper extends StatelessWidget {
spacing: 4,
children: [
if (groupStatus?.isFirst != false)
Text(
message.metadata?["displayName"] ?? message.authorId,
overflow: TextOverflow.ellipsis,
MessageDisplayname(
message,
room,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),

View file

@ -3,7 +3,7 @@ import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_link_previewer/flutter_link_previewer.dart";
import "package:nexus/models/room.dart";
import "package:nexus/widgets/chat_page/html/html.dart";
import "package:nexus/widgets/chat_page/message_wrapper.dart";
import "package:nexus/widgets/chat_page/wrappers/message_wrapper.dart";
import "package:nexus/widgets/chat_page/reply_widget.dart";
class TextMessageWrapper extends StatelessWidget {
@ -109,6 +109,7 @@ class TextMessageWrapper extends StatelessWidget {
),
),
groupStatus,
room,
);
}
}

View file

@ -7,7 +7,7 @@ project(runner LANGUAGES CXX)
set(BINARY_NAME "nexus")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "nexus.federated.nexus")
set(APPLICATION_ID "nexus.federated.Nexus")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.

View file

@ -11,7 +11,6 @@
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>
#include <window_size/window_size_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dynamic_system_colors_registrar =
@ -29,7 +28,4 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) window_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
window_manager_plugin_register_with_registrar(window_manager_registrar);
g_autoptr(FlPluginRegistrar) window_size_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
window_size_plugin_register_with_registrar(window_size_registrar);
}

View file

@ -8,7 +8,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever_linux
url_launcher_linux
window_manager
window_size
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View 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
View 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
View 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
View 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
'';
})

View file

@ -43,6 +43,7 @@ static void my_application_activate(GApplication* application) {
}
}
#endif
gtk_widget_set_size_request(GTK_WIDGET(window), 500, 500);
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));

View file

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

View file

@ -29,10 +29,10 @@ packages:
dependency: transitive
description:
name: analyzer_buffer
sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033
sha256: "5fcd06b0715ebeee99f03e3f437b3412249969d8d12b191ea8a1d76e42a4e4a1"
url: "https://pub.dev"
source: hosted
version: "0.1.11"
version: "0.3.1"
analyzer_plugin:
dependency: transitive
description:
@ -348,10 +348,11 @@ packages:
dynamic_system_colors:
dependency: "direct main"
description:
name: dynamic_system_colors
sha256: "43794e658fa88cbdec9f397dd1afd2eb69b6c9717e99b93b16ba37c3aa3b3a8c"
url: "https://pub.dev"
source: hosted
path: "."
ref: HEAD
resolved-ref: "3b61760d5e0ac1229eefde5b61247947eede4110"
url: "https://github.com/hasali19/flutter_dynamic_system_colors"
source: git
version: "1.8.0"
encrypt:
dependency: transitive
@ -521,10 +522,10 @@ packages:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02"
sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.3.1"
flutter_svg:
dependency: "direct main"
description:
@ -643,10 +644,10 @@ packages:
dependency: "direct main"
description:
name: hooks_riverpod
sha256: b880efcd17757af0aa242e5dceac2fb781a014c22a32435a5daa8f17e9d5d8a9
sha256: "08527ec06aaef75e4b78694e045ef0cd8346594eaf9cc18b0f866398b07b93b1"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.3.1"
html:
dependency: transitive
description:
@ -1051,26 +1052,26 @@ packages:
dependency: transitive
description:
name: riverpod
sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a"
sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.2.1"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0"
sha256: e55bc08c084a424e1bbdc303fe8ea75daafe4269b68fd0e0f6f1678413715b66
url: "https://pub.dev"
source: hosted
version: "1.0.0-dev.8"
version: "1.0.0-dev.9"
riverpod_lint:
dependency: "direct dev"
description:
name: riverpod_lint
sha256: "4d2eb0d19bbe7e3323bd0ce4553b2e6170d161a13914bfdd85a3612329edcb43"
sha256: "64e8debf5b719a37d48b9785dd595d34133fdcd84b8fd07157a621c54ab2156f"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.3"
rxdart:
dependency: transitive
description:
@ -1548,15 +1549,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.1"
window_size:
dependency: "direct main"
description:
path: "plugins/window_size"
ref: HEAD
resolved-ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
url: "https://github.com/google/flutter-desktop-embedding"
source: git
version: "0.1.0"
xdg_directories:
dependency: transitive
description:

View file

@ -21,8 +21,8 @@ dependencies:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter_riverpod: ^3.0.3
hooks_riverpod: ^3.0.3
flutter_riverpod: ^3.3.1
hooks_riverpod: ^3.3.1
intl: ^0.20.1
fast_immutable_collections: ^11.0.0
path_provider: ^2.1.3
@ -31,13 +31,11 @@ dependencies:
image_picker: ^1.1.2
file_picker: ^10.3.3
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
window_manager: ^0.5.1
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding
path: plugins/window_size
flutter_chat_core: ^2.0.0
flyer_chat_image_message: ^2.2.2
flyer_chat_system_message: ^2.1.13
@ -69,7 +67,7 @@ dev_dependencies:
custom_lint: ^0.8.0
flutter_lints: ^6.0.0
freezed: ^3.2.3
riverpod_lint: ^3.0.3
riverpod_lint: ^3.1.3
flutter_launcher_icons: ^0.14.1
json_serializable: ^6.11.1
@ -77,7 +75,7 @@ flutter_launcher_icons:
ios: true
android: true
image_path: assets/icon.png
adaptive_icon_background: "#000000"
adaptive_icon_background: assets/background.png
adaptive_icon_foreground: assets/foreground.png
remove_alpha_ios: true
windows:

View file

@ -3,26 +3,7 @@ import "package:ffigen/ffigen.dart";
import "package:path/path.dart";
void main(List<String> args) async {
final repoDir = Directory.fromUri(
Platform.script.resolve("../src/gomuks/source"),
);
if (await repoDir.exists()) await repoDir.delete(recursive: true);
await repoDir.create(recursive: true);
print("Cloning Gomuks repository...");
final cloneResult = await Process.run("git", [
"clone",
"--depth",
"1",
"https://mau.dev/gomuks/gomuks",
repoDir.path,
]);
if (cloneResult.exitCode != 0) {
throw Exception(
"Failed to clone Gomuks repository: \n${cloneResult.stderr}",
);
}
final repoDir = Directory.fromUri(Platform.script.resolve("../gomuks"));
print("Generating FFI Bindings...");

View file

@ -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

View file

@ -11,7 +11,6 @@
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h>
#include <window_size/window_size_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
DynamicColorPluginCApiRegisterWithRegistrar(
@ -24,6 +23,4 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
WindowSizePluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowSizePlugin"));
}

View file

@ -8,7 +8,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever_windows
url_launcher_windows
window_manager
window_size
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View file

@ -89,11 +89,11 @@ BEGIN
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "nexus.federated.nexus" "\0"
VALUE "CompanyName", "nexus.federated.Nexus" "\0"
VALUE "FileDescription", "nexus" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "nexus" "\0"
VALUE "LegalCopyright", "Copyright (C) 2025 nexus.federated.nexus. All rights reserved." "\0"
VALUE "LegalCopyright", "Copyright (C) 2025 nexus.federated.Nexus. All rights reserved." "\0"
VALUE "OriginalFilename", "nexus.exe" "\0"
VALUE "ProductName", "nexus" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"