Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
deac9ef24e |
|||
|
e24e99fd8e |
|||
|
4580110da0 |
|||
|
d1724932b2 |
|||
|
a6fe80fea2 |
|||
|
9f45cbc1e7 |
|||
|
eca2b0b558 |
|||
|
4e6ba571fc |
|||
|
200e03f082 |
|||
|
8180338d99 |
|||
|
72c5e7d819 |
|||
|
254b7e134f |
25 changed files with 387 additions and 314 deletions
4
.github/workflows/android.yml
vendored
4
.github/workflows/android.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
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 --target-platform android-arm64 --dart-define=GIT_COMMIT=${{ github.sha }}"
|
||||
run: nix develop --command bash -c "flutter pub get && dart scripts/generate.dart && flutter pub run build_runner build && flutter build apk --target-platform android-arm64 --split-per-abi --dart-define=GIT_COMMIT=${{ github.sha }}"
|
||||
env:
|
||||
KEYSTORE_PATH: ../../keystore.jks
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
|
|
@ -36,4 +36,4 @@ jobs:
|
|||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: APK
|
||||
path: build/app/outputs/flutter-apk/app-release.apk
|
||||
path: build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
|
||||
|
|
|
|||
|
|
@ -16,23 +16,6 @@ subprojects {
|
|||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
|
||||
subprojects {
|
||||
if (name == "flutter_avif_android") {
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
exclude("**/FlutterAvifPlugin.kt")
|
||||
}
|
||||
afterEvaluate {
|
||||
extensions.configure<com.android.build.api.dsl.LibraryExtension> {
|
||||
compileSdk = 36
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
|
|
|||
2
gomuks
2
gomuks
|
|
@ -1 +1 @@
|
|||
Subproject commit ad275f7613894d9d2fa4d16d733a6816e2978de9
|
||||
Subproject commit 7e43a63e578b0d6f52269d65297198077bf5b45d
|
||||
|
|
@ -122,7 +122,7 @@ Future<void> main(List<String> args) => build(args, (input, output) async {
|
|||
"sqlite_fts5",
|
||||
"goolm",
|
||||
// goheif/dav1d is not supported on Android or iOS, would need to be fixed upstream
|
||||
if (targetOS == OS.android || targetOS == OS.iOS) "noheic",
|
||||
"noheic",
|
||||
].join(",");
|
||||
|
||||
final archiveFile = targetOS == OS.iOS
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import "package:nexus/models/requests/get_room_state.dart";
|
|||
import "package:nexus/models/requests/get_space_hierarchy.dart";
|
||||
import "package:nexus/models/requests/join_room.dart";
|
||||
import "package:nexus/models/profile_response.dart";
|
||||
import "package:nexus/models/push_notification.dart";
|
||||
import "package:nexus/models/recovery_key.dart";
|
||||
import "package:nexus/models/requests/oauth/exchange_token.dart";
|
||||
import "package:nexus/models/requests/oauth/get_auth_url.dart";
|
||||
|
|
@ -47,7 +48,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_metadata.dart";
|
||||
import "package:nexus/models/room_summary.dart";
|
||||
import "package:nexus/models/space_hierarchy.dart";
|
||||
import "package:nexus/models/spec_versions_response.dart";
|
||||
|
|
@ -128,18 +128,15 @@ class ClientController extends AsyncNotifier<int> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<(Event, RoomMetadata)?> handlePush(Map<String, dynamic> data) async {
|
||||
Future<PushNotification?> handlePush(Map<String, dynamic> data) async {
|
||||
final response = await callGomuksMethod(
|
||||
data,
|
||||
(handle, data) async => GomuksHandlePush(handle, data),
|
||||
);
|
||||
|
||||
return response == null
|
||||
return response?["notification"] == null
|
||||
? null
|
||||
: (
|
||||
Event.fromJson(response["event"]),
|
||||
RoomMetadata.fromJson(response["room"]),
|
||||
);
|
||||
: .fromJson(response["notification"]);
|
||||
}
|
||||
|
||||
Future<dynamic> _sendCommand(
|
||||
|
|
|
|||
|
|
@ -143,6 +143,19 @@ class GomuksListenerController extends AsyncNotifier<void> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Waits for gomuks to report the client's device ID
|
||||
Future<String> deviceId() {
|
||||
final completer = Completer<String>();
|
||||
final subscription = ref.listen(
|
||||
ClientStateController.provider.select((value) => value?.deviceId),
|
||||
(_, next) {
|
||||
if (next != null && !completer.isCompleted) completer.complete(next);
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
return completer.future.whenComplete(subscription.close);
|
||||
}
|
||||
|
||||
static Future<int> _start(
|
||||
int handle,
|
||||
Pointer<
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import "dart:async";
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
|
||||
import "package:flutter/services.dart";
|
||||
|
|
@ -103,24 +104,75 @@ class NotificationController
|
|||
return true;
|
||||
}
|
||||
|
||||
/// Whether notifications can currently be shown, without prompting
|
||||
Future<bool> permissionsGranted() async {
|
||||
if (Platform.isLinux) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final controller = await future;
|
||||
|
||||
if (Platform.isIOS) {
|
||||
return (await controller
|
||||
.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin
|
||||
>()
|
||||
?.checkPermissions())
|
||||
?.isEnabled ??
|
||||
true;
|
||||
} else if (Platform.isMacOS) {
|
||||
return (await controller
|
||||
.resolvePlatformSpecificImplementation<
|
||||
MacOSFlutterLocalNotificationsPlugin
|
||||
>()
|
||||
?.checkPermissions())
|
||||
?.isEnabled ??
|
||||
true;
|
||||
} else if (Platform.isAndroid) {
|
||||
return await controller
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin
|
||||
>()
|
||||
?.areNotificationsEnabled() ??
|
||||
true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> send({
|
||||
required int id,
|
||||
required String title,
|
||||
String? body,
|
||||
File? icon,
|
||||
String? payload,
|
||||
String? senderName,
|
||||
File? senderAvatar,
|
||||
String? selfName,
|
||||
bool isGroup = false,
|
||||
bool sound = true,
|
||||
DateTime? timestamp,
|
||||
String? threadId,
|
||||
}) async {
|
||||
debugPrint("Sending notification for $id");
|
||||
|
||||
// In DMs the title is already the sender, so only prefix in groups
|
||||
final prefix = isGroup ? senderName : null;
|
||||
|
||||
if (Platform.isLinux) {
|
||||
final portal = await ref.watch(PortalController.provider.future);
|
||||
final portal = await ref.read(PortalController.provider.future);
|
||||
const escape = HtmlEscape(HtmlEscapeMode.element);
|
||||
|
||||
await portal.notification.addNotification(
|
||||
id.toString(),
|
||||
title: title,
|
||||
body: body,
|
||||
body: prefix == null || body == null ? body : "$prefix: $body",
|
||||
markupBody: prefix == null || body == null
|
||||
? null
|
||||
: "<b>${escape.convert(prefix)}</b>: ${escape.convert(body)}",
|
||||
defaultAction: "app.event",
|
||||
defaultActionTarget: payload,
|
||||
sound: sound ? null : const XdgNotificationSoundSilent(),
|
||||
category: XdgNotificationCategory.imReceived,
|
||||
priority: .normal,
|
||||
buttons: [
|
||||
|
|
@ -133,25 +185,64 @@ class NotificationController
|
|||
],
|
||||
);
|
||||
} else {
|
||||
final controller = await future;
|
||||
|
||||
// Append to the conversation already shown under this id, if any
|
||||
final existingStyle = Platform.isAndroid
|
||||
? await controller
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin
|
||||
>()
|
||||
?.getActiveNotificationMessagingStyle(id: id)
|
||||
.onError((_, _) => null)
|
||||
: null;
|
||||
|
||||
final darwin = DarwinNotificationDetails(
|
||||
presentSound: sound,
|
||||
threadIdentifier: threadId,
|
||||
);
|
||||
final notificationDetails = NotificationDetails(
|
||||
android: .new(
|
||||
"messages",
|
||||
"Messages",
|
||||
silent: !sound,
|
||||
largeIcon: icon == null ? null : FilePathAndroidBitmap(icon.path),
|
||||
styleInformation: senderName == null || body == null
|
||||
? null
|
||||
: MessagingStyleInformation(
|
||||
.new(name: selfName ?? "You"),
|
||||
conversationTitle: isGroup ? title : null,
|
||||
groupConversation: isGroup,
|
||||
messages: [
|
||||
...?existingStyle?.messages,
|
||||
.new(
|
||||
body,
|
||||
timestamp ?? .now(),
|
||||
.new(
|
||||
name: senderName,
|
||||
icon: senderAvatar == null
|
||||
? null
|
||||
: BitmapFilePathAndroidIcon(senderAvatar.path),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
iOS: darwin,
|
||||
macOS: darwin,
|
||||
windows: .new(
|
||||
audio: sound ? null : .silent(),
|
||||
timestamp: timestamp,
|
||||
header: threadId == null
|
||||
? null
|
||||
: .new(id: threadId, title: title, arguments: payload ?? ""),
|
||||
),
|
||||
// TODO: See if icons can be added to iOS, macOS, and Windows
|
||||
// notifications (#68)
|
||||
iOS: .new(),
|
||||
macOS: .new(),
|
||||
windows: .new(),
|
||||
);
|
||||
|
||||
final controller = await future;
|
||||
|
||||
await controller.show(
|
||||
id: id,
|
||||
title: title,
|
||||
body: body,
|
||||
body: prefix == null || body == null ? body : "$prefix: $body",
|
||||
notificationDetails: notificationDetails,
|
||||
payload: payload,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class SettingsSectionsController
|
|||
icon: Icon(icon),
|
||||
title: title,
|
||||
subtitle: Text(description),
|
||||
initialValue: settings.theme,
|
||||
value: settings.theme,
|
||||
options: ThemeMode.values,
|
||||
getName: (option) => toBeginningOfSentenceCase(option.name),
|
||||
onChanged: (value) => ref
|
||||
|
|
@ -105,7 +105,7 @@ class SettingsSectionsController
|
|||
icon: Icon(icon),
|
||||
title: title,
|
||||
subtitle: Text(description),
|
||||
initialValue: settings.roomOrder,
|
||||
value: settings.roomOrder,
|
||||
options: RoomOrder.values,
|
||||
getName: (option) => switch (option) {
|
||||
.unreadFirst => "Mentioned, then unread",
|
||||
|
|
@ -194,7 +194,6 @@ class SettingsSectionsController
|
|||
final current = pusherRegistered.value;
|
||||
|
||||
return DialogListTile<String?>(
|
||||
key: ValueKey(current),
|
||||
title: title,
|
||||
subtitle: Text(
|
||||
unifiedPushAllowed.maybeWhen(
|
||||
|
|
@ -204,8 +203,7 @@ class SettingsSectionsController
|
|||
description,
|
||||
),
|
||||
icon: Icon(icon),
|
||||
required: false,
|
||||
initialValue: current,
|
||||
value: current,
|
||||
options: [
|
||||
null,
|
||||
...?distributors.value,
|
||||
|
|
@ -227,21 +225,32 @@ class SettingsSectionsController
|
|||
try {
|
||||
loading.value = true;
|
||||
if (value != null) {
|
||||
if (await ref
|
||||
final messenger = ScaffoldMessenger.of(
|
||||
context,
|
||||
);
|
||||
if (!await ref
|
||||
.read(
|
||||
NotificationController
|
||||
.provider
|
||||
.notifier,
|
||||
)
|
||||
.requestPermissions()) {
|
||||
await ref
|
||||
.read(
|
||||
UnifiedPushController
|
||||
.provider
|
||||
.notifier,
|
||||
)
|
||||
.register(value);
|
||||
messenger.showSnackBar(
|
||||
.new(
|
||||
content: Text(
|
||||
"Notifications are disabled. Enable them for Nexus in your system settings.",
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
await ref
|
||||
.read(
|
||||
UnifiedPushController
|
||||
.provider
|
||||
.notifier,
|
||||
)
|
||||
.register(value);
|
||||
} else {
|
||||
await ref
|
||||
.read(
|
||||
|
|
@ -283,7 +292,7 @@ class SettingsSectionsController
|
|||
icon: Icon(icon),
|
||||
title: title,
|
||||
subtitle: Text(description),
|
||||
initialValue: ref
|
||||
value: ref
|
||||
.watch(AccountDataController.provider)
|
||||
.invitePermissionConfig
|
||||
.defaultAction,
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@ import "package:nexus/controllers/notification.dart";
|
|||
import "package:nexus/controllers/push_key.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/controllers/client_state.dart";
|
||||
import "package:nexus/models/content/message.dart";
|
||||
import "package:nexus/models/content/sticker.dart";
|
||||
import "package:nexus/controllers/gomuks_listener.dart";
|
||||
import "package:unifiedpush/unifiedpush.dart";
|
||||
import "package:unifiedpush_storage_shared_preferences/storage.dart";
|
||||
import "package:window_manager/window_manager.dart";
|
||||
|
|
@ -74,47 +72,67 @@ class UnifiedPushController extends AsyncNotifier<String?> {
|
|||
},
|
||||
onMessage: (message, instance) async {
|
||||
debugPrint("UP message received for $instance");
|
||||
if (await deregisterIfDisabled(instance)) {
|
||||
if (isInBackground) exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.decrypted == false) {
|
||||
throw Exception(
|
||||
"Failed to decrypt notification. Try toggling off and on UnifiedPush in settings.",
|
||||
);
|
||||
}
|
||||
final pushResponse = await ref
|
||||
final notification = await ref
|
||||
.read(ClientController.provider.notifier)
|
||||
.handlePush(json.decode(String.fromCharCodes(message.content)));
|
||||
|
||||
if (pushResponse == null) return;
|
||||
|
||||
final (event, roomMetadata) = pushResponse;
|
||||
|
||||
if (event.unreadType?.has(.notify) != true ||
|
||||
if (notification == null ||
|
||||
(!isInBackground &&
|
||||
await windowManager.isFocused().onError((_, _) => true) &&
|
||||
ref.read(SelectedRoomController.provider) == event.roomId)) {
|
||||
ref.read(SelectedRoomController.provider) ==
|
||||
notification.roomId)) {
|
||||
if (isInBackground) exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
final icon = roomMetadata.avatar == null
|
||||
final client = ref.read(ClientController.provider.notifier);
|
||||
|
||||
final icon = notification.roomAvatar == null
|
||||
? null
|
||||
: await ref
|
||||
.read(ClientController.provider.notifier)
|
||||
.downloadMedia(
|
||||
.new(mxc: roomMetadata.avatar!, isAvatar: true),
|
||||
);
|
||||
: await client.downloadMedia(
|
||||
.new(mxc: notification.roomAvatar!, isAvatar: true),
|
||||
);
|
||||
|
||||
final pmp = notification.perMessageProfile;
|
||||
final senderAvatarUrl = pmp?.avatar ?? notification.sender.avatar;
|
||||
// Only Android can show the sender's avatar
|
||||
final senderAvatar = senderAvatarUrl == null || !Platform.isAndroid
|
||||
? null
|
||||
: await client.downloadMedia(
|
||||
.new(mxc: senderAvatarUrl, isAvatar: true),
|
||||
);
|
||||
|
||||
await ref
|
||||
.read(NotificationController.provider.notifier)
|
||||
.send(
|
||||
id: event.eventId.hashCode & 0x7fffffff,
|
||||
title: roomMetadata.name ?? "New Event",
|
||||
// Android stacks messages into one conversation per room
|
||||
id:
|
||||
(Platform.isAndroid
|
||||
? notification.roomId
|
||||
: notification.eventId)
|
||||
.hashCode &
|
||||
0x7fffffff,
|
||||
title: notification.roomName,
|
||||
icon: icon,
|
||||
payload: event.rowId.toString(),
|
||||
body: switch (event.content) {
|
||||
MessageContent(:final body?) ||
|
||||
StickerContent(:final body) => body,
|
||||
_ => null,
|
||||
},
|
||||
payload: notification.eventRowId.toString(),
|
||||
body: notification.text,
|
||||
senderName: pmp?.name ?? notification.sender.name,
|
||||
senderAvatar: senderAvatar,
|
||||
selfName: notification.self.name,
|
||||
isGroup: !notification.isDm,
|
||||
sound: notification.sound,
|
||||
timestamp: notification.timestamp,
|
||||
threadId: notification.roomId,
|
||||
);
|
||||
|
||||
if (isInBackground) exit(0);
|
||||
|
|
@ -122,9 +140,11 @@ class UnifiedPushController extends AsyncNotifier<String?> {
|
|||
onUnregistered: deregister,
|
||||
);
|
||||
|
||||
if (!registered) return null;
|
||||
if (!registered || isInBackground) return null;
|
||||
|
||||
try {
|
||||
if (await deregisterIfDisabled()) return null;
|
||||
|
||||
return await register();
|
||||
} catch (error, stackTrace) {
|
||||
showError(error, stackTrace);
|
||||
|
|
@ -143,19 +163,9 @@ class UnifiedPushController extends AsyncNotifier<String?> {
|
|||
|
||||
state = .loading();
|
||||
try {
|
||||
final deviceIdCompleter = Completer<String>();
|
||||
final subscription = ref.listen(
|
||||
ClientStateController.provider.select((value) => value?.deviceId),
|
||||
(_, next) {
|
||||
if (next != null && !deviceIdCompleter.isCompleted) {
|
||||
deviceIdCompleter.complete(next);
|
||||
}
|
||||
},
|
||||
fireImmediately: true,
|
||||
);
|
||||
final deviceId = await deviceIdCompleter.future.whenComplete(
|
||||
subscription.close,
|
||||
);
|
||||
final deviceId = await ref
|
||||
.read(GomuksListenerController.provider.notifier)
|
||||
.deviceId();
|
||||
|
||||
await Future(() async {
|
||||
final capabilities = await ref
|
||||
|
|
@ -201,12 +211,27 @@ class UnifiedPushController extends AsyncNotifier<String?> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> deregister([String? instance]) async {
|
||||
final clientState = ref.read(ClientStateController.provider);
|
||||
Future<bool> deregisterIfDisabled([String? notificationInstance]) async {
|
||||
if (await ref
|
||||
.read(NotificationController.provider.notifier)
|
||||
.permissionsGranted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final keyProvider = PushKeyController.provider(
|
||||
instance ?? clientState!.deviceId!,
|
||||
);
|
||||
final instance =
|
||||
notificationInstance ??
|
||||
await ref.read(GomuksListenerController.provider.notifier).deviceId();
|
||||
final key = await ref.read(PushKeyController.provider(instance).future);
|
||||
if (key != null) await deregister(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> deregister([String? instance]) async {
|
||||
instance ??= await ref
|
||||
.read(GomuksListenerController.provider.notifier)
|
||||
.deviceId();
|
||||
|
||||
final keyProvider = PushKeyController.provider(instance);
|
||||
final key = await ref.read(keyProvider.future);
|
||||
|
||||
if (key != null) {
|
||||
|
|
@ -220,7 +245,7 @@ class UnifiedPushController extends AsyncNotifier<String?> {
|
|||
);
|
||||
}
|
||||
|
||||
await UnifiedPush.unregister(instance ?? clientState!.deviceId!);
|
||||
await UnifiedPush.unregister(instance);
|
||||
state = .data(null);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import "dart:async";
|
||||
import "dart:io";
|
||||
import "dart:isolate";
|
||||
import "dart:ui" as ui;
|
||||
|
||||
import "package:avif_native/avif_native.dart";
|
||||
import "package:flutter/widgets.dart";
|
||||
import "package:flutter_avif/flutter_avif.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client.dart";
|
||||
import "package:nexus/models/requests/download_media.dart";
|
||||
|
|
@ -24,13 +26,9 @@ class MxcImage(final WidgetRef ref, final DownloadMediaRequest request)
|
|||
final bytes = await File(file.path).readAsBytes();
|
||||
|
||||
// Flutter can't decode AVIF on Linux/Windows (or older Android/iOS), so we do it ourselves
|
||||
final avifType = isAvifFile(bytes.sublist(0, 16.clamp(0, bytes.length)));
|
||||
if (avifType != .unknown) {
|
||||
final codec = avifType == .avif
|
||||
? SingleFrameAvifCodec(bytes: bytes)
|
||||
: MultiFrameAvifCodec(key: hashCode, avifBytes: bytes);
|
||||
await codec.ready();
|
||||
return AvifCodecAdapter(codec);
|
||||
if (String.fromCharCodes(bytes.skip(4).take(8))
|
||||
case "ftypavif" || "ftypavis") {
|
||||
return AvifCodec(await Isolate.run(() => decodeAvif(bytes)));
|
||||
}
|
||||
|
||||
return decode(await .fromUint8List(bytes));
|
||||
|
|
@ -44,21 +42,33 @@ class MxcImage(final WidgetRef ref, final DownloadMediaRequest request)
|
|||
int get hashCode => request.hashCode;
|
||||
}
|
||||
|
||||
class AvifCodecAdapter(final AvifCodec codec) implements ui.Codec {
|
||||
@override
|
||||
int get frameCount => codec.frameCount;
|
||||
class AvifCodec(final AvifImageData data) implements ui.Codec {
|
||||
var _frameIndex = 0;
|
||||
|
||||
@override
|
||||
int get repetitionCount => frameCount > 1 ? -1 : 0;
|
||||
int get frameCount => data.frames.length;
|
||||
|
||||
// libavif uses -2 for unknown, which we treat as infinite like browsers do
|
||||
@override
|
||||
int get repetitionCount =>
|
||||
data.repetitionCount < 0 ? -1 : data.repetitionCount;
|
||||
|
||||
@override
|
||||
Future<ui.FrameInfo> getNextFrame() async {
|
||||
final frame = await codec.getNextFrame();
|
||||
return AvifFrameInfo(frame.duration, frame.image);
|
||||
Future<ui.FrameInfo> getNextFrame() {
|
||||
final frame = data.frames[_frameIndex++ % frameCount];
|
||||
final completer = Completer<ui.FrameInfo>();
|
||||
ui.decodeImageFromPixels(
|
||||
frame.rgba,
|
||||
frame.width,
|
||||
frame.height,
|
||||
.rgba8888,
|
||||
(image) => completer.complete(AvifFrameInfo(frame.duration, image)),
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() => codec.dispose();
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class AvifFrameInfo(
|
||||
|
|
|
|||
44
lib/models/push_notification.dart
Normal file
44
lib/models/push_notification.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
import "package:nexus/models/epoch_date_time_converter.dart";
|
||||
|
||||
part "push_notification.freezed.dart";
|
||||
part "push_notification.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(createToJson: false)
|
||||
class const PushNotification({
|
||||
@EpochDateTimeConverter() required final DateTime timestamp,
|
||||
|
||||
required final String eventId,
|
||||
@JsonKey(name: "event_rowid") required final int eventRowId,
|
||||
|
||||
required final String roomId,
|
||||
required final String roomName,
|
||||
final Uri? roomAvatar,
|
||||
final bool isDm = false,
|
||||
|
||||
required final NotificationUser sender,
|
||||
required final NotificationUser self,
|
||||
final NotificationUser? perMessageProfile,
|
||||
|
||||
required final String text,
|
||||
final Uri? image,
|
||||
|
||||
final bool mention = false,
|
||||
final bool reply = false,
|
||||
final bool sound = false,
|
||||
}) with _$PushNotification {
|
||||
factory PushNotification.fromJson(Map<String, Object?> json) =>
|
||||
_$PushNotificationFromJson(json);
|
||||
}
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable(createToJson: false)
|
||||
class const NotificationUser({
|
||||
required final String id,
|
||||
required final String name,
|
||||
final Uri? avatar,
|
||||
}) with _$NotificationUser {
|
||||
factory NotificationUser.fromJson(Map<String, Object?> json) =>
|
||||
_$NotificationUserFromJson(json);
|
||||
}
|
||||
|
|
@ -53,6 +53,12 @@ class const ReactionRow(final Event event, {super.key}) extends ConsumerWidget {
|
|||
ref,
|
||||
.new(mxc: Uri.parse(reaction)),
|
||||
),
|
||||
errorBuilder: (context, _, _) => Icon(
|
||||
Icons.broken_image,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.error,
|
||||
),
|
||||
)
|
||||
: Text(reaction, overflow: .ellipsis),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import "package:nexus/controllers/client.dart";
|
|||
import "package:nexus/controllers/client_state.dart";
|
||||
import "package:nexus/helpers/extensions/show_adaptive_sheet.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/models/client_state.dart";
|
||||
import "package:nexus/widgets/recovery_key_sheet.dart";
|
||||
import "package:nexus/widgets/sheet_layout.dart";
|
||||
|
||||
|
|
@ -16,11 +17,12 @@ class const ResetEncryptionSheet({
|
|||
}) extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isReset = ref.watch(
|
||||
final verificationState = ref.watch(
|
||||
ClientStateController.provider.select(
|
||||
(state) => state?.verificationState.hasCrossSigning ?? false,
|
||||
(state) => state?.verificationState,
|
||||
),
|
||||
);
|
||||
final isReset = verificationState?.hasCrossSigning ?? false;
|
||||
|
||||
final usePassphrase = useState(false);
|
||||
final passphraseController = useTextEditingController();
|
||||
|
|
@ -95,6 +97,13 @@ class const ResetEncryptionSheet({
|
|||
),
|
||||
],
|
||||
children: [
|
||||
if (verificationState case VerificationState(
|
||||
hasCrossSigning: true,
|
||||
hasSecretStorage: false,
|
||||
))
|
||||
const Text(
|
||||
"Your account has an encryption identity, but no recovery key. If you're still signed in on another client, you can set up a recovery key there instead, then restart Nexus and use it to verify. Otherwise, continue with the reset:",
|
||||
),
|
||||
Text(
|
||||
isReset
|
||||
? "This replaces your encryption identity with a new one. Your other sessions will need to be verified again, and any messages you can't currently decrypt may become unreadable."
|
||||
|
|
|
|||
|
|
@ -4,53 +4,35 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
|
|||
import "package:nexus/widgets/settings/radio_sheet.dart";
|
||||
|
||||
final class const DialogListTile<T>({
|
||||
required final T? initialValue,
|
||||
required final T? value,
|
||||
required final Icon icon,
|
||||
required final String title,
|
||||
required final List<T> options,
|
||||
required final void Function(T value)? onChanged,
|
||||
required final String Function(T option) getName,
|
||||
final Widget? subtitle,
|
||||
final bool required = true,
|
||||
super.key,
|
||||
}) extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) => FormField(
|
||||
validator: (value) =>
|
||||
value == null && required == true ? "This field is required." : null,
|
||||
initialValue: initialValue,
|
||||
builder: (field) => InputDecorator(
|
||||
decoration: InputDecoration(
|
||||
errorText: field.errorText,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
enabledBorder: InputBorder.none,
|
||||
Widget build(BuildContext context, WidgetRef ref) => ListTile(
|
||||
enabled: onChanged != null,
|
||||
onTap: () => context.showAdaptiveSheet(
|
||||
(context) => RadioSheet<T>(
|
||||
title: title,
|
||||
getName: getName,
|
||||
onChanged: onChanged,
|
||||
value: value,
|
||||
options: options,
|
||||
),
|
||||
child: ListTile(
|
||||
enabled: onChanged != null,
|
||||
onTap: () => context.showAdaptiveSheet(
|
||||
(context) => RadioSheet<T>(
|
||||
title: title,
|
||||
getName: getName,
|
||||
onChanged: onChanged == null
|
||||
? null
|
||||
: (value) {
|
||||
field.didChange(value);
|
||||
onChanged!.call(value);
|
||||
},
|
||||
value: field.value,
|
||||
options: options,
|
||||
),
|
||||
),
|
||||
title: Text(title),
|
||||
subtitle: subtitle,
|
||||
leading: icon,
|
||||
trailing: Chip(
|
||||
label: Text(
|
||||
field.value == null ? "None" : getName(field.value as T),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: onChanged == null ? .new(color: Colors.grey) : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(title),
|
||||
subtitle: subtitle,
|
||||
leading: icon,
|
||||
trailing: Chip(
|
||||
label: Text(
|
||||
value == null ? "None" : getName(value as T),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: onChanged == null ? .new(color: Colors.grey) : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
#include <app_links_linux/app_links_plugin_linux.h>
|
||||
#include <dynamic_color/dynamic_color_plugin.h>
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <flutter_avif_linux/flutter_avif_linux_plugin.h>
|
||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||
#include <media_kit_video/media_kit_video_plugin.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
|
|
@ -28,9 +27,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_avif_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAvifLinuxPlugin");
|
||||
flutter_avif_linux_plugin_register_with_registrar(flutter_avif_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
||||
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
app_links_linux
|
||||
dynamic_color
|
||||
file_selector_linux
|
||||
flutter_avif_linux
|
||||
media_kit_libs_linux
|
||||
media_kit_video
|
||||
pasteboard
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ let
|
|||
];
|
||||
cmakeVersions = [ "3.22.1" ];
|
||||
platformVersions = [
|
||||
"31"
|
||||
"34"
|
||||
"36"
|
||||
];
|
||||
|
|
@ -48,17 +47,7 @@ pkgs.mkShell {
|
|||
|
||||
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/aapt2";
|
||||
}
|
||||
// lib.optionalAttrs pkgs.stdenv.isLinux (
|
||||
let
|
||||
arch = if pkgs.stdenv.hostPlatform.isAarch64 then "arm64" else "x64";
|
||||
in
|
||||
{
|
||||
LD_LIBRARY_PATH = lib.concatStringsSep ":" [
|
||||
"./build/native_assets/linux"
|
||||
"./build/linux/${arch}/debug/bundle/lib"
|
||||
"./build/linux/${arch}/release/bundle/lib"
|
||||
(lib.makeLibraryPath [ pkgs.zlib ])
|
||||
];
|
||||
}
|
||||
);
|
||||
// lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}";
|
||||
};
|
||||
}
|
||||
|
|
|
|||
49
linux/nix/pkg/avif_native.nix
Normal file
49
linux/nix/pkg/avif_native.nix
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# avif_native's build hook downloads its prebuilt library, which the sandbox
|
||||
# can't do, so bundle it and point the manifest at the local copy instead
|
||||
{
|
||||
stdenv,
|
||||
fetchurl,
|
||||
jq,
|
||||
}:
|
||||
let
|
||||
artifacts = {
|
||||
x86_64-linux = {
|
||||
arch = "x64";
|
||||
sha256 = "252b064f8ff4f0f265bc44294d4a7ed703dd7199688942c272dd8aa8d3039d5a";
|
||||
};
|
||||
aarch64-linux = {
|
||||
arch = "arm64";
|
||||
sha256 = "cdcf17cfd18b9b0f965b72615ddf11246121114577dc570d0bba25964e56c726";
|
||||
};
|
||||
};
|
||||
inherit (artifacts.${stdenv.hostPlatform.system}) arch sha256;
|
||||
key = "linux-${arch}-decode-dav1d";
|
||||
in
|
||||
{ version, src, ... }:
|
||||
stdenv.mkDerivation {
|
||||
pname = "avif_native";
|
||||
inherit version src;
|
||||
inherit (src) passthru;
|
||||
|
||||
nativeBuildInputs = [ jq ];
|
||||
|
||||
library = fetchurl {
|
||||
url = "https://github.com/Zhangw1998/avif_native/releases/download/native-v0.1.0/${key}-libavif_native.so.gz";
|
||||
inherit sha256;
|
||||
};
|
||||
|
||||
# The hook checksums the library, so it must stay byte-identical
|
||||
dontFixup = true;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
cp -r . $out
|
||||
chmod -R u+w $out
|
||||
gunzip -c $library > $out/native/libavif_native.so
|
||||
jq '.artifacts["${key}"].path = "libavif_native.so"' \
|
||||
native/manifest.json > $out/native/manifest.json
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
|
|
@ -34,6 +34,8 @@ flutter.buildFlutterApplication {
|
|||
|
||||
autoPubspecLock = src + "/pubspec.lock";
|
||||
|
||||
customSourceBuilders.avif_native = callPackage ./avif_native.nix { };
|
||||
|
||||
gitHashes = {
|
||||
linkify = "sha256-TpMD6+0zyY6i9l+6d8ErnVufmepCv362rCtnbOht/z4=";
|
||||
fab_m3e = "sha256-5V9m6yY0jimx2gh7lQHYg4HDKQ4Wrl0ATmBhw5DkQMI=";
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ buildGoModule (
|
|||
|
||||
src = "${src}/gomuks";
|
||||
|
||||
vendorHash = "sha256-+0iFK2ribhiNskXgPz7vslncuOgPMB/ZmFSfDOcz6ww=";
|
||||
vendorHash = "sha256-nd0NEKXscgrFE/gBEW2aP+YeZNTOdbRa4QDLTRwIAZU=";
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import Foundation
|
|||
import app_links
|
||||
import dynamic_color
|
||||
import file_selector_macos
|
||||
import flutter_avif_macos
|
||||
import flutter_local_notifications
|
||||
import media_kit_libs_macos_video
|
||||
import media_kit_video
|
||||
|
|
@ -16,7 +15,6 @@ import package_info_plus
|
|||
import pasteboard
|
||||
import screen_retriever_macos
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
import wakelock_plus
|
||||
import webcrypto
|
||||
|
|
@ -26,7 +24,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FlutterAvifPlugin.register(with: registry.registrar(forPlugin: "FlutterAvifPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||
|
|
@ -34,7 +31,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
||||
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||
WebcryptoPlugin.register(with: registry.registrar(forPlugin: "WebcryptoPlugin"))
|
||||
|
|
|
|||
144
pubspec.lock
144
pubspec.lock
|
|
@ -105,6 +105,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
avif_native:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: avif_native
|
||||
sha256: "0a374a7ddc6e48d671792bd33efd9f46f199389cdcd42561210bd886facbc62e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -313,14 +321,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
exif:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: exif
|
||||
sha256: a7980fdb3b7ffcd0b035e5b8a5e1eef7cadfe90ea6a4e85ebb62f87b96c7a172
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
fab_m3e:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
|
|
@ -455,70 +455,6 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_avif:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_avif
|
||||
sha256: "6035f073189c1ae134affa73338c2eca79bf412e9abdb8f8628fdefcccff4444"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_avif_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_avif_android
|
||||
sha256: "1da135ea0d74225fae3154f954f9ba4cf7aa5e56b30d9851fcfc0ddecab9d8c5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_avif_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_avif_ios
|
||||
sha256: "3daaa599d8fe0193d3ade6cafa1fd4f166d4063b8d499c7c311ce1d170b2759f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_avif_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_avif_linux
|
||||
sha256: dd6179edca12c720761b3acc678d7f15208eb8647adbac5a6a90f52d4ba56e2d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_avif_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_avif_macos
|
||||
sha256: "8e39f365dfc5713d4527f07a92399184857ea30eced85f82171bfd0a45e2c73b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_avif_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_avif_platform_interface
|
||||
sha256: f5c110bdb7de2d4ab4902bfe2ec331fe24bc19b6d54f00ac4fa165effc0edcda
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_avif_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_avif_web
|
||||
sha256: db2ff395b08cbfe9116e48ac65c428ba21a9159972ea019f8d7be580ff6cf6e8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_avif_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_avif_windows
|
||||
sha256: e0c7722df305a6621f4ecdd83098025c705bcf0e8ce9519e03c6f515abcf351d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
flutter_blurhash:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -527,14 +463,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.1"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_cache_manager
|
||||
sha256: f94fc6f5480977e3eb7b622ed488f671ba96c971bd0e274e6bd0f0ba9b044816
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.5"
|
||||
flutter_file_dialog:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1265,14 +1193,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.6"
|
||||
protobuf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protobuf
|
||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1486,54 +1406,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
sqflite:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "273218a1678360a3127fcf15e543381d9b813d413d783bc6e9a53eed4d2eb8fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4+2"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: e4f659c14bdd14464127fcd234a7a96f61fe0f539b8568e5a38f99f5a568a835
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4+1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: b80e94ff0d551d5f1f8c7d14348d29b576cca3ae30f6e195a7c380552c32b1e0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.14"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "8de68fc306040f992aaf378750c16f95e4298c4bb150dd45091d75e42b1f08ee"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4+1"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_platform_interface
|
||||
sha256: c9177b2f3150ed0647b90a94307c28452fe0a5401863044216aed83addd6c7c5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ dependencies:
|
|||
pasteboard: 0.5.0
|
||||
flutter_file_dialog: 3.3.3
|
||||
mime: 2.1.0
|
||||
flutter_avif: 3.1.0
|
||||
avif_native: 0.1.1
|
||||
dev_dependencies:
|
||||
build_runner: 2.16.2
|
||||
flutter_lints: 6.0.0
|
||||
|
|
@ -133,3 +133,8 @@ flutter_launcher_icons:
|
|||
macos:
|
||||
generate: true
|
||||
image_path: assets/mobile.png
|
||||
|
||||
hooks:
|
||||
user_defines:
|
||||
avif_native:
|
||||
features: decode
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
#include <app_links/app_links_plugin_c_api.h>
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <flutter_avif_windows/flutter_avif_windows_plugin.h>
|
||||
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
|
||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||
#include <pasteboard/pasteboard_plugin.h>
|
||||
|
|
@ -25,8 +24,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FlutterAvifWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterAvifWindowsPlugin"));
|
||||
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi"));
|
||||
MediaKitVideoPluginCApiRegisterWithRegistrar(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
app_links
|
||||
dynamic_color
|
||||
file_selector_windows
|
||||
flutter_avif_windows
|
||||
media_kit_libs_windows_video
|
||||
media_kit_video
|
||||
pasteboard
|
||||
|
|
|
|||
Loading…
Reference in a new issue