LCOV - code coverage report
Current view: top level - lib/src/utils - event_localizations.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 61.5 % 179 110
Test Date: 2025-10-13 02:23:18 Functions: - 0 0

            Line data    Source code
       1              : /*
       2              :  *   Famedly Matrix SDK
       3              :  *   Copyright (C) 2019, 2020, 2021 Famedly GmbH
       4              :  *
       5              :  *   This program is free software: you can redistribute it and/or modify
       6              :  *   it under the terms of the GNU Affero General Public License as
       7              :  *   published by the Free Software Foundation, either version 3 of the
       8              :  *   License, or (at your option) any later version.
       9              :  *
      10              :  *   This program is distributed in the hope that it will be useful,
      11              :  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      13              :  *   GNU Affero General Public License for more details.
      14              :  *
      15              :  *   You should have received a copy of the GNU Affero General Public License
      16              :  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
      17              :  */
      18              : 
      19              : import 'package:collection/collection.dart';
      20              : 
      21              : import 'package:matrix/encryption.dart';
      22              : import 'package:matrix/matrix.dart';
      23              : 
      24              : abstract class EventLocalizations {
      25              :   // As we need to create the localized body off of a different set of parameters, we
      26              :   // might create it with `event.plaintextBody`, maybe with `event.body`, maybe with the
      27              :   // reply fallback stripped, and maybe with the new body in `event.content['m.new_content']`.
      28              :   // Thus, it seems easier to offload that logic into `Event.getLocalizedBody()` and pass the
      29              :   // `body` variable around here.
      30            2 :   static String _localizedBodyNormalMessage(
      31              :     Event event,
      32              :     MatrixLocalizations i18n,
      33              :     String body,
      34              :   ) {
      35            2 :     switch (event.messageType) {
      36            2 :       case MessageTypes.Image:
      37            2 :         return i18n.sentAPicture(
      38            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      39              :         );
      40            2 :       case MessageTypes.File:
      41            2 :         return i18n.sentAFile(
      42            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      43              :         );
      44            2 :       case MessageTypes.Audio:
      45            4 :         if (event.content.tryGetMap('org.matrix.msc3245.voice') != null) {
      46            0 :           final durationInt = event.content
      47            0 :               .tryGetMap<String, Object?>('info')
      48            0 :               ?.tryGet<int>('duration');
      49            0 :           return i18n.voiceMessage(
      50            0 :             event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      51            0 :             durationInt == null ? null : Duration(milliseconds: durationInt),
      52              :           );
      53              :         }
      54            2 :         return i18n.sentAnAudio(
      55            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      56              :         );
      57            2 :       case MessageTypes.Video:
      58            2 :         return i18n.sentAVideo(
      59            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      60              :         );
      61            2 :       case MessageTypes.Location:
      62            2 :         return i18n.sharedTheLocation(
      63            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      64              :         );
      65            2 :       case MessageTypes.Sticker:
      66            0 :         return i18n.sentASticker(
      67            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      68              :         );
      69            2 :       case MessageTypes.Emote:
      70            2 :         return '* $body';
      71            2 :       case EventTypes.KeyVerificationRequest:
      72            2 :         return i18n.requestedKeyVerification(
      73            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      74              :         );
      75            2 :       case EventTypes.KeyVerificationCancel:
      76            2 :         return i18n.canceledKeyVerification(
      77            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      78              :         );
      79            2 :       case EventTypes.KeyVerificationDone:
      80            2 :         return i18n.completedKeyVerification(
      81            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      82              :         );
      83            2 :       case EventTypes.KeyVerificationReady:
      84            2 :         return i18n.isReadyForKeyVerification(
      85            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      86              :         );
      87            2 :       case EventTypes.KeyVerificationAccept:
      88            2 :         return i18n.acceptedKeyVerification(
      89            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      90              :         );
      91            2 :       case EventTypes.KeyVerificationStart:
      92            2 :         return i18n.startedKeyVerification(
      93            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
      94              :         );
      95            2 :       case MessageTypes.BadEncrypted:
      96              :         String errorText;
      97            0 :         switch (event.body) {
      98            0 :           case DecryptException.channelCorrupted:
      99            0 :             errorText = '${i18n.channelCorruptedDecryptError}.';
     100              :             break;
     101            0 :           case DecryptException.notEnabled:
     102            0 :             errorText = '${i18n.encryptionNotEnabled}.';
     103              :             break;
     104            0 :           case DecryptException.unknownAlgorithm:
     105            0 :             errorText = '${i18n.unknownEncryptionAlgorithm}.';
     106              :             break;
     107            0 :           case DecryptException.unknownSession:
     108            0 :             errorText = '${i18n.noPermission}.';
     109              :             break;
     110              :           default:
     111              :             errorText = body;
     112              :             break;
     113              :         }
     114            0 :         return i18n.couldNotDecryptMessage(errorText);
     115              :       case MessageTypes.Text:
     116              :       case MessageTypes.Notice:
     117              :       case MessageTypes.None:
     118              :       default:
     119              :         return body;
     120              :     }
     121              :   }
     122              : 
     123              :   // This map holds how to localize event types, and thus which event types exist.
     124              :   // If an event exists but it does not have a localized body, set its callback to null
     125              :   static final Map<String,
     126              :           String Function(Event event, MatrixLocalizations i18n, String body)?>
     127            6 :       localizationsMap = {
     128            4 :     EventTypes.Sticker: (event, i18n, body) => i18n.sentASticker(
     129            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     130              :         ),
     131            4 :     EventTypes.Redaction: (event, i18n, body) => i18n.redactedAnEvent(event),
     132            4 :     EventTypes.RoomAliases: (event, i18n, body) => i18n.changedTheRoomAliases(
     133            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     134              :         ),
     135            2 :     EventTypes.RoomCanonicalAlias: (event, i18n, body) =>
     136            2 :         i18n.changedTheRoomInvitationLink(
     137            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     138              :         ),
     139            4 :     EventTypes.RoomCreate: (event, i18n, body) => i18n.createdTheChat(
     140            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     141              :         ),
     142            4 :     EventTypes.RoomTombstone: (event, i18n, body) => i18n.roomHasBeenUpgraded,
     143            2 :     EventTypes.RoomJoinRules: (event, i18n, body) {
     144            2 :       final joinRules = JoinRules.values.firstWhereOrNull(
     145            2 :         (r) =>
     146            6 :             r.toString().replaceAll('JoinRules.', '') ==
     147            4 :             event.content['join_rule'],
     148              :       );
     149              :       if (joinRules == null) {
     150            0 :         return i18n.changedTheJoinRules(
     151            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     152              :         );
     153              :       } else {
     154            2 :         return i18n.changedTheJoinRulesTo(
     155            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     156            2 :           joinRules.getLocalizedString(i18n),
     157              :         );
     158              :       }
     159              :     },
     160            2 :     EventTypes.RoomMember: (event, i18n, body) {
     161            4 :       final targetName = event.stateKeyUser?.calcDisplayname(i18n: i18n) ?? '';
     162              :       final senderName =
     163            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n);
     164           10 :       final userIsTarget = event.stateKey == event.room.client.userID;
     165           10 :       final userIsSender = event.senderId == event.room.client.userID;
     166              : 
     167            2 :       switch (event.roomMemberChangeType) {
     168            2 :         case RoomMemberChangeType.avatar:
     169            0 :           return i18n.changedTheProfileAvatar(targetName);
     170            2 :         case RoomMemberChangeType.displayname:
     171              :           final newDisplayname =
     172            0 :               event.content.tryGet<String>('displayname') ?? '';
     173              :           final oldDisplayname =
     174            0 :               event.prevContent?.tryGet<String>('displayname') ?? '';
     175            0 :           return i18n.changedTheDisplaynameTo(oldDisplayname, newDisplayname);
     176            2 :         case RoomMemberChangeType.join:
     177              :           return userIsTarget
     178            0 :               ? i18n.youJoinedTheChat
     179            2 :               : i18n.joinedTheChat(targetName);
     180            2 :         case RoomMemberChangeType.acceptInvite:
     181              :           return userIsTarget
     182            0 :               ? i18n.youAcceptedTheInvitation
     183            2 :               : i18n.acceptedTheInvitation(targetName);
     184            2 :         case RoomMemberChangeType.rejectInvite:
     185              :           return userIsTarget
     186            0 :               ? i18n.youRejectedTheInvitation
     187            2 :               : i18n.rejectedTheInvitation(targetName);
     188            2 :         case RoomMemberChangeType.withdrawInvitation:
     189              :           return userIsSender
     190            0 :               ? i18n.youHaveWithdrawnTheInvitationFor(targetName)
     191            2 :               : i18n.hasWithdrawnTheInvitationFor(senderName, targetName);
     192            2 :         case RoomMemberChangeType.leave:
     193            0 :           return i18n.userLeftTheChat(targetName);
     194            2 :         case RoomMemberChangeType.kick:
     195              :           return userIsSender
     196            0 :               ? i18n.youKicked(targetName)
     197            2 :               : i18n.kicked(senderName, targetName);
     198            2 :         case RoomMemberChangeType.invite:
     199              :           return userIsSender
     200            0 :               ? i18n.youInvitedUser(targetName)
     201              :               : userIsTarget
     202            0 :                   ? i18n.youInvitedBy(senderName)
     203            2 :                   : i18n.invitedUser(senderName, targetName);
     204            2 :         case RoomMemberChangeType.ban:
     205              :           return userIsSender
     206            0 :               ? i18n.youBannedUser(targetName)
     207            2 :               : i18n.bannedUser(senderName, targetName);
     208            0 :         case RoomMemberChangeType.unban:
     209              :           return userIsSender
     210            0 :               ? i18n.youUnbannedUser(targetName)
     211            0 :               : i18n.unbannedUser(senderName, targetName);
     212            0 :         case RoomMemberChangeType.knock:
     213            0 :           return i18n.hasKnocked(targetName);
     214            0 :         case RoomMemberChangeType.other:
     215              :           return userIsTarget
     216            0 :               ? i18n.youJoinedTheChat
     217            0 :               : i18n.joinedTheChat(targetName);
     218              :       }
     219              :     },
     220            2 :     EventTypes.RoomPowerLevels: (event, i18n, body) =>
     221            2 :         i18n.changedTheChatPermissions(
     222            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     223              :         ),
     224            4 :     EventTypes.RoomName: (event, i18n, body) => i18n.changedTheChatNameTo(
     225            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     226            4 :           event.content.tryGet<String>('name') ?? '',
     227              :         ),
     228            2 :     EventTypes.RoomTopic: (event, i18n, body) =>
     229            2 :         i18n.changedTheChatDescriptionTo(
     230            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     231            4 :           event.content.tryGet<String>('topic') ?? '',
     232              :         ),
     233            4 :     EventTypes.RoomAvatar: (event, i18n, body) => i18n.changedTheChatAvatar(
     234            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     235              :         ),
     236            0 :     EventTypes.GuestAccess: (event, i18n, body) {
     237            0 :       final guestAccess = GuestAccess.values.firstWhereOrNull(
     238            0 :         (r) =>
     239            0 :             r.toString().replaceAll('GuestAccess.', '') ==
     240            0 :             event.content['guest_access'],
     241              :       );
     242              :       if (guestAccess == null) {
     243            0 :         return i18n.changedTheGuestAccessRules(
     244            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     245              :         );
     246              :       } else {
     247            0 :         return i18n.changedTheGuestAccessRulesTo(
     248            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     249            0 :           guestAccess.getLocalizedString(i18n),
     250              :         );
     251              :       }
     252              :     },
     253            2 :     EventTypes.HistoryVisibility: (event, i18n, body) {
     254            2 :       final historyVisibility = HistoryVisibility.values.firstWhereOrNull(
     255            2 :         (r) =>
     256            6 :             r.toString().replaceAll('HistoryVisibility.', '') ==
     257            4 :             event.content['history_visibility'],
     258              :       );
     259              :       if (historyVisibility == null) {
     260            0 :         return i18n.changedTheHistoryVisibility(
     261            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     262              :         );
     263              :       } else {
     264            2 :         return i18n.changedTheHistoryVisibilityTo(
     265            4 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     266            2 :           historyVisibility.getLocalizedString(i18n),
     267              :         );
     268              :       }
     269              :     },
     270            2 :     EventTypes.Encryption: (event, i18n, body) {
     271            2 :       var localizedBody = i18n.activatedEndToEndEncryption(
     272            4 :         event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     273              :       );
     274            8 :       if (event.room.client.encryptionEnabled == false) {
     275            6 :         localizedBody += '. ${i18n.needPantalaimonWarning}';
     276              :       }
     277              :       return localizedBody;
     278              :     },
     279            0 :     EventTypes.CallAnswer: (event, i18n, body) => i18n.answeredTheCall(
     280            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     281              :         ),
     282            0 :     EventTypes.CallHangup: (event, i18n, body) => i18n.endedTheCall(
     283            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     284              :         ),
     285            0 :     EventTypes.CallInvite: (event, i18n, body) => i18n.startedACall(
     286            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     287              :         ),
     288            0 :     EventTypes.CallCandidates: (event, i18n, body) => i18n.sentCallInformations(
     289            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     290              :         ),
     291            0 :     EventTypes.Encrypted: (event, i18n, body) =>
     292            0 :         _localizedBodyNormalMessage(event, i18n, body),
     293            2 :     EventTypes.Message: (event, i18n, body) =>
     294            2 :         _localizedBodyNormalMessage(event, i18n, body),
     295            0 :     EventTypes.Reaction: (event, i18n, body) => i18n.sentReaction(
     296            0 :           event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
     297            0 :           event.content
     298            0 :                   .tryGetMap<String, Object?>('m.relates_to')
     299            0 :                   ?.tryGet<String>('key') ??
     300              :               body,
     301              :         ),
     302            0 :     EventTypes.refreshingLastEvent: (_, i18n, ___) => i18n.refreshingLastEvent,
     303              :   };
     304              : }
        

Generated by: LCOV version 2.0-1