forked from Nexus/nexus
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
115bec5f05 |
|||
|
fc4aa5a10d |
|||
|
f3c31db244 |
|||
|
419d75ca83 |
|||
|
9831446c66 |
|||
|
d7f49568a7 |
|||
|
f0f426a647 |
|||
|
80fa9673bb |
|||
|
8c25228bd9 |
|||
|
9bf664624a |
|||
|
e8b90591d5 |
|||
|
b2813a0eb5 |
|||
|
9298eb815d |
|||
|
06075f75c5 |
|||
|
60968f5f3e |
|||
|
afd6168535 |
|||
|
2a1cc33ee3 |
13 changed files with 142 additions and 62 deletions
32
.github/workflows/macos.yml
vendored
Normal file
32
.github/workflows/macos.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
name: "Build MacOS App"
|
||||||
|
|
||||||
|
on:
|
||||||
|
# TODO: Uncomment this once the MacOS workflow actually works
|
||||||
|
# push:
|
||||||
|
# branches: ["main"]
|
||||||
|
# tags: ["*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-app:
|
||||||
|
runs-on: macos-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: Build app
|
||||||
|
run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build && flutter build macos --release"
|
||||||
|
|
||||||
|
- name: Upload installer artifact
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: App
|
||||||
|
path: build/macos/Build/Products/Release/Nexus.app
|
||||||
|
|
@ -127,15 +127,16 @@ If you want to try out Nexus, grab one of the following artifacts from CI:
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
- With Nix: Either use direnv and `direnv allow`, or `nix flake develop`
|
- With Nix: Either use direnv and `direnv allow`, or `nix flake develop`
|
||||||
- Without Nix: Install Flutter, Go, Git, Libclang, and Glibc. Do not use any Snap packages, they cause various compilation issues.
|
- Without Nix: Install Flutter, Go, Git, Libclang, Libass, MPV, and Glibc. Do not use any Snap packages, they cause various compilation issues.
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
You will need:
|
You will need:
|
||||||
|
|
||||||
- Flutter
|
- Flutter
|
||||||
- Android SDK + NDK
|
|
||||||
- Git
|
- Git
|
||||||
|
- Libass
|
||||||
|
- MPV
|
||||||
- Go
|
- Go
|
||||||
- Visual Studio 2022 (Desktop development with C++)
|
- Visual Studio 2022 (Desktop development with C++)
|
||||||
- [MSYS2/MinGW-w64 GCC](https://www.msys2.org/) (for CGO)
|
- [MSYS2/MinGW-w64 GCC](https://www.msys2.org/) (for CGO)
|
||||||
|
|
@ -202,7 +203,7 @@ Development instructions can be found in [DEVELOPMENT.md](./DEVELOPMENT.md).
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
Join the [Nexus Client Matrix Room](https://matrix.to/#/#nexus:federated.nexus) for questions or help with developing or using Nexus Client.
|
Join the [Nexus Client Matrix room](https://matrix.to/#/#nexus:federated.nexus) for questions or help with developing or using Nexus Client.
|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,16 @@ class AttachmentController extends Notifier<(String, MessageContent?)?> {
|
||||||
final filename = basename(file.path);
|
final filename = basename(file.path);
|
||||||
state = (filename, null);
|
state = (filename, null);
|
||||||
|
|
||||||
final room = ref.watch(
|
final isEncrypted = ref.read(
|
||||||
RoomsController.provider.select((value) => value[roomId]),
|
RoomsController.provider.select(
|
||||||
|
(value) =>
|
||||||
|
value[roomId]?.state[EventType.encryption.type]?.isNotEmpty == true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final content = await ref
|
final content = await ref
|
||||||
.watch(ClientController.provider.notifier)
|
.watch(ClientController.provider.notifier)
|
||||||
.uploadMedia(
|
.uploadMedia(.new(path: file.path, encrypt: isEncrypted));
|
||||||
.new(
|
|
||||||
path: file.path,
|
|
||||||
encrypt: room?.state[EventType.encryption.type]?.isNotEmpty == true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
state = (filename, content);
|
state = (filename, content);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
lib/controllers/member_list_opened.dart
Normal file
22
lib/controllers/member_list_opened.dart
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/shared_prefs.dart";
|
||||||
|
|
||||||
|
class MemberListOpenedController extends Notifier<bool> {
|
||||||
|
static const String key = "memberListOpened";
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool build() =>
|
||||||
|
ref.watch(SharedPrefsController.provider).requireValue.getBool(key) ??
|
||||||
|
true;
|
||||||
|
|
||||||
|
Future<void> set(bool value) async {
|
||||||
|
final prefs = ref.watch(SharedPrefsController.provider).requireValue;
|
||||||
|
state = value;
|
||||||
|
|
||||||
|
prefs.setBool(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider = NotifierProvider<MemberListOpenedController, bool>(
|
||||||
|
MemberListOpenedController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -125,11 +125,9 @@ class RoomChatController extends AsyncNotifier<IList<Event>?> {
|
||||||
if (relationType == .edit) {
|
if (relationType == .edit) {
|
||||||
baseContent = relation?.content;
|
baseContent = relation?.content;
|
||||||
} else {
|
} else {
|
||||||
final attachment = ref.watch(AttachmentController.provider(roomId));
|
final provider = AttachmentController.provider(roomId);
|
||||||
if (attachment != null && attachment.$2 == null) return;
|
baseContent = ref.watch(provider)?.$2;
|
||||||
|
ref.invalidate(provider);
|
||||||
baseContent = attachment?.$2;
|
|
||||||
ref.invalidate(AttachmentController.provider(roomId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var taggedMessage = text;
|
var taggedMessage = text;
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,10 @@ class Composer extends HookConsumerWidget {
|
||||||
final attachment = ref.watch(AttachmentController.provider(roomId));
|
final attachment = ref.watch(AttachmentController.provider(roomId));
|
||||||
|
|
||||||
void send() {
|
void send() {
|
||||||
if (controller.value.text.isEmpty && attachment == null) return;
|
if (controller.value.text.isEmpty && attachment == null ||
|
||||||
|
attachment != null && attachment.$2 == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
onSend(
|
onSend(
|
||||||
controller.value.formattedText,
|
controller.value.formattedText,
|
||||||
shouldMention: shouldMention.value,
|
shouldMention: shouldMention.value,
|
||||||
|
|
|
||||||
|
|
@ -216,10 +216,13 @@ class EventRenderer extends HookConsumerWidget {
|
||||||
if (event.content is! MessageContent) ReactionRow(event),
|
if (event.content is! MessageContent) ReactionRow(event),
|
||||||
|
|
||||||
if (event.sendError != null && event.sendError != "not sent")
|
if (event.sendError != null && event.sendError != "not sent")
|
||||||
Text(
|
Padding(
|
||||||
event.sendError!,
|
padding: .only(bottom: 4),
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
child: Text(
|
||||||
color: theme.colorScheme.error,
|
event.sendError!,
|
||||||
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: theme.colorScheme.error,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
].map((child) => Padding(padding: .only(left: 4), child: child)),
|
].map((child) => Padding(padding: .only(left: 4), child: child)),
|
||||||
|
|
|
||||||
|
|
@ -286,9 +286,8 @@ class MessageRenderer extends ConsumerWidget {
|
||||||
"This message is redacted",
|
"This message is redacted",
|
||||||
style: errorStyle,
|
style: errorStyle,
|
||||||
)
|
)
|
||||||
: Row(
|
: Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
mainAxisSize: .min,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Unknown message type:",
|
"Unknown message type:",
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import "package:measure_size/measure_size.dart";
|
||||||
import "package:nexus/controllers/account_data.dart";
|
import "package:nexus/controllers/account_data.dart";
|
||||||
import "package:nexus/controllers/client.dart";
|
import "package:nexus/controllers/client.dart";
|
||||||
import "package:nexus/controllers/client_state.dart";
|
import "package:nexus/controllers/client_state.dart";
|
||||||
|
import "package:nexus/controllers/member_list_opened.dart";
|
||||||
import "package:nexus/controllers/pinned_ids.dart";
|
import "package:nexus/controllers/pinned_ids.dart";
|
||||||
import "package:nexus/controllers/power_level.dart";
|
import "package:nexus/controllers/power_level.dart";
|
||||||
import "package:nexus/controllers/rooms.dart";
|
import "package:nexus/controllers/rooms.dart";
|
||||||
|
|
@ -46,9 +47,8 @@ class RoomChat extends HookConsumerWidget {
|
||||||
|
|
||||||
final composerSize = useState<double>(64);
|
final composerSize = useState<double>(64);
|
||||||
|
|
||||||
final memberListOpened = useState<bool>(showMembersByDefault);
|
|
||||||
|
|
||||||
final userId = ref.watch(ClientStateController.provider)?.userId;
|
final userId = ref.watch(ClientStateController.provider)?.userId;
|
||||||
|
final memberListOpened = ref.watch(MemberListOpenedController.provider);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
final nothing = Center(
|
final nothing = Center(
|
||||||
|
|
@ -448,7 +448,9 @@ class RoomChat extends HookConsumerWidget {
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
onOpenDrawer: Scaffold.of(context).openDrawer,
|
onOpenDrawer: Scaffold.of(context).openDrawer,
|
||||||
onOpenMemberList: (thisContext) {
|
onOpenMemberList: (thisContext) {
|
||||||
memberListOpened.value = !memberListOpened.value;
|
ref
|
||||||
|
.watch(MemberListOpenedController.provider.notifier)
|
||||||
|
.set(!memberListOpened);
|
||||||
Scaffold.of(thisContext).openEndDrawer();
|
Scaffold.of(thisContext).openEndDrawer();
|
||||||
},
|
},
|
||||||
onOpenPinnedMessagesList: () {
|
onOpenPinnedMessagesList: () {
|
||||||
|
|
@ -553,7 +555,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (memberListOpened.value == true && showMembersByDefault)
|
if (memberListOpened == true && showMembersByDefault)
|
||||||
MemberList(roomId),
|
MemberList(roomId),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -18,25 +18,25 @@ let
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages =
|
||||||
go
|
with pkgs;
|
||||||
git
|
[
|
||||||
jdk17
|
go
|
||||||
libGL
|
git
|
||||||
wayland
|
jdk17
|
||||||
(flutter.override {
|
libGL
|
||||||
extraPkgConfigPackages = [
|
(flutter.override {
|
||||||
mpv-unwrapped
|
extraPkgConfigPackages = [
|
||||||
libass
|
mpv-unwrapped
|
||||||
];
|
libass
|
||||||
})
|
];
|
||||||
android.platform-tools
|
})
|
||||||
];
|
android.platform-tools
|
||||||
|
]
|
||||||
|
++ lib.optional pkgs.stdenv.isLinux wayland;
|
||||||
|
|
||||||
env = rec {
|
env = rec {
|
||||||
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.libclang ];
|
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_HOME = "${android.androidsdk}/libexec/android-sdk";
|
||||||
ANDROID_SDK_ROOT = ANDROID_HOME;
|
ANDROID_SDK_ROOT = ANDROID_HOME;
|
||||||
|
|
@ -44,5 +44,9 @@ pkgs.mkShell {
|
||||||
|
|
||||||
TOOLS = "${ANDROID_HOME}/build-tools/${"36.0.0"}";
|
TOOLS = "${ANDROID_HOME}/build-tools/${"36.0.0"}";
|
||||||
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${TOOLS}/aapt2";
|
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${TOOLS}/aapt2";
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||||
|
CPATH = lib.makeSearchPath "include" [ pkgs.glibc.dev ];
|
||||||
|
LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ flutter.buildFlutterApplication {
|
||||||
meta = {
|
meta = {
|
||||||
description = "A simple and user-friendly Matrix client";
|
description = "A simple and user-friendly Matrix client";
|
||||||
mainProgram = "nexus";
|
mainProgram = "nexus";
|
||||||
platforms = lib.platforms.linux;
|
platforms = lib.platforms.unix;
|
||||||
maintainers = with lib.maintainers; [ quadradical ];
|
maintainers = with lib.maintainers; [ quadradical ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,38 @@
|
||||||
{
|
{
|
||||||
src,
|
src,
|
||||||
|
stdenv,
|
||||||
buildGoModule,
|
buildGoModule,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
buildGoModule (finalAttrs: {
|
buildGoModule (
|
||||||
pname = "gomuks-ffi";
|
finalAttrs:
|
||||||
version = "submodule";
|
let
|
||||||
|
filename = "libgomuks${stdenv.hostPlatform.extensions.sharedLibrary}";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
pname = "gomuks-ffi";
|
||||||
|
version = "submodule";
|
||||||
|
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
|
|
||||||
src = "${src}/gomuks";
|
src = "${src}/gomuks";
|
||||||
|
|
||||||
vendorHash = "sha256-4QU51WGiNKYm7z/ajnas2qQaMK5BgyHwPfmNpQvW0qg=";
|
vendorHash = "sha256-4QU51WGiNKYm7z/ajnas2qQaMK5BgyHwPfmNpQvW0qg=";
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
runHook preBuild
|
runHook preBuild
|
||||||
|
|
||||||
go build -buildmode=c-shared -o libgomuks.so -tags goolm,noheic,sqlite_fts5 ./pkg/ffi
|
go build -buildmode=c-shared -o ${filename} -tags goolm,noheic,sqlite_fts5 ./pkg/ffi
|
||||||
|
|
||||||
runHook postBuild
|
runHook postBuild
|
||||||
'';
|
'';
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
|
|
||||||
install -Dm0644 libgomuks.so -t $out/lib
|
install -Dm0644 ${filename} -t $out/lib
|
||||||
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,18 @@ void main(List<String> args) async {
|
||||||
).generate(
|
).generate(
|
||||||
libclangDylib: libclangPath == null
|
libclangDylib: libclangPath == null
|
||||||
? null
|
? null
|
||||||
: Uri.file(join(libclangPath, "libclang.so")),
|
: Uri.file(
|
||||||
|
join(
|
||||||
|
libclangPath,
|
||||||
|
"libclang.${(Platform.isLinux || Platform.isAndroid)
|
||||||
|
? "so"
|
||||||
|
: Platform.isMacOS
|
||||||
|
? "dylib"
|
||||||
|
: Platform.isWindows
|
||||||
|
? "dll"
|
||||||
|
: throw UnsupportedError("Unsupported Platform")}",
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
print("Done!");
|
print("Done!");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue