diff --git a/.github/workflows/versions.env b/.github/workflows/versions.env index 4d5a9ba26..118db7025 100644 --- a/.github/workflows/versions.env +++ b/.github/workflows/versions.env @@ -1,2 +1,2 @@ -FLUTTER_VERSION=3.35.3 +FLUTTER_VERSION=3.35.5 JAVA_VERSION=17 diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 50a2e0afb..c60ef4e97 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -14,6 +14,7 @@ if (file("google-services.json").exists()) { dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") // For flutter_local_notifications // Workaround for: https://github.com/MaikuB/flutter_local_notifications/issues/2286 + implementation("androidx.core:core-ktx:1.17.0") // For Android Auto } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ddb933f17..f4bfeaf5c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -174,6 +174,11 @@ + + + + + \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e33907ed2..9c957989d 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Mar 17 08:36:03 CET 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 62e05cf33..a5d070147 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -18,7 +18,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.3" apply false + id("com.android.application") version "8.9.1" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false if (file("app/google-services.json").exists()) { id("com.google.gms.google-services") version "4.3.8" apply false diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index e77008266..e84eada49 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -3388,6 +3388,12 @@ "@declineInvitation": {}, "noMessagesYet": "Pole veel ühtegi sõnumit", "@noMessagesYet": {}, + "longPressToRecordVoiceMessage": "Pika vajutusega saad salvestada häälsõnumi.", + "@longPressToRecordVoiceMessage": {}, + "pause": "Peata", + "@pause": {}, + "resume": "Jätka", + "@resume": {}, "writeAMessageLangCodes": "Kirjuta {l1} või {l2}...", "requests": "Päringud", "holdForInfo": "Vajuta ja hoia sõna info saamiseks.", diff --git a/lib/l10n/intl_gl.arb b/lib/l10n/intl_gl.arb index 1b7af11a4..fa0c16f64 100644 --- a/lib/l10n/intl_gl.arb +++ b/lib/l10n/intl_gl.arb @@ -3384,8 +3384,16 @@ "@declineInvitation": {}, "noMessagesYet": "Aínda non hai mensaxes", "@noMessagesYet": {}, - "checkList": "Lista de verificación", - "displayNavigationRail": "Mostrar rail de navegación en móbil", + "checkList": "Comprobar lista", + "@checkList": {}, + "displayNavigationRail": "Mostrar carril de navegación nos móbiles", + "@displayNavigationRail": {}, + "longPressToRecordVoiceMessage": "Pulsación longa para gravar mensaxe de voz.", + "@longPressToRecordVoiceMessage": {}, + "pause": "Deter", + "@pause": {}, + "resume": "Continuar", + "@resume": {}, "writeAMessageLangCodes": "Escribe en {l1} ou {l2}...", "requests": "Solicitudes", "holdForInfo": "Fai clic e mantén para obter información sobre a palabra.", @@ -4513,14 +4521,6 @@ "inviteYourFriends": "Invita aos teus amigos", "playWithAI": "Xoga con IA por agora", "courseStartDesc": "O Pangea Bot está listo para comezar en calquera momento!\n\n...pero aprender é mellor con amigos!", - "@checkList": { - "type": "String", - "placeholders": {} - }, - "@displayNavigationRail": { - "type": "String", - "placeholders": {} - }, "@writeAMessageLangCodes": { "type": "String", "placeholders": { diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index f831346f2..6f07d9918 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -3387,6 +3387,12 @@ "@declineInvitation": {}, "noMessagesYet": "Nog geen berichten", "@noMessagesYet": {}, + "longPressToRecordVoiceMessage": "Lang drukken om een spraakbericht op te nemen.", + "@longPressToRecordVoiceMessage": {}, + "pause": "Pauzeer", + "@pause": {}, + "resume": "Hervat", + "@resume": {}, "writeAMessageLangCodes": "Typ in {l1} of {l2}...", "requests": "Verzoeken", "holdForInfo": "Klik en houd vast voor woordinformatie.", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 1db7dda2f..d5bd4fc7d 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -3388,6 +3388,12 @@ "@declineInvitation": {}, "noMessagesYet": "尚无消息", "@noMessagesYet": {}, + "longPressToRecordVoiceMessage": "长按录制语音消息。", + "@longPressToRecordVoiceMessage": {}, + "pause": "暂停", + "@pause": {}, + "resume": "继续", + "@resume": {}, "writeAMessageLangCodes": "输入 {l1} 或 {l2}...", "requests": "请求", "holdForInfo": "点击并按住获取单词信息。", diff --git a/lib/pages/settings_ignore_list/settings_ignore_list.dart b/lib/pages/settings_ignore_list/settings_ignore_list.dart index c55987e34..3bcea3dfe 100644 --- a/lib/pages/settings_ignore_list/settings_ignore_list.dart +++ b/lib/pages/settings_ignore_list/settings_ignore_list.dart @@ -46,22 +46,7 @@ class SettingsIgnoreListController extends State { final client = Matrix.of(context).client; showFutureLoadingDialog( context: context, - future: () async { - for (final room in client.rooms) { - final isInviteFromUser = room.membership == Membership.invite && - room.getState(EventTypes.RoomMember, client.userID!)?.senderId == - userId; - - if (room.directChatMatrixID == userId || isInviteFromUser) { - try { - await room.leave(); - } catch (e, s) { - Logs().w('Unable to leave room with blocked user $userId', e, s); - } - } - } - await client.ignoreUser(userId); - }, + future: () => client.ignoreUser(userId), ); setState(() {}); controller.clear(); diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index b2285e7f2..2bec4ecf2 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -97,6 +97,7 @@ class BackgroundPush { NotificationResponseJson.fromJsonString(message), client: client, router: FluffyChatApp.router, + l10n: l10n, ); } catch (e, s) { Logs().wtf('Main Notification Tap crashed', e, s); @@ -122,6 +123,7 @@ class BackgroundPush { response, client: client, router: FluffyChatApp.router, + l10n: l10n, ), onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); @@ -456,6 +458,7 @@ class BackgroundPush { response, client: client, router: FluffyChatApp.router, + l10n: l10n, ); } }); diff --git a/lib/utils/notification_background_handler.dart b/lib/utils/notification_background_handler.dart index 8c9a279ee..531d7e19d 100644 --- a/lib/utils/notification_background_handler.dart +++ b/lib/utils/notification_background_handler.dart @@ -8,7 +8,14 @@ import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/utils/client_download_content_extension.dart'; import 'package:fluffychat/utils/client_manager.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/push_helper.dart'; +import '../config/app_config.dart'; +import '../config/setting_keys.dart'; bool _vodInitialized = false; @@ -52,9 +59,10 @@ void notificationTapBackground( await vod.init(); _vodInitialized = true; } + final store = await SharedPreferences.getInstance(); final client = (await ClientManager.getClients( initialize: false, - store: await SharedPreferences.getInstance(), + store: store, )) .first; await client.abortSync(); @@ -62,6 +70,11 @@ void notificationTapBackground( waitForFirstSync: false, waitUntilLoadCompletedLoaded: false, ); + + AppConfig.sendPublicReadReceipts = + store.getBool(SettingKeys.sendPublicReadReceipts) ?? + AppConfig.sendPublicReadReceipts; + if (!client.isLogged()) { throw Exception('Notification tab in background but not logged in!'); } @@ -77,14 +90,17 @@ Future notificationTap( NotificationResponse notificationResponse, { GoRouter? router, required Client client, + L10n? l10n, }) async { Logs().d( 'Notification action handler started', notificationResponse.notificationResponseType.name, ); + final payload = + FluffyChatPushPayload.fromString(notificationResponse.payload ?? ''); switch (notificationResponse.notificationResponseType) { case NotificationResponseType.selectedNotification: - final roomId = notificationResponse.payload; + final roomId = payload.roomId; if (roomId == null) return; if (router == null) { @@ -111,7 +127,7 @@ Future notificationTap( if (actionType == null) { throw Exception('Selected notification with action but no action ID'); } - final roomId = notificationResponse.payload; + final roomId = payload.roomId; if (roomId == null) { throw Exception('Selected notification with action but no payload'); } @@ -127,9 +143,9 @@ Future notificationTap( switch (actionType) { case FluffyChatNotificationActions.markAsRead: await room.setReadMarker( - room.lastEvent!.eventId, - mRead: room.lastEvent!.eventId, - public: false, // TODO: Load preference here + payload.eventId ?? room.lastEvent!.eventId, + mRead: payload.eventId ?? room.lastEvent!.eventId, + public: AppConfig.sendPublicReadReceipts, ); case FluffyChatNotificationActions.reply: final input = notificationResponse.input; @@ -138,7 +154,90 @@ Future notificationTap( 'Selected notification with reply action but without input', ); } - await room.sendTextEvent(input); + + final eventId = await room.sendTextEvent( + input, + parseCommands: false, + displayPendingEvent: false, + ); + + if (PlatformInfos.isAndroid) { + final ownProfile = await room.client.fetchOwnProfile(); + final avatar = ownProfile.avatarUrl; + final avatarFile = avatar == null + ? null + : await client + .downloadMxcCached( + avatar, + thumbnailMethod: ThumbnailMethod.crop, + width: notificationAvatarDimension, + height: notificationAvatarDimension, + animated: false, + isThumbnail: true, + rounded: true, + ) + .timeout(const Duration(seconds: 3)); + final messagingStyleInformation = + await AndroidFlutterLocalNotificationsPlugin() + .getActiveNotificationMessagingStyle(room.id.hashCode); + if (messagingStyleInformation == null) return; + l10n ??= await lookupL10n(PlatformDispatcher.instance.locale); + messagingStyleInformation.messages?.add( + Message( + input, + DateTime.now(), + Person( + key: room.client.userID, + name: l10n.you, + icon: avatarFile == null + ? null + : ByteArrayAndroidIcon(avatarFile), + ), + ), + ); + + await FlutterLocalNotificationsPlugin().show( + room.id.hashCode, + room.getLocalizedDisplayname(MatrixLocals(l10n)), + input, + NotificationDetails( + android: AndroidNotificationDetails( + AppConfig.pushNotificationsChannelId, + l10n.incomingMessages, + category: AndroidNotificationCategory.message, + shortcutId: room.id, + styleInformation: messagingStyleInformation, + groupKey: room.id, + playSound: false, + enableVibration: false, + actions: [ + AndroidNotificationAction( + FluffyChatNotificationActions.reply.name, + l10n.reply, + inputs: [ + AndroidNotificationActionInput( + label: l10n.writeAMessage, + ), + ], + cancelNotification: false, + allowGeneratedReplies: true, + semanticAction: SemanticAction.reply, + ), + AndroidNotificationAction( + FluffyChatNotificationActions.markAsRead.name, + l10n.markAsRead, + semanticAction: SemanticAction.markAsRead, + ), + ], + ), + ), + payload: FluffyChatPushPayload( + client.clientName, + room.id, + eventId, + ).toString(), + ); + } } } } diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index eb2f9fa00..8854eb629 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -45,7 +45,7 @@ Future pushHelper( } catch (e, s) { Logs().e('Push Helper has crashed! Writing into temporary file', e, s); - l10n ??= await lookupL10n(const Locale('en')); + l10n ??= await lookupL10n(PlatformDispatcher.instance.locale); flutterLocalNotificationsPlugin.show( notification.roomId?.hashCode ?? 0, // #Pangea @@ -311,10 +311,12 @@ Future _tryPushHelper( ], cancelNotification: false, allowGeneratedReplies: true, + semanticAction: SemanticAction.reply, ), AndroidNotificationAction( FluffyChatNotificationActions.markAsRead.name, l10n.markAsRead, + semanticAction: SemanticAction.markAsRead, ), ], ); @@ -353,13 +355,32 @@ Future _tryPushHelper( body, platformChannelSpecifics, // #Pangea - // payload: event.roomId, + // payload: + // FluffyChatPushPayload(client.clientName, event.room.id, event.eventId) + // .toString(), payload: payload, // Pangea# ); Logs().v('Push helper has been completed!'); } +class FluffyChatPushPayload { + final String? clientName, roomId, eventId; + + FluffyChatPushPayload(this.clientName, this.roomId, this.eventId); + + factory FluffyChatPushPayload.fromString(String payload) { + final parts = payload.split('|'); + if (parts.length != 3) { + return FluffyChatPushPayload(null, null, null); + } + return FluffyChatPushPayload(parts[0], parts[1], parts[2]); + } + + @override + String toString() => '$clientName|$roomId|$eventId'; +} + /// Creates a shortcut for Android platform but does not block displaying the /// notification. This is optional but provides a nicer view of the /// notification popup. diff --git a/pubspec.lock b/pubspec.lock index 8d0ecc6b5..6aacdbe86 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2061,6 +2061,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" scroll_to_index: dependency: "direct main" description: @@ -2478,26 +2518,42 @@ packages: dependency: "direct main" description: name: unifiedpush - sha256: "1418375efb580af9640de4eaf4209cb6481f9a48792648ced3051f30e67d9568" + sha256: "8ed9767f750a1dc6159a77e2171641d0cb825dc87682d1ce1b8618689b79f58e" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.2.0" unifiedpush_android: dependency: transitive description: name: unifiedpush_android - sha256: "2f25db8eb2fc3183bf2e43db89fff20b2587adc1c361e1d1e06b223a0d45b50a" + sha256: "556796c81e8151ee8e4275baea2f7191119e8b1412ec35523cc2ac1c44c348bf" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.4.0" + unifiedpush_linux: + dependency: transitive + description: + name: unifiedpush_linux + sha256: c062d5eedd1cec70bcd33270cc4e01ae0ff6501f33d471167c06b34a968adfeb + url: "https://pub.dev" + source: hosted + version: "1.0.0" unifiedpush_platform_interface: dependency: transitive description: name: unifiedpush_platform_interface - sha256: bb49d2748211520e35e0374ab816faa8a2c635267e71909d334ad868d532eba5 + sha256: "83372bc8d794b8b12ef6993b518d7be907dcfc2191bdf6de0ece5c4445d89880" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.0" + unifiedpush_storage_interface: + dependency: transitive + description: + name: unifiedpush_storage_interface + sha256: b8d423a4695efc616aa21d8ab48fb5ef99d6288c68b56282b8faac1579ceabd9 + url: "https://pub.dev" + source: hosted + version: "1.0.0" unifiedpush_ui: dependency: "direct main" description: @@ -2746,6 +2802,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + webcrypto: + dependency: transitive + description: + name: webcrypto + sha256: e393b3d0b01694a8f81efecf278ed7392877130e6e7b29f578863e4f2d0b2ebd + url: "https://pub.dev" + source: hosted + version: "0.5.8" webdriver: dependency: transitive description: @@ -2762,6 +2826,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + webpush_encryption: + dependency: transitive + description: + name: webpush_encryption + sha256: "63046b7d6909f4a72ce3c153fa574726e257aaf21b1995ba063dc241a1b1520b" + url: "https://pub.dev" + source: hosted + version: "1.0.0" webrtc_interface: dependency: "direct main" description: @@ -2786,6 +2858,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + window_manager: + dependency: transitive + description: + name: window_manager + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" + url: "https://pub.dev" + source: hosted + version: "0.5.1" window_to_front: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ed6d4d701..f933759b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -91,7 +91,7 @@ dependencies: sqlcipher_flutter_libs: ^0.6.8 swipe_to_action: ^0.3.0 tor_detector_web: ^1.1.0 - unifiedpush: ^6.0.2 + unifiedpush: ^6.2.0 unifiedpush_ui: ^0.1.0 universal_html: ^2.2.4 url_launcher: ^6.3.2