/* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import 'package:collection/collection.dart'; import '../../encryption.dart'; import '../../matrix.dart'; abstract class EventLocalizations { // As we need to create the localized body off of a different set of parameters, we // might create it with `event.plaintextBody`, maybe with `event.body`, maybe with the // reply fallback stripped, and maybe with the new body in `event.content['m.new_content']`. // Thus, it seems easier to offload that logic into `Event.getLocalizedBody()` and pass the // `body` variable around here. static String _localizedBodyNormalMessage( Event event, MatrixLocalizations i18n, String body) { switch (event.messageType) { case MessageTypes.Image: return i18n.sentAPicture(event.sender.calcDisplayname()); case MessageTypes.File: return i18n.sentAFile(event.sender.calcDisplayname()); case MessageTypes.Audio: return i18n.sentAnAudio(event.sender.calcDisplayname()); case MessageTypes.Video: return i18n.sentAVideo(event.sender.calcDisplayname()); case MessageTypes.Location: return i18n.sharedTheLocation(event.sender.calcDisplayname()); case MessageTypes.Sticker: return i18n.sentASticker(event.sender.calcDisplayname()); case MessageTypes.Emote: return '* $body'; case MessageTypes.BadEncrypted: String errorText; switch (event.body) { case DecryptException.channelCorrupted: errorText = i18n.channelCorruptedDecryptError + '.'; break; case DecryptException.notEnabled: errorText = i18n.encryptionNotEnabled + '.'; break; case DecryptException.unknownAlgorithm: errorText = i18n.unknownEncryptionAlgorithm + '.'; break; case DecryptException.unknownSession: errorText = i18n.noPermission + '.'; break; default: errorText = body; break; } return i18n.couldNotDecryptMessage(errorText); case MessageTypes.Text: case MessageTypes.Notice: case MessageTypes.None: default: return body; } } // This map holds how to localize event types, and thus which event types exist. // If an event exists but it does not have a localized body, set its callback to null static final Map localizationsMap = { EventTypes.Sticker: (event, i18n, body) => i18n.sentASticker(event.sender.calcDisplayname()), EventTypes.Redaction: (event, i18n, body) => i18n.redactedAnEvent(event.sender.calcDisplayname()), EventTypes.RoomAliases: (event, i18n, body) => i18n.changedTheRoomAliases(event.sender.calcDisplayname()), EventTypes.RoomCanonicalAlias: (event, i18n, body) => i18n.changedTheRoomInvitationLink(event.sender.calcDisplayname()), EventTypes.RoomCreate: (event, i18n, body) => i18n.createdTheChat(event.sender.calcDisplayname()), EventTypes.RoomTombstone: (event, i18n, body) => i18n.roomHasBeenUpgraded, EventTypes.RoomJoinRules: (event, i18n, body) { final joinRules = JoinRules.values.firstWhereOrNull((r) => r.toString().replaceAll('JoinRules.', '') == event.content['join_rule']); if (joinRules == null) { return i18n.changedTheJoinRules(event.sender.calcDisplayname()); } else { return i18n.changedTheJoinRulesTo( event.sender.calcDisplayname(), joinRules.getLocalizedString(i18n)); } }, EventTypes.RoomMember: (event, i18n, body) { var text = 'Failed to parse member event'; final targetName = event.stateKeyUser?.calcDisplayname() ?? ''; // Has the membership changed? final newMembership = event.content['membership'] ?? ''; final oldMembership = event.prevContent?['membership'] ?? ''; if (newMembership != oldMembership) { if (oldMembership == 'invite' && newMembership == 'join') { text = i18n.acceptedTheInvitation(targetName); } else if (oldMembership == 'invite' && newMembership == 'leave') { if (event.stateKey == event.senderId) { text = i18n.rejectedTheInvitation(targetName); } else { text = i18n.hasWithdrawnTheInvitationFor( event.sender.calcDisplayname(), targetName); } } else if (oldMembership == 'leave' && newMembership == 'join') { text = i18n.joinedTheChat(targetName); } else if (oldMembership == 'join' && newMembership == 'ban') { text = i18n.kickedAndBanned(event.sender.calcDisplayname(), targetName); } else if (oldMembership == 'join' && newMembership == 'leave' && event.stateKey != event.senderId) { text = i18n.kicked(event.sender.calcDisplayname(), targetName); } else if (oldMembership == 'join' && newMembership == 'leave' && event.stateKey == event.senderId) { text = i18n.userLeftTheChat(targetName); } else if (oldMembership == 'invite' && newMembership == 'ban') { text = i18n.bannedUser(event.sender.calcDisplayname(), targetName); } else if (oldMembership == 'leave' && newMembership == 'ban') { text = i18n.bannedUser(event.sender.calcDisplayname(), targetName); } else if (oldMembership == 'ban' && newMembership == 'leave') { text = i18n.unbannedUser(event.sender.calcDisplayname(), targetName); } else if (newMembership == 'invite') { text = i18n.invitedUser(event.sender.calcDisplayname(), targetName); } else if (newMembership == 'join') { text = i18n.joinedTheChat(targetName); } } else if (newMembership == 'join') { final newAvatar = event.content.tryGet('avatar_url') ?? ''; final oldAvatar = event.prevContent?.tryGet('avatar_url') ?? ''; final newDisplayname = event.content.tryGet('displayname') ?? ''; final oldDisplayname = event.prevContent?.tryGet('displayname') ?? ''; final stateKey = event.stateKey; // Has the user avatar changed? if (newAvatar != oldAvatar) { text = i18n.changedTheProfileAvatar(targetName); } // Has the user displayname changed? else if (newDisplayname != oldDisplayname && stateKey != null) { text = i18n.changedTheDisplaynameTo(oldDisplayname, newDisplayname); } } return text; }, EventTypes.RoomPowerLevels: (event, i18n, body) => i18n.changedTheChatPermissions(event.sender.calcDisplayname()), EventTypes.RoomName: (event, i18n, body) => i18n.changedTheChatNameTo( event.sender.calcDisplayname(), event.content['name']), EventTypes.RoomTopic: (event, i18n, body) => i18n.changedTheChatDescriptionTo( event.sender.calcDisplayname(), event.content['topic']), EventTypes.RoomAvatar: (event, i18n, body) => i18n.changedTheChatAvatar(event.sender.calcDisplayname()), EventTypes.GuestAccess: (event, i18n, body) { final guestAccess = GuestAccess.values.firstWhereOrNull((r) => r.toString().replaceAll('GuestAccess.', '') == event.content['guest_access']); if (guestAccess == null) { return i18n.changedTheGuestAccessRules(event.sender.calcDisplayname()); } else { return i18n.changedTheGuestAccessRulesTo(event.sender.calcDisplayname(), guestAccess.getLocalizedString(i18n)); } }, EventTypes.HistoryVisibility: (event, i18n, body) { final historyVisibility = HistoryVisibility.values.firstWhereOrNull((r) => r.toString().replaceAll('HistoryVisibility.', '') == event.content['history_visibility']); if (historyVisibility == null) { return i18n.changedTheHistoryVisibility(event.sender.calcDisplayname()); } else { return i18n.changedTheHistoryVisibilityTo( event.sender.calcDisplayname(), historyVisibility.getLocalizedString(i18n)); } }, EventTypes.Encryption: (event, i18n, body) { var localizedBody = i18n.activatedEndToEndEncryption(event.sender.calcDisplayname()); if (event.room.client.encryptionEnabled == false) { localizedBody += '. ' + i18n.needPantalaimonWarning; } return localizedBody; }, EventTypes.CallAnswer: (event, i18n, body) => i18n.answeredTheCall(event.sender.calcDisplayname()), EventTypes.CallHangup: (event, i18n, body) => i18n.endedTheCall(event.sender.calcDisplayname()), EventTypes.CallInvite: (event, i18n, body) => i18n.startedACall(event.sender.calcDisplayname()), EventTypes.CallCandidates: (event, i18n, body) => i18n.sentCallInformations(event.sender.calcDisplayname()), EventTypes.Encrypted: (event, i18n, body) => _localizedBodyNormalMessage(event, i18n, body), EventTypes.Message: (event, i18n, body) => _localizedBodyNormalMessage(event, i18n, body), EventTypes.Reaction: (event, i18n, body) => i18n.sentReaction( event.sender.calcDisplayname(), event.content .tryGetMap('m.relates_to') ?.tryGet('key') ?? body, ), }; }