Compare commits

..
52 changed files with 347 additions and 455 deletions

View file

@ -1,48 +1,50 @@
name: "Build iOS App"
name: "Build IPA"
on:
push:
branches: ["main"]
tags: ["*"]
workflow_dispatch:
push:
branches: ["main"]
tags: ["*"]
workflow_dispatch:
jobs:
build-app:
runs-on: macos-latest
build-app:
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
submodules: recursive
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: 3.44.4
- 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: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: gomuks/go.mod
- name: Build app
run: |
nix develop --command bash -c '
set -e
flutter pub get
dart scripts/generate.dart
flutter pub run build_runner build
- name: Build app
run: |
flutter pub get
dart scripts/generate.dart
flutter pub run build_runner build
flutter build ios --release --no-codesign
FLUTTER_ROOT="$(dirname "$(dirname "$(readlink -f "$(command -v flutter)")")")"
sudo find -L "$FLUTTER_ROOT" -type d -iname "*.framework" -exec chmod -R u+w {} +
# Unsigned IPA: SideStore/AltStore re-sign this on-device with the
# user's own Apple ID at install time, so no certificate is needed here.
- name: Package unsigned IPA
run: |
mkdir Payload
cp -r build/ios/iphoneos/Runner.app Payload/
zip -r Nexus.ipa Payload
flutter build ios --release --no-codesign
'
- name: Upload IPA artifact
uses: actions/upload-artifact@v6
with:
name: Nexus.ipa
path: Nexus.ipa
# Unsigned IPA: SideStore/AltStore re-sign this on-device with the
# user's own Apple ID at install time, so no certificate is needed here.
- name: Package unsigned IPA
run: |
mkdir Payload
cp -r build/ios/iphoneos/Runner.app Payload/
zip -r Nexus.ipa Payload
- name: Upload IPA artifact
uses: actions/upload-artifact@v6
with:
name: Nexus.ipa
path: Nexus.ipa

View file

@ -1,47 +1,42 @@
name: "Build MacOS App"
on:
push:
branches: ["main"]
tags: ["*"]
workflow_dispatch:
push:
branches: ["main"]
tags: ["*"]
workflow_dispatch:
jobs:
build-app:
runs-on: macos-latest
build-app:
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
submodules: recursive
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: 3.44.4
- 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: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: gomuks/go.mod
- name: Build app
run: |
nix develop --command bash -c '
set -e
flutter pub get
dart scripts/generate.dart
flutter pub run build_runner build
- name: Build App
run: |
flutter pub get
dart scripts/generate.dart
flutter pub run build_runner build
flutter build macos --release
FLUTTER_ROOT="$(dirname "$(dirname "$(readlink -f "$(command -v flutter)")")")"
sudo find -L "$FLUTTER_ROOT" -type d -iname "*.framework" -exec chmod -R u+w {} +
- name: Create DMG
id: create-dmg
uses: L-Super/create-dmg-actions@28511e988b13ca34096d439159a66d468b8e724f
with:
dmg_name: nexus
src_dir: build/macos/Build/Products/Release/Nexus.app
flutter build macos --release
'
- name: Upload DMG
uses: actions/upload-artifact@v6
with:
name: nexus.dmg
path: ${{ steps.create-dmg.outputs.dmg_path }}
- name: Upload installer artifact
uses: actions/upload-artifact@v6
with:
name: App
path: build/macos/Build/Products/Release/Nexus.app

3
.gitignore vendored
View file

@ -42,3 +42,6 @@ password.txt
# Nix
/result
# Local IPA builds
*.ipa

View file

@ -15,12 +15,12 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend.
## Progress
- [x] Platform Support
- [ ] Platform Support
- [x] Linux
- [x] Windows - Currently has some bugs, due to us not having any developers on Windows. If you want to fix these, get in contact with us though the [Nexus Client Matrix room](https://matrix.to/#/#nexus:federated.nexus).
- [x] Windows
- [x] Android
- [x] MacOS - Unsigned .dmg only
- [x] iOS - Unsigned .ipa only
- [ ] MacOS ([#50](https://git.federated.nexus/Nexus/nexus/issues/50))
- [ ] iOS
- [ ] Web (may not be possible)
- [x] Login (via OAuth)
- [x] Rooms / Spaces
@ -116,9 +116,6 @@ If you want to try out Nexus, grab one of the following artifacts from CI:
- [Android APK](https://nightly.link/Henry-Hiles/nexus/workflows/android/main/APK.zip)
- [Windows EXE](https://nightly.link/Henry-Hiles/nexus/workflows/windows/main/windows-installer.zip)
- [Unsigned iOS IPA](https://nightly.link/Henry-Hiles/nexus/workflows/ios/main/Nexus.ipa.zip)
- [Unsigned MacOS DMG](https://nightly.link/Henry-Hiles/nexus/workflows/macos/main/nexus.dmg.zip)
- [Windows EXE](https://nightly.link/Henry-Hiles/nexus/workflows/windows/main/windows-installer.zip)
- Flatpak
- [AArch64/Arm64](https://nightly.link/Henry-Hiles/nexus/workflows/flatpak/main/flatpak-aarch64.zip)
- [x86_64/AMD64](https://nightly.link/Henry-Hiles/nexus/workflows/flatpak/main/flatpak-x86_64.zip)
@ -129,8 +126,8 @@ If you want to try out Nexus, grab one of the following artifacts from CI:
#### Linux
- On NixOS: Either use direnv and `direnv allow`, or `nix flake develop`
- On other distros: Install Flutter, Go, Git, Libclang, Libass, MPV, and Glibc. Do not use any Snap packages, they cause various compilation issues.
- With Nix: Either use direnv and `direnv allow`, or `nix flake develop`
- Without Nix: Install Flutter, Go, Git, Libclang, Libass, MPV, and Glibc. Do not use any Snap packages, they cause various compilation issues.
#### Windows

18
flake.lock generated
View file

@ -5,11 +5,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1785627969,
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
"lastModified": 1782949081,
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
"type": "github"
},
"original": {
@ -73,11 +73,11 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1785031560,
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
"lastModified": 1782614948,
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
"type": "github"
},
"original": {
@ -88,11 +88,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1785967620,
"narHash": "sha256-IItrdb7Puk05RqOBWZYFC5X6Wl1sJmCfh5MWVHw5iMM=",
"lastModified": 1784497964,
"narHash": "sha256-vlHUuqAcbcH2RKmHbPiuQzbv1pnzzavXnI62RD0bqCU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b7c2ada94fe99c15b0dbcf4d11fd7850b957a436",
"rev": "241313f4e8e508cb9b13278c2b0fa25b9ca27163",
"type": "github"
},
"original": {

View file

@ -29,7 +29,6 @@ Future<void> main(List<String> args) => build(args, (input, output) async {
iosSdk = iosConfig.targetSdk;
final minVersion = iosConfig.targetVersion;
iosSdkPath = await getXCodeTool(sdkType: iosSdk.type);
final archTriple = switch (targetArch) {
Architecture.arm64 => "arm64",
Architecture.x64 => "x86_64",
@ -37,7 +36,6 @@ Future<void> main(List<String> args) => build(args, (input, output) async {
"Unsupported iOS architecture: $targetArch",
),
};
iosTargetTriple = iosSdk == IOSSdk.iPhoneSimulator
? "$archTriple-apple-ios$minVersion.0-simulator"
: "$archTriple-apple-ios$minVersion.0";
@ -50,10 +48,7 @@ Future<void> main(List<String> args) => build(args, (input, output) async {
break;
case OS.macOS:
libFileName = "libgomuks.dylib";
extraEnv = {
"SDKROOT": await getXCodeTool(),
"MACOSX_DEPLOYMENT_TARGET": codeConfig.macOS.targetVersion.toString(),
};
extraEnv = {"SDKROOT": await getXCodeTool()};
break;
case OS.windows:
libFileName = "libgomuks.dll";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 712 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before After
Before After

View file

@ -38,7 +38,6 @@ import "package:nexus/models/requests/set_membership.dart";
import "package:nexus/models/requests/set_state.dart";
import "package:nexus/models/requests/upload_media.dart";
import "package:nexus/models/room.dart";
import "package:nexus/models/room_summary.dart";
import "package:nexus/models/spec_versions_response.dart";
import "package:nexus/models/sync_data.dart";
import "package:nexus/src/third_party/gomuks.g.dart";
@ -214,11 +213,10 @@ class ClientController extends AsyncNotifier<int> {
}
}
Future<String> joinRoom(JoinRoomRequest request) async =>
(await _sendCommand("join_room", request.toJson()))["room_id"];
Future<RoomSummary> getRoomSummary(JoinRoomRequest request) async =>
.fromJson(await _sendCommand("get_room_summary", request.toJson()));
Future<String> joinRoom(JoinRoomRequest request) async {
final response = await _sendCommand("join_room", request.toJson());
return response["room_id"];
}
Future<void> leaveRoom(Room room) async {
if (room.metadata == null) return;
@ -313,7 +311,7 @@ class ClientController extends AsyncNotifier<int> {
Future<Uri?> discoverHomeserver(Uri homeserver) async {
try {
final response = await _sendCommand("discover_homeserver", {
"user_id": "@fake-user:${homeserver.authority}",
"user_id": "@fake-user:${homeserver.host}",
});
return Uri.parse(response["m.homeserver"]?["base_url"]);
} catch (error) {

View file

@ -75,13 +75,12 @@ class RoomChatController extends AsyncNotifier<IList<Event>?> {
);
Future<bool> loadOlder() async {
state = AsyncLoading();
final timelineKeys = ref
.read(RoomsController.provider.select((value) => value[roomId]))
?.timeline
.keys;
final response = await ref
.read(ClientController.provider.notifier)
.watch(ClientController.provider.notifier)
.paginate(
.new(
roomId: roomId,
@ -91,30 +90,26 @@ class RoomChatController extends AsyncNotifier<IList<Event>?> {
),
);
if (response.events.isEmpty) {
state = .data(state.value);
} else {
ref
.read(RoomsController.provider.notifier)
.update(
IMap({
roomId: Room(
events: IMap.fromIterable(
response.events.addAll(response.relatedEvents),
keyMapper: (event) => event.rowId,
valueMapper: (event) => event,
),
hasMore: response.hasMore,
timeline: IMap.fromIterable(
response.events,
keyMapper: (event) => event.timelineRowId,
valueMapper: (event) => event.rowId,
),
ref
.watch(RoomsController.provider.notifier)
.update(
IMap({
roomId: Room(
events: IMap.fromIterable(
response.events.addAll(response.relatedEvents),
keyMapper: (event) => event.rowId,
valueMapper: (event) => event,
),
}),
.new(),
);
}
hasMore: response.hasMore,
timeline: IMap.fromIterable(
response.events,
keyMapper: (event) => event.timelineRowId,
valueMapper: (event) => event.rowId,
),
),
}),
.new(),
);
return response.hasMore;
}

View file

@ -1,18 +0,0 @@
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/client.dart";
import "package:nexus/models/requests/join_room.dart";
import "package:nexus/models/room_summary.dart";
class RoomSummaryController extends AsyncNotifier<RoomSummary> {
final JoinRoomRequest request;
RoomSummaryController(this.request);
@override
Future<RoomSummary> build() =>
ref.watch(ClientController.provider.notifier).getRoomSummary(request);
static final provider = AsyncNotifierProvider.family
.autoDispose<RoomSummaryController, RoomSummary, JoinRoomRequest>(
RoomSummaryController.new,
);
}

View file

@ -9,14 +9,14 @@ class SettingsController extends AsyncNotifier<Settings> {
final file = await ref.watch(SettingsFileController.provider.future);
try {
return .fromJson(json.decode(await file.readAsString()));
return Settings.fromJson(json.decode(await file.readAsString()));
} catch (_) {
return .new();
return Settings();
}
}
Future<void> set(Settings settings) async {
state = .data(settings);
state = AsyncData(settings);
final file = await ref.watch(SettingsFileController.provider.future);
await file.writeAsString(json.encode(settings.toJson()));
}

View file

@ -9,7 +9,6 @@ extension LinkToMention on String {
///
/// Returns the decoded identifier (e.g. "#room:matrix.org")
/// or null if this is not a Matrix link.
/// TODO: Needs to be reworked to handle event links. Might be worth just rewriting, I don't like this code.
String? get mention {
final trimmed = trim();

View file

@ -112,7 +112,7 @@ class App extends StatelessWidget {
data: (settings) =>
settings.useDynamicTheming ? lightDynamic : null,
) ??
ThemeData.light().colorScheme)
ColorScheme.fromSeed(seedColor: Colors.indigo))
.theme,
darkTheme:
(ref
@ -122,7 +122,10 @@ class App extends StatelessWidget {
data: (settings) =>
settings.useDynamicTheming ? darkDynamic : null,
) ??
ThemeData.dark().colorScheme)
ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.dark,
))
.theme,
themeMode: ref
.watch(SettingsController.provider)

View file

@ -7,7 +7,7 @@ part "join_room.g.dart";
abstract class JoinRoomRequest with _$JoinRoomRequest {
const factory JoinRoomRequest({
required String roomIdOrAlias,
@Default(IList.empty()) IList<String> via,
required IList<String> via,
}) = _JoinRoomRequest;
factory JoinRoomRequest.fromJson(Map<String, Object?> json) =>

View file

@ -1,23 +0,0 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/content/create.dart";
import "package:nexus/models/join_rule.dart";
part "room_summary.freezed.dart";
part "room_summary.g.dart";
@freezed
abstract class RoomSummary with _$RoomSummary {
const factory RoomSummary({
required String roomId,
@JsonKey(name: "num_joined_members") required int joinedMembers,
JoinRule? joinRule,
String? name,
Uri? avatarUrl,
String? canonicalAlias,
String? topic,
String? roomVersion,
@JsonKey(unknownEnumValue: RoomType.room) RoomType? roomType,
}) = _RoomSummary;
factory RoomSummary.fromJson(Map<String, Object?> json) =>
_$RoomSummaryFromJson(json);
}

View file

@ -26,17 +26,6 @@ class SelectServerPage extends HookConsumerWidget {
final homeserverUrl = useTextEditingController();
Future<void> setHomeserver(Uri? newHomeserver) async {
if (newHomeserver == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Failed to parse homeserver URL. Are you sure you typed it correctly?",
style: .new(color: theme.colorScheme.onErrorContainer),
),
backgroundColor: theme.colorScheme.errorContainer,
),
);
}
isLoading.value = true;
try {
@ -44,14 +33,26 @@ class SelectServerPage extends HookConsumerWidget {
newHomeserver = Uri.https(newHomeserver!.path);
}
final newUrl = await ref
.read(ClientController.provider.notifier)
.discoverHomeserver(newHomeserver!);
final newUrl = newHomeserver == null
? null
: await ref
.read(ClientController.provider.notifier)
.discoverHomeserver(newHomeserver);
if (context.mounted) {
Future<void> tryLogin(Uri url) async {
if (newUrl == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Homeserver verification failed. Is your homeserver down?",
style: .new(color: theme.colorScheme.onErrorContainer),
),
backgroundColor: theme.colorScheme.errorContainer,
),
);
} else {
final codeResponse = await ref.watch(
AuthUrlController.provider(url).future,
AuthUrlController.provider(newUrl).future,
);
await ref.watch(LaunchHelper.provider).launchUrl(codeResponse.url);
@ -69,7 +70,7 @@ class SelectServerPage extends HookConsumerWidget {
.watch(ClientController.provider.notifier)
.exchangeToken(
.new(
homeserverUrl: url,
homeserverUrl: newUrl,
codeVerifier: codeResponse.codeVerifier,
redirectUri: .new(
scheme: "nexus.federated.nexus",
@ -77,7 +78,7 @@ class SelectServerPage extends HookConsumerWidget {
),
code: code,
clientId: await ref.watch(
ClientIdController.provider(url).future,
ClientIdController.provider(newUrl).future,
),
),
)
@ -88,25 +89,6 @@ class SelectServerPage extends HookConsumerWidget {
}
});
}
if (newUrl == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Homeserver verification failed.",
style: .new(color: theme.colorScheme.onErrorContainer),
),
action: SnackBarAction(
onPressed: () => tryLogin(newHomeserver!),
label: "Attempt log in anyways",
textColor: theme.colorScheme.onErrorContainer,
),
backgroundColor: theme.colorScheme.errorContainer,
),
);
} else {
tryLogin(newUrl);
}
}
} catch (error, stackTrace) {
showError(error, stackTrace);

View file

@ -235,11 +235,9 @@ class Composer extends HookConsumerWidget {
maxLines: 12,
minLines: 1,
autofocus:
(Platform.isLinux ||
Platform.isMacOS ||
Platform.isWindows)
? true
: false,
Platform.isLinux ||
Platform.isMacOS ||
Platform.isWindows,
decoration: .new(
hintText: "Your message here...",
border: .none,

View file

@ -1,11 +1,8 @@
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/room_summary.dart";
import "package:nexus/controllers/user.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/room_summary.dart";
class MentionChip extends ConsumerWidget {
final String? roomId;
@ -15,48 +12,29 @@ class MentionChip extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final mention = content.mention;
final data = switch (mention?.characters.firstOrNull) {
"@" =>
ref
.watch(
UserController.provider(.new(roomId: roomId, userId: mention!)),
)
.whenOrNull(data: (data) => data),
"#" || "!" =>
ref
.watch(
RoomSummaryController.provider(.new(roomIdOrAlias: mention!)),
)
.whenOrNull(data: (data) => data),
_ => null,
};
final membership = mention?.startsWith("@") == true
? ref
.watch(
UserController.provider(.new(roomId: roomId, userId: mention!)),
)
.whenOrNull(data: (data) => data)
: null;
return mention == null
? SizedBox.shrink()
: InkWell(
onTap: () {
if (data case MembershipContent membership) {
if (membership != null) {
context.showUserPopover(membership, mention, roomId: roomId);
} else if (data case RoomSummary summary) {
// TODO: Handle summary
}
},
child: IgnorePointer(
child: Chip(
label: Text(
switch (data) {
RoomSummary summary =>
(summary.name == null ? null : "#${summary.name}") ??
summary.canonicalAlias ??
summary.roomId,
MembershipContent membership =>
membership.displayName == null
? mention
: "@${membership.displayName}",
_ => mention,
},
(membership?.displayName == null
? null
: "@${membership!.displayName}") ??
mention,
style: .new(
fontWeight: .bold,
color: Theme.of(context).colorScheme.onPrimary,

View file

@ -80,7 +80,6 @@ class RoomChat extends HookConsumerWidget {
final controllerData = ref.watch(controllerProvider);
final topEventBeforeLoad = useState<String?>(null);
final hasMore = useState<bool>(true);
Future<void> jumpToId(String eventId) async {
final index = controllerData.value?.indexWhere(
@ -106,7 +105,7 @@ class RoomChat extends HookConsumerWidget {
Future<void> loadOlder() async {
if (controllerData case AsyncData(:final value?)) {
topEventBeforeLoad.value = value.firstOrNull?.eventId;
hasMore.value = await notifier.loadOlder();
await notifier.loadOlder();
}
}
@ -311,7 +310,7 @@ class RoomChat extends HookConsumerWidget {
await Clipboard.setData(
ClipboardData(
text:
"matrix:roomid/${room.metadata?.id.substring(1)}/e/${event.eventId}$vias",
"matrix:roomid/${room.metadata?.id.substring(1)}/e/${event.eventId}$vias)",
),
);
},
@ -467,24 +466,23 @@ class RoomChat extends HookConsumerWidget {
child: switch (controllerData) {
AsyncData(:final value?) ||
AsyncLoading(:final value?) => CustomScrollView(
controller: scrollController,
keyboardDismissBehavior:
ScrollViewKeyboardDismissBehavior.onDrag,
controller: scrollController,
slivers: [
if (hasMore.value)
SliverToBoxAdapter(
child: Padding(
padding: .symmetric(vertical: 36),
child: Center(
child: ElevatedButton(
onPressed: controllerData is AsyncData
? loadOlder
: null,
child: Text("Load More"),
),
SliverToBoxAdapter(
child: Padding(
padding: .symmetric(vertical: 36),
child: Center(
child: ElevatedButton(
onPressed: controllerData is AsyncData
? loadOlder
: null,
child: Text("Load More"),
),
),
),
),
SuperSliverList.builder(
listController: listController.value,

View file

@ -35,7 +35,8 @@ class RoomMenu extends ConsumerWidget {
await Clipboard.setData(
.new(
text: "matrix:roomid/${room!.metadata?.id.substring(1)}$vias",
text:
"matrix:roomid/${room!.metadata?.id.substring(1)}$vias)",
),
);
},

View file

@ -1,4 +1,4 @@
platform :osx, '13.0'
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View file

@ -567,7 +567,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@ -649,7 +649,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@ -699,7 +699,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;

View file

@ -1,68 +1,68 @@
{
"info": {
"version": 1,
"author": "xcode"
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
},
"images": [
{
"size": "16x16",
"idiom": "mac",
"filename": "app_icon_16.png",
"scale": "1x"
},
{
"size": "16x16",
"idiom": "mac",
"filename": "app_icon_32.png",
"scale": "2x"
},
{
"size": "32x32",
"idiom": "mac",
"filename": "app_icon_32.png",
"scale": "1x"
},
{
"size": "32x32",
"idiom": "mac",
"filename": "app_icon_64.png",
"scale": "2x"
},
{
"size": "128x128",
"idiom": "mac",
"filename": "app_icon_128.png",
"scale": "1x"
},
{
"size": "128x128",
"idiom": "mac",
"filename": "app_icon_256.png",
"scale": "2x"
},
{
"size": "256x256",
"idiom": "mac",
"filename": "app_icon_256.png",
"scale": "1x"
},
{
"size": "256x256",
"idiom": "mac",
"filename": "app_icon_512.png",
"scale": "2x"
},
{
"size": "512x512",
"idiom": "mac",
"filename": "app_icon_512.png",
"scale": "1x"
},
{
"size": "512x512",
"idiom": "mac",
"filename": "app_icon_1024.png",
"scale": "2x"
}
]
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 520 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

View file

@ -293,10 +293,10 @@ packages:
dependency: "direct main"
description:
name: dynamic_color
sha256: "4aeb76de323e8eb660bab5557d826ad7ebbf1434a598f0c6aa8a11a9696a6757"
sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.8.1"
dynamic_polls:
dependency: "direct main"
description:
@ -366,10 +366,10 @@ packages:
dependency: "direct main"
description:
name: ffigen
sha256: "56fc0be88937409839a59c133dfdee4165e80da324c9e39f59013060249ffb44"
sha256: b7803707faeec4ce3c1b0c2274906504b796e3b70ad573577e72333bd1c9b3ba
url: "https://pub.dev"
source: hosted
version: "21.0.0"
version: "20.1.1"
file:
dependency: transitive
description:
@ -762,10 +762,10 @@ packages:
dependency: "direct dev"
description:
name: json_serializable
sha256: e45aefa0324f08c683caafbb94b72837aa6193c61822799c916e45f4a263113d
sha256: ffcd10cde35a93b2abbbcc26bd9971f4ca93763e8abe78d855e3c4177797e501
url: "https://pub.dev"
source: hosted
version: "6.14.1"
version: "6.14.0"
leak_tracker:
dependency: transitive
description:
@ -819,10 +819,10 @@ packages:
dependency: "direct main"
description:
name: m3e_buttons
sha256: "20b29df165d4d866ffd0d4808090210740690707705f44fc61c9b275bec3af36"
sha256: af1b19bd5ec7327d20b5a57c6c91e1ed30ee15a4dbade781c0cdbc72e6b04b5e
url: "https://pub.dev"
source: hosted
version: "0.0.5"
version: "0.0.4"
m3e_card_list:
dependency: "direct main"
description:
@ -951,14 +951,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: f9c168717100ae6d9fee9ffb0be379bf1f8b26b0f6bcbd4fdddcd931993a6a72
url: "https://pub.dev"
source: hosted
version: "0.19.2"
navigation_rail_m3e:
dependency: "direct main"
description:

View file

@ -4,98 +4,95 @@ version: 0.1.0
publish_to: none
flutter:
fonts:
- family: fallback-emoji
fonts:
- asset: assets/fonts/NotoColorEmoji.ttf
- family: fallback-sans
fonts:
- asset: assets/fonts/Roboto.ttf
assets:
- assets/
uses-material-design: true
fonts:
- family: fallback-emoji
fonts:
- asset: assets/fonts/NotoColorEmoji.ttf
- family: fallback-sans
fonts:
- asset: assets/fonts/Roboto.ttf
assets:
- assets/
uses-material-design: true
environment:
sdk: "^3.12.2"
sdk: "^3.12.2"
dependency_overrides:
path_provider_android: 2.2.23 # Pinned to avoid JNI
linkify:
git:
url: https://github.com/appelladev/linkify
ref: fix/consecutive-periods-loose-url
path_provider_android: 2.2.23 # Pinned to avoid JNI
linkify:
git:
url: https://github.com/appelladev/linkify
ref: fix/consecutive-periods-loose-url
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter_riverpod: 3.3.2
hooks_riverpod: 3.3.2
intl: 0.20.2
fast_immutable_collections: 11.2.0
path_provider: 2.1.6
url_launcher: 6.3.2
freezed_annotation: 3.1.0
image_picker: ^1.2.3
path: 1.9.1
dynamic_color: 1.9.0
collection: 1.19.1
window_manager: 0.5.2
color_hash: 1.0.1
flutter_widget_from_html_core: 0.17.2
flutter_svg: 2.3.0
json_annotation: 4.12.0
shared_preferences: 2.5.5
fluttertagger: 2.3.2
dynamic_polls: 0.0.7
flutter_hooks: 0.21.3+1
ffi: 2.2.0
hooks: 2.0.2
code_assets: 1.2.1
ffigen: 21.0.0
timeago: 3.7.1
http: 1.6.0
flutter_linkify: 6.0.0
linkify: 5.0.0
emoji_text_field:
git:
url: https://github.com/Henry-Hiles/emoji_text_field
flutter_blurhash: 0.9.1
super_sliver_list: 0.4.1
media_kit: 1.2.6
media_kit_video: 2.0.1
media_kit_libs_video: 1.0.7
measure_size: 5.0.2
m3e_buttons: 0.0.5
navigation_rail_m3e:
git:
url: https://github.com/Henry-Hiles/material_3_expressive
path: packages/navigation_rail_m3e
m3e_card_list: 0.1.0
xdg_directories: 1.1.0
package_info_plus: 10.2.1
app_links: 7.2.1
file_selector: ^1.1.0
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter_riverpod: 3.3.2
hooks_riverpod: 3.3.2
intl: 0.20.2
fast_immutable_collections: 11.2.0
path_provider: 2.1.6
url_launcher: 6.3.2
freezed_annotation: 3.1.0
image_picker: ^1.2.3
path: 1.9.1
dynamic_color: 1.8.1
collection: 1.19.1
window_manager: 0.5.2
color_hash: 1.0.1
flutter_widget_from_html_core: 0.17.2
flutter_svg: 2.3.0
json_annotation: 4.12.0
shared_preferences: 2.5.5
fluttertagger: 2.3.2
dynamic_polls: 0.0.7
flutter_hooks: 0.21.3+1
ffi: 2.2.0
hooks: 2.0.2
code_assets: 1.2.1
ffigen: 20.1.1
timeago: 3.7.1
http: 1.6.0
flutter_linkify: 6.0.0
linkify: 5.0.0
emoji_text_field:
git:
url: https://github.com/Henry-Hiles/emoji_text_field
flutter_blurhash: 0.9.1
super_sliver_list: 0.4.1
media_kit: 1.2.6
media_kit_video: 2.0.1
media_kit_libs_video: 1.0.7
measure_size: 5.0.2
m3e_buttons: 0.0.4
navigation_rail_m3e:
git:
url: https://github.com/Henry-Hiles/material_3_expressive
path: packages/navigation_rail_m3e
m3e_card_list: 0.1.0
xdg_directories: 1.1.0
package_info_plus: 10.2.1
app_links: 7.2.1
file_selector: ^1.1.0
dev_dependencies:
build_runner: ^2.15.1
flutter_lints: 6.0.0
freezed: 3.2.6-dev.1
riverpod_lint: 3.1.4
flutter_launcher_icons: 0.14.4
json_serializable: 6.14.1
build_runner: ^2.15.1
flutter_lints: 6.0.0
freezed: 3.2.6-dev.1
riverpod_lint: 3.1.4
flutter_launcher_icons: 0.14.4
json_serializable: 6.14.0
flutter_launcher_icons:
image_path: assets/icon.png
ios: true
image_path_ios: assets/mobile.png
android: true
adaptive_icon_background: assets/background.png
adaptive_icon_foreground: assets/foreground.png
adaptive_icon_monochrome: assets/monochrome.png
windows:
generate: true
macos:
generate: true
image_path: assets/mobile.png
ios: true
android: true
image_path: assets/icon.png
adaptive_icon_background: assets/background.png
adaptive_icon_foreground: assets/foreground.png
adaptive_icon_monochrome: assets/monochrome.png
remove_alpha_ios: true
windows:
generate: true