mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-22 15:42:10 +02:00
f69a974d59
# Conflicts: # .github/workflows/ci-build.yml # .github/workflows/ci-tests.yml # .github/workflows/release-alpha.yml # .github/workflows/triage-move-labelled.yml # .github/workflows/triage-priority-bugs.yml # .gitignore # CHANGES.md # Config/AppConfiguration.swift # Config/AppIdentifiers.xcconfig # Config/AppVersion.xcconfig # Config/BuildSettings.swift # Config/CommonConfiguration.swift # Gemfile # Gemfile.lock # IDETemplateMacros.plist # Podfile # Podfile.lock # README.md # Riot.xcodeproj/xcshareddata/xcschemes/Riot.xcscheme # Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button.png # Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@2x.png # Riot/Assets/Images.xcassets/Common/reveal_password_button.imageset/reveal_password_button@3x.png # Riot/Assets/Images.xcassets/People/people_floating_action.imageset/Contents.json # Riot/Assets/Images.xcassets/Rooms/rooms_floating_action.imageset/Contents.json # Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_key.imageset/Contents.json # Riot/Assets/Images.xcassets/Secrets/Recovery/secrets_recovery_passphrase.imageset/Contents.json # Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_key.imageset/Contents.json # Riot/Assets/Images.xcassets/Secrets/Setup/secrets_setup_passphrase.imageset/Contents.json # Riot/Assets/Images.xcassets/TabBar/tab_people.imageset/Contents.json # Riot/Assets/Images.xcassets/TabBar/tab_rooms.imageset/Contents.json # Riot/Assets/SharedImages.xcassets/AppIcon.appiconset/Contents.json # Riot/Assets/SharedImages.xcassets/horizontal_logo.imageset/Contents.json # Riot/Assets/ar.lproj/InfoPlist.strings # Riot/Assets/cs.lproj/Vector.strings # Riot/Assets/de.lproj/InfoPlist.strings # Riot/Assets/de.lproj/Localizable.strings # Riot/Assets/de.lproj/Vector.strings # Riot/Assets/en.lproj/Localizable.strings # Riot/Assets/en.lproj/Untranslated.strings # Riot/Assets/en.lproj/Vector.strings # Riot/Assets/es.lproj/InfoPlist.strings # Riot/Assets/es.lproj/Vector.strings # Riot/Assets/et.lproj/InfoPlist.strings # Riot/Assets/et.lproj/Vector.strings # Riot/Assets/fr.lproj/InfoPlist.strings # Riot/Assets/fr.lproj/Vector.strings # Riot/Assets/hu.lproj/InfoPlist.strings # Riot/Assets/hu.lproj/Vector.strings # Riot/Assets/id.lproj/InfoPlist.strings # Riot/Assets/id.lproj/Vector.strings # Riot/Assets/is.lproj/InfoPlist.strings # Riot/Assets/is.lproj/Vector.strings # Riot/Assets/it.lproj/InfoPlist.strings # Riot/Assets/it.lproj/Vector.strings # Riot/Assets/ja.lproj/InfoPlist.strings # Riot/Assets/ja.lproj/Localizable.strings # Riot/Assets/ja.lproj/Vector.strings # Riot/Assets/nl.lproj/InfoPlist.strings # Riot/Assets/nl.lproj/Vector.strings # Riot/Assets/pl.lproj/InfoPlist.strings # Riot/Assets/pl.lproj/Vector.strings # Riot/Assets/pt_BR.lproj/InfoPlist.strings # Riot/Assets/pt_BR.lproj/Vector.strings # Riot/Assets/ru.lproj/InfoPlist.strings # Riot/Assets/ru.lproj/Vector.strings # Riot/Assets/sk.lproj/InfoPlist.strings # Riot/Assets/sk.lproj/Vector.strings # Riot/Assets/sq.lproj/InfoPlist.strings # Riot/Assets/sq.lproj/Vector.strings # Riot/Assets/sv.lproj/InfoPlist.strings # Riot/Assets/sv.lproj/Vector.strings # Riot/Assets/third_party_licenses.html # Riot/Assets/uk.lproj/InfoPlist.strings # Riot/Assets/uk.lproj/Vector.strings # Riot/Assets/zh_Hans.lproj/InfoPlist.strings # Riot/Assets/zh_Hans.lproj/Localizable.strings # Riot/Assets/zh_Hans.lproj/Vector.strings # Riot/Assets/zh_Hant.lproj/Vector.strings # Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h # Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m # Riot/Categories/MXRestClient+Async.swift # Riot/Categories/MXSession+Riot.m # Riot/Categories/NSAttributedString.swift # Riot/Categories/Publisher+Riot.swift # Riot/Categories/RoomBubbleCellData.swift # Riot/Categories/UILabel.swift # Riot/Categories/UIScrollView.swift # Riot/Categories/UIView.swift # Riot/Categories/UIViewController.swift # Riot/Generated/Images.swift # Riot/Generated/Strings.swift # Riot/Generated/UntranslatedStrings.swift # Riot/Managers/EncryptionKeyManager/EncryptionKeyManager.swift # Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift # Riot/Managers/PushNotification/PushNotificationService.m # Riot/Managers/PushNotification/PushNotificationStore.swift # Riot/Managers/Settings/RiotSettings.swift # Riot/Managers/Settings/Shared/RiotSharedSettings.swift # Riot/Managers/Theme/Themes/DarkTheme.swift # Riot/Managers/Theme/Themes/DefaultTheme.swift # Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift # Riot/Model/HomeserverConfiguration/HomeserverConfigurationBuilder.swift # Riot/Modules/Analytics/Analytics.swift # Riot/Modules/Analytics/AnalyticsUIElement.swift # Riot/Modules/Analytics/DecryptionFailureTracker.m # Riot/Modules/Application/AppCoordinator.swift # Riot/Modules/Application/LegacyAppDelegate.h # Riot/Modules/Application/LegacyAppDelegate.m # Riot/Modules/Authentication/AuthenticationCoordinatorProtocol.swift # Riot/Modules/Common/Avatar/AvatarView.swift # Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h # Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m # Riot/Modules/Common/Recents/RecentsViewController.h # Riot/Modules/Common/Recents/RecentsViewController.m # Riot/Modules/Common/Recents/Views/RecentTableViewCell.m # Riot/Modules/Common/SwiftUI/VectorHostingController.swift # Riot/Modules/Common/Toasts/RoundedToastView.swift # Riot/Modules/Common/Toasts/ToastViewState.swift # Riot/Modules/Common/UserIndicators/UserIndicatorPresenter.swift # Riot/Modules/Common/UserIndicators/UserIndicatorStore.swift # Riot/Modules/Common/UserIndicators/ViewPresenters/ToastViewPresenter.swift # Riot/Modules/Common/WebViewController/WebViewViewController.m # Riot/Modules/Communities/Home/GroupHomeViewController.m # Riot/Modules/Communities/Members/GroupParticipantsViewController.m # Riot/Modules/Communities/Rooms/GroupRoomsViewController.m # Riot/Modules/Contacts/ContactsTableViewController.m # Riot/Modules/Contacts/ContactsTableViewController.xib # Riot/Modules/Contacts/Details/ContactDetailsViewController.m # Riot/Modules/Contacts/Views/ContactTableViewCell.m # Riot/Modules/Contacts/Views/ContactTableViewCell.xib # Riot/Modules/ContextMenu/ActionProviders/RoomActionProvider.swift # Riot/Modules/ContextMenu/Services/RoomContextActionService.swift # Riot/Modules/CreateRoom/CreateRoomCoordinator.swift # Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsCoordinator.swift # Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewController.swift # Riot/Modules/CreateRoom/EnterNewRoomDetails/EnterNewRoomDetailsViewModel.swift # Riot/Modules/DeepLink/UniversalLinkParameters.swift # Riot/Modules/Favorites/FavouritesViewController.h # Riot/Modules/Favorites/FavouritesViewController.m # Riot/Modules/GlobalSearch/UnifiedSearchViewController.m # Riot/Modules/Home/HomeViewController.m # Riot/Modules/Home/Views/RoomCollectionViewCell.m # Riot/Modules/Integrations/WidgetPicker/WidgetPickerViewController.m # Riot/Modules/Integrations/Widgets/StickerPicker/StickerPickerViewController.m # Riot/Modules/KeyBackup/ManualExport/EncryptionKeysExportPresenter.swift # Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift # Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift # Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitCoordinator.swift # Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.storyboard # Riot/Modules/KeyVerification/Device/SelfVerifyWait/KeyVerificationSelfVerifyWaitViewController.swift # Riot/Modules/KeyVerification/User/SessionStatus/UserVerificationSessionStatusViewController.swift # Riot/Modules/LocationSharing/LocationManager.swift # Riot/Modules/LocationSharing/UserLocationService.swift # Riot/Modules/LocationSharing/UserLocationServiceProvider.swift # Riot/Modules/MatrixKit/Categories/NSBundle+MatrixKit.m # Riot/Modules/MatrixKit/Controllers/MXKAccountDetailsViewController.m # Riot/Modules/MatrixKit/Controllers/MXKAuthenticationViewController.m # Riot/Modules/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h # Riot/Modules/MatrixKit/Controllers/MXKViewController.h # Riot/Modules/MatrixKit/Models/Account/MXKAccount.h # Riot/Modules/MatrixKit/Models/Account/MXKAccount.m # Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.h # Riot/Modules/MatrixKit/Models/Account/MXKAccountManager.m # Riot/Modules/MatrixKit/Models/Contact/MXKContact.h # Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.h # Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m # Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h # Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataWithAppendingMode.m # Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleComponent.h # Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleComponent.m # Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m # Riot/Modules/MatrixKit/Models/Room/MXKSendReplyEventStringLocalizer.swift # Riot/Modules/MatrixKit/Utils/ErrorPresentation/MXKErrorPresentableBuilder.m # Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.h # Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m # Riot/Modules/MatrixKit/Utils/MXKTools.h # Riot/Modules/MatrixKit/Utils/MXKTools.m # Riot/Modules/MatrixKit/Views/Account/MXKAccountTableViewCell.m # Riot/Modules/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelAndTextField.xib # Riot/Modules/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithTextFieldAndButton.m # Riot/Modules/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.m # Riot/Modules/MatrixKit/Views/RoomMemberList/MXKRoomMemberTableViewCell.m # Riot/Modules/MediaPicker/MediaPickerViewController.m # Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift # Riot/Modules/MediaPickerV2/MediaPickerPresenter.swift # Riot/Modules/Onboarding/OnboardingCoordinator.swift # Riot/Modules/Onboarding/OnboardingCoordinatorBridgePresenter.swift # Riot/Modules/Onboarding/OnboardingCoordinatorProtocol.swift # Riot/Modules/People/PeopleViewController.h # Riot/Modules/People/PeopleViewController.m # Riot/Modules/Pills/PillTextAttachment.swift # Riot/Modules/Pills/PillsFormatter.swift # Riot/Modules/QRCode/QRCodeGenerator.swift # Riot/Modules/Room/CellData/RoomBubbleCellData.m # Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift # Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalCoordinator.swift # Riot/Modules/Room/CreationModal/RoomCreationEventsModal/RoomCreationEventsModalViewModel.swift # Riot/Modules/Room/CreationModal/RoomCreationModalCoordinatorBridgePresenter.swift # Riot/Modules/Room/DataSources/RoomDataSource.m # Riot/Modules/Room/DataSources/RoomDataSource.swift # Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift # Riot/Modules/Room/Files/RoomFilesViewController.m # Riot/Modules/Room/Location/RoomTimelineLocationView.swift # Riot/Modules/Room/Location/RoomTimelineLocationView.xib # Riot/Modules/Room/MXKRoomViewController.h # Riot/Modules/Room/MXKRoomViewController.m # Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h # Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m # Riot/Modules/Room/Members/RoomParticipantsViewController.h # Riot/Modules/Room/Members/RoomParticipantsViewController.m # Riot/Modules/Room/ParticipantsInviteModal/ContactsPicker/ContactsPickerViewModel.swift # Riot/Modules/Room/RoomCoordinator.swift # Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewAction.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewController.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModel.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewModelType.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/RoomInfoListViewState.swift # Riot/Modules/Room/RoomInfo/RoomInfoList/Views/RoomInfoBasicView.swift # Riot/Modules/Room/RoomViewController.m # Riot/Modules/Room/Settings/RoomSettingsViewController.m # Riot/Modules/Room/TimelineCells/BaseRoomCell/RoomCellContentView.xib # Riot/Modules/Room/TimelineCells/Common/MXKRoomBubbleTableViewCell.m # Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroCell.swift # Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroCellContentView.swift # Riot/Modules/Room/TimelineCells/RoomCreationIntro/RoomCreationIntroViewData.swift # Riot/Modules/Room/TimelineCells/RoomMembership/RoomMembershipCollapsedBubbleCell.m # Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h # Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m # Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineStyle.swift # Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailBaseBubbleCell.swift # Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.swift # Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/FileAttachment/Incoming/Clear/RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/FileAttachment/Incoming/Encrypted/RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/FileAttachment/Outgoing/Clear/RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/FileAttachment/Outgoing/Encrypted/RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Location/LocationPlainCell.swift # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Poll/PollPlainCell.swift # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/Sticker/RoomSelectedStickerBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/TextMessage/Incoming/Clear/RoomIncomingTextMsgBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/TextMessage/Incoming/Clear/RoomIncomingTextMsgWithPaginationTitleBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/TextMessage/Incoming/Encrypted/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/TextMessage/Outgoing/Clear/RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/TextMessage/Outgoing/Encrypted/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.xib # Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m # Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineStyle.swift # Riot/Modules/Room/TimelineCells/Styles/RoomTimelineStyle.swift # Riot/Modules/Room/TimelineDecorations/Reactions/RoomReactionActionViewCell.swift # Riot/Modules/Room/TimelineDecorations/Reactions/RoomReactionActionViewCell.xib # Riot/Modules/Room/TimelineDecorations/Reactions/RoomReactionViewCell.xib # Riot/Modules/Room/TimelineDecorations/Reactions/RoomReactionsView.swift # Riot/Modules/Room/TimelineDecorations/Reactions/RoomReactionsViewModel.swift # Riot/Modules/Room/TimelineDecorations/Reactions/RoomReactionsViewModelType.swift # Riot/Modules/Room/TimelineDecorations/Threads/Summary/ThreadSummaryView.swift # Riot/Modules/Room/VoiceMessages/VoiceMessageAudioConverter.swift # Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift # Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift # Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib # Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift # Riot/Modules/Rooms/RoomsViewController.h # Riot/Modules/Rooms/ShowDirectory/Cells/Network/DirectoryNetworkTableHeaderFooterView.swift # Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.swift # Riot/Modules/Rooms/ShowDirectory/Cells/Room/DirectoryRoomTableViewCell.xib # Riot/Modules/Rooms/ShowDirectory/PublicRoomsDirectoryViewModel.swift # Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewController.swift # Riot/Modules/Rooms/ShowDirectory/ShowDirectoryViewModel.swift # Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyCoordinator.swift # Riot/Modules/Secrets/Recover/RecoverWithKey/SecretsRecoveryWithKeyViewController.swift # Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseCoordinator.swift # Riot/Modules/Secrets/Recover/RecoverWithPassphrase/SecretsRecoveryWithPassphraseViewController.swift # Riot/Modules/Secrets/Recover/SecretsRecoveryCoordinator.swift # Riot/Modules/Secrets/Reset/SecretsResetViewController.storyboard # Riot/Modules/Secrets/Reset/SecretsResetViewController.swift # Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewController.swift # Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewController.storyboard # Riot/Modules/Secrets/Setup/RecoveryPassphrase/SecretsSetupRecoveryPassphraseViewController.swift # Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift # Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModel.swift # Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewModelType.swift # Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift # Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.storyboard # Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewController.swift # Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModel.swift # Riot/Modules/SetPinCode/PinCodePreferences.swift # Riot/Modules/SetPinCode/SetPinCoordinator.swift # Riot/Modules/SetPinCode/SetPinCoordinatorBridgePresenter.swift # Riot/Modules/SetPinCode/SetupBiometrics/BiometricsAuthenticationPresenter.swift # Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsCoordinator.swift # Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewController.swift # Riot/Modules/SetPinCode/SetupBiometrics/SetupBiometricsViewModel.swift # Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m # Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m # Riot/Modules/Settings/Security/SecurityViewController.m # Riot/Modules/Settings/SettingsViewController.m # Riot/Modules/SideMenu/SideMenuCoordinator.swift # Riot/Modules/SideMenu/SideMenuViewModel.swift # Riot/Modules/Spaces/SpaceList/SpaceListViewModel.swift # Riot/Modules/Spaces/SpaceMenu/SpaceMenuPresenter.swift # Riot/Modules/Spaces/SpaceMenu/SpaceMenuViewModel.swift # Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift # Riot/Modules/SplitView/SplitViewCoordinator.swift # Riot/Modules/SplitView/SplitViewCoordinatorType.swift # Riot/Modules/StartChat/StartChatViewController.m # Riot/Modules/TabBar/MasterTabBarController.h # Riot/Modules/TabBar/MasterTabBarController.m # Riot/Modules/TabBar/TabBarCoordinator.swift # Riot/Modules/TabBar/TabBarCoordinatorType.swift # Riot/Modules/Threads/ThreadList/ThreadListViewModel.swift # Riot/SupportingFiles/Info.plist # Riot/SupportingFiles/Riot-Bridging-Header.h # Riot/SupportingFiles/Riot.entitlements # Riot/Utils/EventFormatter+DTCoreTextFix.m # Riot/Utils/EventFormatter.m # Riot/Utils/Tools.h # Riot/Utils/Tools.m # Riot/Utils/URLValidator.swift # Riot/Utils/UniversalLink.h # Riot/Utils/UniversalLink.m # Riot/target.yml # RiotNSE/NotificationService.swift # RiotNSE/RiotNSE.entitlements # RiotNSE/target.yml # RiotShareExtension/Shared/View/ShareViewController.m # RiotShareExtension/SupportingFiles/Info.plist # RiotShareExtension/target.yml # RiotSwiftUI/Modules/AnalyticsPrompt/AnalyticsPromptViewModel.swift # RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptCoordinator.swift # RiotSwiftUI/Modules/AnalyticsPrompt/Coordinator/AnalyticsPromptStrings.swift # RiotSwiftUI/Modules/AnalyticsPrompt/MockAnalyticsPromptScreenState.swift # RiotSwiftUI/Modules/AnalyticsPrompt/Test/UI/AnalyticsPromptUITests.swift # RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPrompt.swift # RiotSwiftUI/Modules/AnalyticsPrompt/View/AnalyticsPromptCheckmarkItem.swift # RiotSwiftUI/Modules/Authentication/Common/AuthenticationHomeserverViewData.swift # RiotSwiftUI/Modules/Authentication/Common/AuthenticationModels.swift # RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationRestClient.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginModels.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginParameters.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/LoginWizard.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationParameters.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/RegistrationWizard.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/SessionCreator.swift # RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/ThreePIDModels.swift # RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift # RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift # RiotSwiftUI/Modules/Authentication/Login/Test/UI/AuthenticationLoginUITests.swift # RiotSwiftUI/Modules/Authentication/Login/Test/Unit/AuthenticationLoginViewModelTests.swift # RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift # RiotSwiftUI/Modules/Authentication/ReCaptcha/Test/UI/AuthenticationReCaptchaUITests.swift # RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaScreen.swift # RiotSwiftUI/Modules/Authentication/ReCaptcha/View/AuthenticationReCaptchaWebView.swift # RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift # RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift # RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift # RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift # RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift # RiotSwiftUI/Modules/Authentication/Registration/Test/UI/AuthenticationRegistrationUITests.swift # RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift # RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift # RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift # RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift # RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift # RiotSwiftUI/Modules/Authentication/ServerSelection/MockAuthenticationServerSelectionScreenState.swift # RiotSwiftUI/Modules/Authentication/ServerSelection/Test/UI/AuthenticationServerSelectionUITests.swift # RiotSwiftUI/Modules/Authentication/ServerSelection/Test/Unit/AuthenticationServerSelectionViewModelTests.swift # RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift # RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsModels.swift # RiotSwiftUI/Modules/Authentication/Terms/AuthenticationTermsViewModel.swift # RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift # RiotSwiftUI/Modules/Authentication/Terms/MockAuthenticationTermsScreenState.swift # RiotSwiftUI/Modules/Authentication/Terms/Test/UI/AuthenticationTermsUITests.swift # RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsScreen.swift # RiotSwiftUI/Modules/Authentication/Terms/View/AuthenticationTermsToggleStyle.swift # RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailModels.swift # RiotSwiftUI/Modules/Authentication/VerifyEmail/AuthenticationVerifyEmailViewModel.swift # RiotSwiftUI/Modules/Authentication/VerifyEmail/Coordinator/AuthenticationVerifyEmailCoordinator.swift # RiotSwiftUI/Modules/Authentication/VerifyEmail/MockAuthenticationVerifyEmailScreenState.swift # RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/UI/AuthenticationVerifyEmailUITests.swift # RiotSwiftUI/Modules/Authentication/VerifyEmail/Test/Unit/AuthenticationVerifyEmailViewModelTests.swift # RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailForm.swift # RiotSwiftUI/Modules/Authentication/VerifyEmail/View/AuthenticationVerifyEmailScreen.swift # RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnModels.swift # RiotSwiftUI/Modules/Authentication/VerifyMsisdn/AuthenticationVerifyMsisdnViewModel.swift # RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Coordinator/AuthenticationVerifyMsisdnCoordinator.swift # RiotSwiftUI/Modules/Authentication/VerifyMsisdn/MockAuthenticationVerifyMsisdnScreenState.swift # RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/UI/AuthenticationVerifyMsisdnUITests.swift # RiotSwiftUI/Modules/Authentication/VerifyMsisdn/Test/Unit/AuthenticationVerifyMsisdnViewModelTests.swift # RiotSwiftUI/Modules/Authentication/VerifyMsisdn/View/AuthenticationVerifyMsisdnForm.swift # RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicator.swift # RiotSwiftUI/Modules/Common/ActivityIndicator/ActivityIndicatorModifier.swift # RiotSwiftUI/Modules/Common/Avatar/Service/MatrixSDK/AvatarService.swift # RiotSwiftUI/Modules/Common/Avatar/Service/Mock/MockAvatarService.swift # RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift # RiotSwiftUI/Modules/Common/Avatar/View/PlaceholderAvatarImage.swift # RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift # RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarServiceProtocol.swift # RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift # RiotSwiftUI/Modules/Common/Bridging/VectorContentView.swift # RiotSwiftUI/Modules/Common/DependencyInjection/DependencyContainerKey.swift # RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift # RiotSwiftUI/Modules/Common/EffectsScene/EffectsView.swift # RiotSwiftUI/Modules/Common/Extensions/Publisher.swift # RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift # RiotSwiftUI/Modules/Common/Mock/MockScreenState.swift # RiotSwiftUI/Modules/Common/Mock/ScreenList.swift # RiotSwiftUI/Modules/Common/Mock/ScreenStateInfo.swift # RiotSwiftUI/Modules/Common/Mock/StateRenderer.swift # RiotSwiftUI/Modules/Common/Test/UI/MockScreenTest.swift # RiotSwiftUI/Modules/Common/Test/UI/XCUIApplication+Riot.swift # RiotSwiftUI/Modules/Common/Test/XCTestPublisherExtensions.swift # RiotSwiftUI/Modules/Common/Theme/ThemeIdentifierExtensions.swift # RiotSwiftUI/Modules/Common/Theme/ThemeKey.swift # RiotSwiftUI/Modules/Common/Theme/ThemePublisher.swift # RiotSwiftUI/Modules/Common/Theme/ThemeSwiftUI.swift # RiotSwiftUI/Modules/Common/Theme/ThemeUsersColorsExtension.swift # RiotSwiftUI/Modules/Common/Theme/Themes/DarkThemeSwiftUI.swift # RiotSwiftUI/Modules/Common/Theme/Themes/DefaultThemeSwiftUI.swift # RiotSwiftUI/Modules/Common/Util/BorderModifier.swift # RiotSwiftUI/Modules/Common/Util/BorderedInputFieldStyle.swift # RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift # RiotSwiftUI/Modules/Common/Util/InlineTextButton.swift # RiotSwiftUI/Modules/Common/Util/MultilineTextField.swift # RiotSwiftUI/Modules/Common/Util/OptionButton.swift # RiotSwiftUI/Modules/Common/Util/PrimaryActionButtonStyle.swift # RiotSwiftUI/Modules/Common/Util/RadioButton.swift # RiotSwiftUI/Modules/Common/Util/RoundedBorderTextEditor.swift # RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift # RiotSwiftUI/Modules/Common/Util/RoundedCornerShape.swift # RiotSwiftUI/Modules/Common/Util/SafeBindingCollectionEnumerator.swift # RiotSwiftUI/Modules/Common/Util/ScreenTrackerViewModifier.swift # RiotSwiftUI/Modules/Common/Util/SearchBar.swift # RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift # RiotSwiftUI/Modules/Common/Util/StyledText.swift # RiotSwiftUI/Modules/Common/Util/ThemableButton.swift # RiotSwiftUI/Modules/Common/Util/ThemableNavigationBar.swift # RiotSwiftUI/Modules/Common/Util/ThemableTextEditor.swift # RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift # RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift # RiotSwiftUI/Modules/Common/ViewFrameReader/FramePreferenceKey.swift # RiotSwiftUI/Modules/Common/ViewFrameReader/ViewFrameReader.swift # RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift # RiotSwiftUI/Modules/Onboarding/Avatar/Coordinator/OnboardingAvatarCoordinator.swift # RiotSwiftUI/Modules/Onboarding/Avatar/MockOnboardingAvatarScreenState.swift # RiotSwiftUI/Modules/Onboarding/Avatar/Test/UI/OnboardingAvatarUITests.swift # RiotSwiftUI/Modules/Onboarding/Avatar/Test/Unit/OnboardingAvatarViewModelTests.swift # RiotSwiftUI/Modules/Onboarding/Celebration/Test/UI/OnboardingCelebrationUITests.swift # RiotSwiftUI/Modules/Onboarding/Celebration/View/OnboardingCelebrationScreen.swift # RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift # RiotSwiftUI/Modules/Onboarding/Common/OnboardingMetrics.swift # RiotSwiftUI/Modules/Onboarding/Congratulations/OnboardingCongratulationsModels.swift # RiotSwiftUI/Modules/Onboarding/Congratulations/Test/UI/OnboardingCongratulationsUITests.swift # RiotSwiftUI/Modules/Onboarding/Congratulations/View/OnboardingCongratulationsScreen.swift # RiotSwiftUI/Modules/Onboarding/DisplayName/Coordinator/OnboardingDisplayNameCoordinator.swift # RiotSwiftUI/Modules/Onboarding/DisplayName/Test/UI/OnboardingDisplayNameUITests.swift # RiotSwiftUI/Modules/Onboarding/DisplayName/View/OnboardingDisplayNameScreen.swift # RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift # RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift # RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPage.swift # RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreenPageIndicator.swift # RiotSwiftUI/Modules/Onboarding/UseCase/Coordinator/OnboardingUseCaseSelectionCoordinator.swift # RiotSwiftUI/Modules/Onboarding/UseCase/OnboardingUseCaseModels.swift # RiotSwiftUI/Modules/Onboarding/UseCase/Test/UI/OnboardingUseCaseUITests.swift # RiotSwiftUI/Modules/Onboarding/UseCase/View/OnboardingUseCaseSelectionScreen.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/Coordinator/LiveLocationSharingViewerCoordinator.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/LiveLocationSharingViewerViewModelProtocol.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/MockLiveLocationSharingViewerScreenState.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/Test/UI/LiveLocationSharingViewerUITests.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/View/LiveLocationListItem.swift # RiotSwiftUI/Modules/Room/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift # RiotSwiftUI/Modules/Room/LocationSharing/Coordinator/LocationSharingCoordinator.swift # RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift # RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingScreenState.swift # RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift # RiotSwiftUI/Modules/Room/LocationSharing/Service/MatrixSDK/LocationSharingService.swift # RiotSwiftUI/Modules/Room/LocationSharing/Service/Mock/MockLocationSharingService.swift # RiotSwiftUI/Modules/Room/LocationSharing/Test/UI/LocationSharingUITests.swift # RiotSwiftUI/Modules/Room/LocationSharing/Test/Unit/LocationSharingViewModelTests.swift # RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift # RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMarkerView.swift # RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingOptionButton.swift # RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift # RiotSwiftUI/Modules/Room/LocationSharing/View/MapCreditsView.swift # RiotSwiftUI/Modules/Room/LocationSharing/View/UserLocationAnnotationView.swift # RiotSwiftUI/Modules/Room/NotificationSettings/Coordinator/RoomNotificationSettingsCoordinator.swift # RiotSwiftUI/Modules/Room/NotificationSettings/View/FormItemButtonStyle.swift # RiotSwiftUI/Modules/Room/NotificationSettings/View/FormPickerItem.swift # RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionFooter.swift # RiotSwiftUI/Modules/Room/NotificationSettings/View/FormSectionHeader.swift # RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettings.swift # RiotSwiftUI/Modules/Room/NotificationSettings/View/RoomNotificationSettingsHeader.swift # RiotSwiftUI/Modules/Room/NotificationSettings/View/VectorForm.swift # RiotSwiftUI/Modules/Room/NotificationSettings/ViewModel/RoomNotificationSettingsSwiftUIViewModel.swift # RiotSwiftUI/Modules/Room/PollEditForm/Coordinator/PollEditFormCoordinator.swift # RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormScreenState.swift # RiotSwiftUI/Modules/Room/PollEditForm/PollEditFormViewModel.swift # RiotSwiftUI/Modules/Room/PollEditForm/Test/UI/PollEditFormUITests.swift # RiotSwiftUI/Modules/Room/PollEditForm/Test/Unit/PollEditFormViewModelTests.swift # RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditForm.swift # RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormAnswerOptionView.swift # RiotSwiftUI/Modules/Room/PollEditForm/View/PollEditFormTypePicker.swift # RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift # RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/MockRoomAccessTypeChooserScreenState.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/UI/RoomAccessTypeChooserUITests.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Test/Unit/RoomAccessTypeChooserViewModelTests.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Coordinator/RoomRestrictedAccessSpaceChooserViewProvider.swift # RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift # RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift # RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift # RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Coordinator/RoomSuggestionSpaceChooserViewProvider.swift # RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/View/RoomSuggestionSpaceChooserSelector.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/Coordinator/RoomUpgradeCoordinator.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/MockRoomUpgradeScreenState.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModelProtocol.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/Service/MatrixSDK/RoomUpgradeService.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/Service/Mock/MockRoomUpgradeService.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/Service/RoomUpgradeServiceProtocol.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/Test/Unit/RoomUpgradeViewModelTests.swift # RiotSwiftUI/Modules/Room/RoomUpgrade/View/RoomUpgrade.swift # RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift # RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift # RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/StaticLocationViewingModels.swift # RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift # RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/StaticLocationViewingViewModelProtocol.swift # RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/UI/StaticLocationViewingUITests.swift # RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift # RiotSwiftUI/Modules/Room/StaticLocationSharingViewer/View/StaticLocationView.swift # RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollCoordinator.swift # RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift # RiotSwiftUI/Modules/Room/TimelinePoll/Test/UI/TimelinePollUITests.swift # RiotSwiftUI/Modules/Room/TimelinePoll/Test/Unit/TimelinePollViewModelTests.swift # RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollScreenState.swift # RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModel.swift # RiotSwiftUI/Modules/Room/TimelinePoll/TimelinePollViewModelProtocol.swift # RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollAnswerOptionButton.swift # RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollView.swift # RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift # RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift # RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift # RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift # RiotSwiftUI/Modules/Room/UserSuggestion/Test/UI/UserSuggestionUITests.swift # RiotSwiftUI/Modules/Room/UserSuggestion/Test/Unit/UserSuggestionServiceTests.swift # RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionScreenState.swift # RiotSwiftUI/Modules/Room/UserSuggestion/UserSuggestionViewModel.swift # RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionList.swift # RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListItem.swift # RiotSwiftUI/Modules/Room/UserSuggestion/View/UserSuggestionListWithInput.swift # RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsBridgePresenter.swift # RiotSwiftUI/Modules/Settings/Notifications/Coordinator/NotificationSettingsCoordinator.swift # RiotSwiftUI/Modules/Settings/Notifications/Model/NotificationSettingsScreen.swift # RiotSwiftUI/Modules/Settings/Notifications/Service/MatrixSDK/MXNotificationSettingsService.swift # RiotSwiftUI/Modules/Settings/Notifications/Service/Mock/MockNotificationSettingsService.swift # RiotSwiftUI/Modules/Settings/Notifications/Service/NotificationSettingsServiceType.swift # RiotSwiftUI/Modules/Settings/Notifications/View/Chip.swift # RiotSwiftUI/Modules/Settings/Notifications/View/Chips.swift # RiotSwiftUI/Modules/Settings/Notifications/View/ChipsInput.swift # RiotSwiftUI/Modules/Settings/Notifications/View/DefaultNotificationSettings.swift # RiotSwiftUI/Modules/Settings/Notifications/View/FormInputFieldStyle.swift # RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift # RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettings.swift # RiotSwiftUI/Modules/Settings/Notifications/View/NotificationSettingsKeywords.swift # RiotSwiftUI/Modules/Settings/Notifications/View/OtherNotificationSettings.swift # RiotSwiftUI/Modules/Settings/Notifications/ViewModel/NotificationSettingsViewModel.swift # RiotSwiftUI/Modules/Spaces/AddRoomSelector/Coordinator/AddRoomSelectorViewProvider.swift # RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift # RiotSwiftUI/Modules/Spaces/LeaveSpace/Coordinator/LeaveSpaceViewProvider.swift # RiotSwiftUI/Modules/Spaces/LeaveSpace/View/LeaveSpace.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Coordinator/MatrixItemChooserCoordinator.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModel.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MatrixItemChooserViewModelProtocol.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/MockMatrixItemChooserScreenState.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixItemChooserServiceProtocol.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserService.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/Mock/MockMatrixItemChooserService.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/UI/MatrixItemChooserUITests.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Test/Unit/MatrixItemChooserViewModelTests.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserListRow.swift # RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooserSectionHeader.swift # RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Coordinator/RoomAncestorSelectorViewProvider.swift # RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/View/RoomAncestorSelector.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/Coordinator/SpaceCreationCoordinator.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Coordinator/SpaceCreationEmailInvitesCoordinator.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/MatrixSDK/SpaceCreationEmailInvitesService.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesScreenState.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/Mock/MockSpaceCreationEmailInvitesService.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Service/SpaceCreationEmailInvitesServiceProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/UI/SpaceCreationEmailInvitesUITests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/Test/Unit/SpaceCreationEmailInvitesViewModelTests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModel.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/ViewModel/SpaceCreationEmailInvitesViewModelProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/Coordinator/SpaceCreationMatrixItemChooserViewProvider.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMatrixItemChooser/View/SpaceCreationMatrixItemChooser.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Coordinator/SpaceCreationMenuCoordinator.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/UI/SpaceCreationMenuUITests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/Test/Unit/SpaceCreationMenuViewModelTests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/View/SpaceCreationMenu.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModel.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationMenu/ViewModel/SpaceCreationMenuViewModelProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Coordinator/SpaceCreationPostProcessCoordinator.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/MatrixSDK/SpaceCreationPostProcessService.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessScreenState.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/Mock/MockSpaceCreationPostProcessService.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Service/SpaceCreationPostProcessServiceProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/UI/SpaceCreationPostProcessUITests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/Test/Unit/SpaceCreationPostProcessViewModelTests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcess.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/View/SpaceCreationPostProcessItem.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModel.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationPostProcess/ViewModel/SpaceCreationPostProcessViewModelProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Coordinator/SpaceCreationRoomsCoordinator.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Service/Mock/MockSpaceCreationRoomsScreenState.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/UI/SpaceCreationRoomsUITests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/Test/Unit/SpaceCreationRoomsViewModelTests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/View/SpaceCreationRooms.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModel.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationRooms/ViewModel/SpaceCreationRoomsViewModelProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Coordinator/SpaceCreationSettingsCoordinator.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/MatrixSDK/SpaceCreationSettingsService.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsScreenState.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/Mock/MockSpaceCreationSettingsService.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Service/SpaceCreationSettingsServiceProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/UI/SpaceCreationSettingsUITests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/Test/Unit/SpaceCreationSettingsViewModelTests.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/View/SpaceCreationSettings.swift # RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationSettings/ViewModel/SpaceCreationSettingsViewModel.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinator.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/Coordinator/SpaceSettingsModalCoordinatorBridgePresenter.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Coordinator/SpaceSettingsCoordinator.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/MockSpaceSettingsScreenState.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/MatrixSDK/SpaceSettingsService.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/Mock/MockSpaceSettingsService.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Service/SpaceSettingsServiceProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModel.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/SpaceSettingsViewModelProtocol.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/UI/SpaceSettingsUITests.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/Test/Unit/SpaceSettingsViewModelTests.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettings.swift # RiotSwiftUI/Modules/Spaces/SpaceSettings/SpaceSettings/View/SpaceSettingsOptionListItem.swift # RiotSwiftUI/Modules/Template/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift # RiotSwiftUI/Modules/Template/SimpleUserProfileExample/Test/UI/TemplateUserProfileUITests.swift # RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Test/UI/TemplateRoomChatUITests.swift # RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomList/Test/UI/TemplateRoomListUITests.swift # RiotSwiftUI/RiotSwiftUIApp.swift # RiotSwiftUI/target.yml # RiotSwiftUI/targetUITests.yml # RiotTests/MatrixKitTests/MXKEventFormatter+Tests.h # RiotTests/MatrixKitTests/MXKEventFormatterTests.m # RiotTests/MatrixKitTests/MXKRoomDataSourceTests.swift # RiotTests/MatrixKitTests/MatrixKitTests-Bridging-Header.h # RiotTests/Modules/Authentication/AuthenticationServiceTests.swift # RiotTests/OnboardingTests.swift # RiotTests/PillsFormatterTests.swift # RiotTests/target.yml # SiriIntents/IntentHandler.m # SiriIntents/target.yml # Tools/SwiftGen/swiftgen-config.yml # Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.storyboard # fastlane/.env.default # fastlane/Fastfile # project.yml
913 lines
48 KiB
Swift
913 lines
48 KiB
Swift
/*
|
|
Copyright 2020 New Vector Ltd
|
|
Copyright (c) 2021 BWI GmbH
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
import UserNotifications
|
|
import MatrixSDK
|
|
|
|
/// The number of milliseconds in one second.
|
|
private let MSEC_PER_SEC: TimeInterval = 1000
|
|
|
|
class NotificationService: UNNotificationServiceExtension {
|
|
|
|
private struct NSE {
|
|
enum Constants {
|
|
static let voipPushRequestTimeout: TimeInterval = 15
|
|
static let timeNeededToSendVoIPPushes: TimeInterval = 20
|
|
}
|
|
}
|
|
|
|
// MARK: - Properties
|
|
|
|
/// Receiving dates for notifications. Keys are eventId's
|
|
private var receiveDates: [String: Date] = [:]
|
|
|
|
/// Content handlers. Keys are eventId's
|
|
private var contentHandlers: [String: ((UNNotificationContent) -> Void)] = [:]
|
|
|
|
/// Flags to indicate there is an ongoing VoIP Push request for events. Keys are eventId's
|
|
private var ongoingVoIPPushRequests: [String: Bool] = [:]
|
|
|
|
private var userAccount: MXKAccount?
|
|
|
|
/// Best attempt contents. Will be updated incrementally, if something fails during the process, this best attempt content will be showed as notification. Keys are eventId's
|
|
private var bestAttemptContents: [String: UNMutableNotificationContent] = [:]
|
|
|
|
private static var backgroundSyncService: MXBackgroundSyncService!
|
|
private var showDecryptedContentInNotifications: Bool {
|
|
return RiotSettings.shared.showDecryptedContentInNotifications
|
|
}
|
|
private lazy var configuration: Configurable = {
|
|
return CommonConfiguration()
|
|
}()
|
|
private lazy var mxRestClient: MXRestClient? = {
|
|
guard let userAccount = userAccount else {
|
|
return nil
|
|
}
|
|
let restClient = MXRestClient(credentials: userAccount.mxCredentials, unrecognizedCertificateHandler: nil, persistentTokenDataHandler: { persistTokenDataHandler in
|
|
MXKAccountManager.shared().readAndWriteCredentials(persistTokenDataHandler)
|
|
}, unauthenticatedHandler: { error, softLogout, refreshTokenAuth, completion in
|
|
userAccount.handleUnauthenticatedWithError(error, isSoftLogout: softLogout, isRefreshTokenAuth: refreshTokenAuth, andCompletion: completion)
|
|
})
|
|
return restClient
|
|
}()
|
|
|
|
private static var isLoggerInitialized: Bool = false
|
|
private lazy var pushGatewayRestClient: MXPushGatewayRestClient = {
|
|
let url = URL(string: AppConfigService.shared.pusherUrl())!
|
|
return MXPushGatewayRestClient(pushGateway: url.scheme! + "://" + url.host!, andOnUnrecognizedCertificateBlock: nil)
|
|
}()
|
|
private var pushNotificationStore: PushNotificationStore = PushNotificationStore()
|
|
private let localAuthenticationService = LocalAuthenticationService(pinCodePreferences: .shared)
|
|
private static let backgroundServiceInitQueue = DispatchQueue(label: "io.element.NotificationService.backgroundServiceInitQueue")
|
|
// MARK: - Method Overrides
|
|
|
|
override init() {
|
|
super.init()
|
|
|
|
// Set up runtime language and fallback by considering the userDefaults object shared within the application group.
|
|
let sharedUserDefaults = MXKAppSettings.standard().sharedUserDefaults
|
|
if let language = sharedUserDefaults?.string(forKey: "appLanguage") {
|
|
Bundle.mxk_setLanguage(language)
|
|
}
|
|
Bundle.mxk_setFallbackLanguage("en")
|
|
}
|
|
|
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
|
let userInfo = request.content.userInfo
|
|
|
|
// Set static application settings
|
|
configuration.setupSettings()
|
|
|
|
if DataProtectionHelper.isDeviceInRebootedAndLockedState(appGroupIdentifier: MXSDKOptions.sharedInstance().applicationGroupIdentifier) {
|
|
// kill the process in this state, this leads for the notification to be displayed as came from APNS
|
|
exit(0)
|
|
}
|
|
|
|
// setup logs
|
|
setupLogger()
|
|
|
|
MXLog.debug(" ")
|
|
MXLog.debug(" ")
|
|
MXLog.debug("################################################################################")
|
|
MXLog.debug("[NotificationService] Instance: \(self), thread: \(Thread.current)")
|
|
MXLog.debug("[NotificationService] Payload came: \(userInfo)")
|
|
|
|
// log memory at the beginning of the process
|
|
logMemory()
|
|
|
|
setupAnalytics()
|
|
|
|
UNUserNotificationCenter.current().removeUnwantedNotifications()
|
|
// check if this is a Matrix notification
|
|
guard let roomId = userInfo["room_id"] as? String, let eventId = userInfo["event_id"] as? String else {
|
|
// it's not a Matrix notification, do not change the content
|
|
MXLog.debug("[NotificationService] didReceiveRequest: This is not a Matrix notification.")
|
|
contentHandler(request.content)
|
|
return
|
|
}
|
|
|
|
// check if the event arrived outside the user defined working time for this room
|
|
guard !isRoomMuted(roomID: roomId) else {
|
|
// it's not a Matrix notification, do not change the content
|
|
MXLog.debug("[NotificationService] didReceiveRequest: This is not a Matrix notification.")
|
|
contentHandler(UNNotificationContent())
|
|
return
|
|
}
|
|
|
|
// save this content as fallback content
|
|
guard let content = request.content.mutableCopy() as? UNMutableNotificationContent else {
|
|
return
|
|
}
|
|
|
|
// store receive date
|
|
receiveDates[eventId] = Date()
|
|
|
|
// read badge from "unread_count"
|
|
// no need to check before, if it's nil, the badge will remain unchanged
|
|
content.badge = userInfo["unread_count"] as? NSNumber
|
|
|
|
bestAttemptContents[eventId] = content
|
|
contentHandlers[eventId] = contentHandler
|
|
|
|
// setup user account
|
|
setup(withRoomId: roomId, eventId: eventId) {
|
|
// preprocess the payload, will attempt to fetch room display name
|
|
self.preprocessPayload(forEventId: eventId, roomId: roomId)
|
|
// fetch the event first
|
|
self.fetchAndProcessEvent(withEventId: eventId, roomId: roomId)
|
|
}
|
|
}
|
|
|
|
override func serviceExtensionTimeWillExpire() {
|
|
// Called just before the extension will be terminated by the system.
|
|
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
|
|
|
MXLog.debug("[NotificationService] serviceExtensionTimeWillExpire")
|
|
// No-op here. If the process is killed by the OS due to time limit, it will also show the notification with the original content.
|
|
}
|
|
|
|
deinit {
|
|
MXLog.debug("[NotificationService] deinit for \(self)");
|
|
self.logMemory()
|
|
MXLog.debug(" ")
|
|
}
|
|
|
|
|
|
// MARK: - Private
|
|
|
|
private func logMemory() {
|
|
MXLog.debug("[NotificationService] Memory: footprint: \(MXMemory.formattedMemoryFootprint()) - available: \(MXMemory.formattedMemoryAvailable())")
|
|
}
|
|
|
|
private func setupLogger() {
|
|
if !NotificationService.isLoggerInitialized {
|
|
let configuration = MXLogConfiguration()
|
|
configuration.logLevel = .verbose
|
|
configuration.maxLogFilesCount = 100
|
|
configuration.logFilesSizeLimit = 10 * 1024 * 1024; // 10MB
|
|
configuration.subLogName = "nse"
|
|
|
|
if isatty(STDERR_FILENO) == 0 {
|
|
configuration.redirectLogsToFiles = true
|
|
}
|
|
|
|
MXLog.configure(configuration)
|
|
|
|
NotificationService.isLoggerInitialized = true
|
|
}
|
|
}
|
|
|
|
|
|
private func isRoomMuted(roomID: String) -> Bool {
|
|
let sharedUserDefaults = MXKAppSettings.standard().sharedUserDefaults
|
|
|
|
if #available(iOSApplicationExtension 14.0, *) {
|
|
guard let data = sharedUserDefaults?.data(forKey: NotificationTimes.sharedUserDefaultsKey), let notificationTimes = try? JSONDecoder().decode(NotificationTimes.self, from: data) else {
|
|
return false
|
|
}
|
|
|
|
let now = Date()
|
|
|
|
let calendar = Calendar.current
|
|
let weekday: NotificationTimesWeekday
|
|
switch calendar.component(.weekday, from: now) {
|
|
case 1: // sunday
|
|
weekday = notificationTimes.weekday(index: 6)
|
|
case 2: // monday
|
|
weekday = notificationTimes.weekday(index: 0)
|
|
case 3: // tuesday
|
|
weekday = notificationTimes.weekday(index: 1)
|
|
case 4: // wednesday
|
|
weekday = notificationTimes.weekday(index: 2)
|
|
case 5: // thursday
|
|
weekday = notificationTimes.weekday(index: 3)
|
|
case 6: // friday
|
|
weekday = notificationTimes.weekday(index: 4)
|
|
case 7: // saturday
|
|
weekday = notificationTimes.weekday(index: 5)
|
|
default:
|
|
MXLog.error("[NotificationService] isRoomMuted failed because of an invalid day component.")
|
|
return false
|
|
}
|
|
|
|
return notificationTimes.isEnabled && weekday.isEnabled && weekday.startTime <= now && weekday.endTime >= now
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
private func setupAnalytics(){
|
|
// Configure our analytics. It will start if the option is enabled
|
|
let analytics = Analytics.shared
|
|
MXSDKOptions.sharedInstance().analyticsDelegate = analytics
|
|
analytics.startIfEnabled()
|
|
}
|
|
|
|
private func setup(withRoomId roomId: String, eventId: String, completion: @escaping () -> Void) {
|
|
MXKAccountManager.sharedManager(withReload: true)
|
|
self.userAccount = MXKAccountManager.shared()?.activeAccounts.first
|
|
if let userAccount = userAccount {
|
|
Self.backgroundServiceInitQueue.sync {
|
|
if NotificationService.backgroundSyncService?.credentials != userAccount.mxCredentials {
|
|
MXLog.debug("[NotificationService] setup: MXBackgroundSyncService init: BEFORE")
|
|
self.logMemory()
|
|
NotificationService.backgroundSyncService = MXBackgroundSyncService(withCredentials: userAccount.mxCredentials, persistTokenDataHandler: { persistTokenDataHandler in
|
|
MXKAccountManager.shared().readAndWriteCredentials(persistTokenDataHandler)
|
|
}, unauthenticatedHandler: { error, softLogout, refreshTokenAuth, completion in
|
|
userAccount.handleUnauthenticatedWithError(error, isSoftLogout: softLogout, isRefreshTokenAuth: refreshTokenAuth, andCompletion: completion)
|
|
})
|
|
MXLog.debug("[NotificationService] setup: MXBackgroundSyncService init: AFTER")
|
|
self.logMemory()
|
|
}
|
|
completion()
|
|
}
|
|
} else {
|
|
MXLog.debug("[NotificationService] setup: No active accounts")
|
|
fallbackToBestAttemptContent(forEventId: eventId)
|
|
}
|
|
}
|
|
|
|
/// Attempts to preprocess payload and attach room display name to the best attempt content
|
|
/// - Parameters:
|
|
/// - eventId: Event identifier to mutate best attempt content
|
|
/// - roomId: Room identifier to fetch display name
|
|
private func preprocessPayload(forEventId eventId: String, roomId: String) {
|
|
if localAuthenticationService.isProtectionSet {
|
|
MXLog.debug("[NotificationService] preprocessPayload: Do not preprocess because app protection is set")
|
|
return
|
|
}
|
|
|
|
// If a room summary is available, use the displayname for the best attempt title.
|
|
guard let roomSummary = NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId) else { return }
|
|
guard let roomDisplayName = roomSummary.displayname else { return }
|
|
bestAttemptContents[eventId]?.title = roomDisplayName
|
|
|
|
// At this stage we don't know the message type, so leave the body as set in didReceive.
|
|
}
|
|
|
|
private func fetchAndProcessEvent(withEventId eventId: String, roomId: String) {
|
|
MXLog.debug("[NotificationService] fetchAndProcessEvent")
|
|
|
|
NotificationService.backgroundSyncService.event(withEventId: eventId, inRoom: roomId) { [weak self] (response) in
|
|
switch response {
|
|
case .success(let event):
|
|
MXLog.debug("[NotificationService] fetchAndProcessEvent: Event fetched successfully")
|
|
self?.checkPlaybackAndContinueProcessing(event, roomId: roomId)
|
|
case .failure(let error):
|
|
MXLog.error("[NotificationService] fetchAndProcessEvent: Failed fetching notification event with error: \(error)")
|
|
self?.fallbackToBestAttemptContent(forEventId: eventId)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func checkPlaybackAndContinueProcessing(_ notificationEvent: MXEvent, roomId: String) {
|
|
NotificationService.backgroundSyncService.readMarkerEvent(forRoomId: roomId) { [weak self] response in
|
|
switch response {
|
|
case .success(let readMarkerEvent):
|
|
MXLog.debug("[NotificationService] checkPlaybackAndContinueProcessing: Read marker event fetched successfully")
|
|
|
|
// As origin server timestamps are not always correct data in a federated environment, we add 10 minutes
|
|
// to the calculation to reduce the possibility that an event is marked as read which isn't.
|
|
let notificationTimestamp = notificationEvent.originServerTs + (10 * 60 * 1000)
|
|
|
|
if readMarkerEvent.originServerTs > notificationTimestamp {
|
|
MXLog.error("[NotificationService] checkPlaybackAndContinueProcessing: Event already read, discarding.")
|
|
self?.discardEvent(event: notificationEvent)
|
|
} else {
|
|
self?.processEvent(notificationEvent)
|
|
}
|
|
|
|
case .failure(let error):
|
|
MXLog.error("[NotificationService] checkPlaybackAndContinueProcessing: Failed fetching read marker event with error: \(error)")
|
|
self?.processEvent(notificationEvent)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func processEvent(_ event: MXEvent) {
|
|
if let receiveDate = receiveDates[event.eventId] {
|
|
MXLog.debug("[NotificationService] processEvent: notification receive delay: \(receiveDate.timeIntervalSince1970*MSEC_PER_SEC - TimeInterval(event.originServerTs)) ms")
|
|
}
|
|
|
|
guard let content = bestAttemptContents[event.eventId], let userAccount = userAccount else {
|
|
self.fallbackToBestAttemptContent(forEventId: event.eventId)
|
|
return
|
|
}
|
|
|
|
self.notificationContent(forEvent: event, forAccount: userAccount) { [weak self] (notificationContent, ignoreBadgeUpdate) in
|
|
guard let self = self else { return }
|
|
|
|
guard let newContent = notificationContent else {
|
|
// We still want them removed if the NSE filtering entitlement is not available
|
|
content.categoryIdentifier = Constants.toBeRemovedNotificationCategoryIdentifier
|
|
self.discardEvent(event: event)
|
|
return
|
|
}
|
|
|
|
content.title = newContent.title
|
|
content.subtitle = newContent.subtitle
|
|
content.body = newContent.body
|
|
content.threadIdentifier = newContent.threadIdentifier
|
|
content.categoryIdentifier = newContent.categoryIdentifier
|
|
content.userInfo = newContent.userInfo
|
|
content.sound = newContent.sound
|
|
|
|
if ignoreBadgeUpdate {
|
|
content.badge = nil
|
|
}
|
|
|
|
if self.ongoingVoIPPushRequests[event.eventId] == true {
|
|
// modify the best attempt content, to be able to use in the future
|
|
self.bestAttemptContents[event.eventId] = content
|
|
|
|
// There is an ongoing VoIP Push request for this event, wait for it to be completed.
|
|
// When it completes, it'll continue with the bestAttemptContent.
|
|
return
|
|
} else {
|
|
self.finishProcessing(forEventId: event.eventId, withContent: content)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func discardEvent(event:MXEvent) {
|
|
MXLog.debug("[NotificationService] discardEvent: Discarding event: \(String(describing: event.eventId))")
|
|
finishProcessing(forEventId: event.eventId, withContent: UNNotificationContent())
|
|
}
|
|
|
|
private func fallbackToBestAttemptContent(forEventId eventId: String) {
|
|
MXLog.debug("[NotificationService] fallbackToBestAttemptContent: method called.")
|
|
|
|
guard let content = bestAttemptContents[eventId] else {
|
|
MXLog.debug("[NotificationService] fallbackToBestAttemptContent: Best attempt content is missing.")
|
|
return
|
|
}
|
|
|
|
finishProcessing(forEventId: eventId, withContent: content)
|
|
}
|
|
|
|
private func finishProcessing(forEventId eventId: String, withContent content: UNNotificationContent) {
|
|
MXLog.debug("[NotificationService] finishProcessingEvent: Calling content handler for: \(String(describing: eventId))")
|
|
|
|
contentHandlers[eventId]?(content)
|
|
|
|
// clear maps
|
|
contentHandlers.removeValue(forKey: eventId)
|
|
bestAttemptContents.removeValue(forKey: eventId)
|
|
receiveDates.removeValue(forKey: eventId)
|
|
|
|
// We are done for this push
|
|
MXLog.debug("--------------------------------------------------------------------------------")
|
|
}
|
|
|
|
private func notificationContent(forEvent event: MXEvent, forAccount account: MXKAccount, onComplete: @escaping (UNNotificationContent?, Bool) -> Void) {
|
|
guard let content = event.content, content.count > 0 else {
|
|
MXLog.debug("[NotificationService] notificationContentForEvent: empty event content")
|
|
onComplete(nil, false)
|
|
return
|
|
}
|
|
|
|
let roomId = event.roomId!
|
|
let isRoomMentionsOnly = NotificationService.backgroundSyncService.isRoomMentionsOnly(roomId)
|
|
let roomSummary = NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId)
|
|
|
|
MXLog.debug("[NotificationService] notificationContentForEvent: Attempt to fetch the room state")
|
|
|
|
self.context(ofEvent: event, inRoom: roomId, completion: { (response) in
|
|
switch response {
|
|
case .success(let (roomState, eventSenderName)):
|
|
var notificationTitle: String?
|
|
var notificationBody: String?
|
|
var additionalUserInfo: [AnyHashable: Any]?
|
|
var ignoreBadgeUpdate = false
|
|
var threadIdentifier: String? = roomId
|
|
let currentUserId = account.mxCredentials.userId
|
|
let roomDisplayName = roomSummary?.displayname
|
|
let pushRule = NotificationService.backgroundSyncService.pushRule(matching: event, roomState: roomState)
|
|
|
|
switch event.eventType {
|
|
case .callInvite:
|
|
let offer = event.content["offer"] as? [AnyHashable: Any]
|
|
let sdp = offer?["sdp"] as? String
|
|
let isVideoCall = sdp?.contains("m=video") ?? false
|
|
|
|
if isVideoCall {
|
|
notificationBody = NotificationService.localizedString(forKey: "VIDEO_CALL_FROM_USER", eventSenderName)
|
|
} else {
|
|
notificationBody = NotificationService.localizedString(forKey: "VOICE_CALL_FROM_USER", eventSenderName)
|
|
}
|
|
|
|
// call notifications should stand out from normal messages, so we don't stack them
|
|
threadIdentifier = nil
|
|
|
|
if let callInviteContent = MXCallInviteEventContent(fromJSON: event.content),
|
|
callInviteContent.lifetime > event.age,
|
|
(callInviteContent.lifetime - event.age) > UInt(NSE.Constants.timeNeededToSendVoIPPushes * MSEC_PER_SEC) {
|
|
NotificationService.backgroundSyncService.roomAccountData(forRoomId: roomId) { response in
|
|
if let accountData = response.value, accountData.virtualRoomInfo.isVirtual {
|
|
self.sendReadReceipt(forEvent: event)
|
|
ignoreBadgeUpdate = true
|
|
}
|
|
self.validateNotificationContentAndComplete(
|
|
notificationTitle: notificationTitle,
|
|
notificationBody: notificationBody,
|
|
additionalUserInfo: additionalUserInfo,
|
|
ignoreBadgeUpdate: ignoreBadgeUpdate,
|
|
threadIdentifier: threadIdentifier,
|
|
currentUserId: currentUserId,
|
|
event: event,
|
|
pushRule: pushRule,
|
|
onComplete: onComplete
|
|
)
|
|
}
|
|
self.sendVoipPush(forEvent: event)
|
|
return
|
|
} else {
|
|
MXLog.debug("[NotificationService] notificationContent: Do not attempt to send a VoIP push, there is not enough time to process it.")
|
|
}
|
|
case .roomEncrypted:
|
|
// If unable to decrypt the event, use the fallback.
|
|
break
|
|
case .roomMessage:
|
|
if isRoomMentionsOnly {
|
|
// A local notification will be displayed only for highlighted notification.
|
|
var isHighlighted = false
|
|
|
|
// Check whether is there an highlight tweak on it
|
|
for ruleAction in pushRule?.actions ?? [] {
|
|
guard let action = ruleAction as? MXPushRuleAction else { continue }
|
|
guard action.actionType == MXPushRuleActionTypeSetTweak else { continue }
|
|
guard action.parameters["set_tweak"] as? String == "highlight" else { continue }
|
|
// Check the highlight tweak "value"
|
|
// If not present, highlight. Else check its value before highlighting
|
|
if nil == action.parameters["value"] || true == (action.parameters["value"] as? Bool) {
|
|
isHighlighted = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !isHighlighted {
|
|
// In practice, this only hides the notification's content. An empty notification may be less useful in this instance?
|
|
// Ignore this notif.
|
|
MXLog.debug("[NotificationService] notificationContentForEvent: Ignore non highlighted notif in mentions only room")
|
|
onComplete(nil, false)
|
|
return
|
|
}
|
|
}
|
|
|
|
let msgType = event.content[kMXMessageTypeKey] as? String
|
|
let messageContent = event.content[kMXMessageBodyKey] as? String ?? ""
|
|
let isReply = event.isReply()
|
|
|
|
if isReply {
|
|
notificationTitle = self.replyTitle(for: eventSenderName, in: roomDisplayName)
|
|
} else {
|
|
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
|
}
|
|
|
|
if event.isEncrypted && !self.showDecryptedContentInNotifications {
|
|
// Hide the content
|
|
notificationBody = NotificationService.localizedString(forKey: "MESSAGE")
|
|
break
|
|
}
|
|
|
|
if event.location != nil {
|
|
notificationBody = NotificationService.localizedString(forKey: "LOCATION_FROM_USER", eventSenderName)
|
|
break
|
|
}
|
|
|
|
switch msgType {
|
|
case kMXMessageTypeEmote:
|
|
notificationBody = NotificationService.localizedString(forKey: "ACTION_FROM_USER", eventSenderName, messageContent)
|
|
case kMXMessageTypeImage:
|
|
notificationBody = NotificationService.localizedString(forKey: "PICTURE_FROM_USER", eventSenderName)
|
|
case kMXMessageTypeVideo:
|
|
notificationBody = NotificationService.localizedString(forKey: "VIDEO_FROM_USER", eventSenderName)
|
|
case kMXMessageTypeAudio:
|
|
if event.isVoiceMessage() {
|
|
notificationBody = NotificationService.localizedString(forKey: "VOICE_MESSAGE_FROM_USER", eventSenderName)
|
|
} else {
|
|
notificationBody = NotificationService.localizedString(forKey: "AUDIO_FROM_USER", eventSenderName, messageContent)
|
|
}
|
|
case kMXMessageTypeFile:
|
|
notificationBody = NotificationService.localizedString(forKey: "FILE_FROM_USER", eventSenderName, messageContent)
|
|
|
|
// All other message types such as text, notice, server notice etc
|
|
default:
|
|
if event.isReply() {
|
|
let parser = MXReplyEventParser()
|
|
let replyParts = parser.parse(event)
|
|
notificationBody = replyParts?.bodyParts.replyText
|
|
} else {
|
|
notificationBody = messageContent
|
|
}
|
|
}
|
|
case .roomMember:
|
|
// If the current user is already joined, display updated displayname/avatar events.
|
|
// This is an unexpected path, but has been seen in some circumstances.
|
|
if NotificationService.backgroundSyncService.roomSummary(forRoomId: roomId)?.membership == .join {
|
|
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
|
|
|
// If the sender's membership is join and hasn't changed.
|
|
if let newContent = MXRoomMemberEventContent(fromJSON: event.content),
|
|
let prevContentDict = event.prevContent,
|
|
let oldContent = MXRoomMemberEventContent(fromJSON: prevContentDict),
|
|
newContent.membership == kMXMembershipStringJoin,
|
|
oldContent.membership == kMXMembershipStringJoin {
|
|
|
|
// Check for display name changes
|
|
if newContent.displayname != oldContent.displayname {
|
|
// If there was a change, use the sender's userID if one was blank and show the change.
|
|
if let oldDisplayname = oldContent.displayname ?? event.sender,
|
|
let displayname = newContent.displayname ?? event.sender {
|
|
notificationBody = NotificationService.localizedString(forKey: "USER_UPDATED_DISPLAYNAME", oldDisplayname, displayname)
|
|
} else {
|
|
// Should never be reached as the event should always have a sender.
|
|
notificationBody = NotificationService.localizedString(forKey: "GENERIC_USER_UPDATED_DISPLAYNAME", eventSenderName)
|
|
}
|
|
} else {
|
|
// If the display name hasn't changed, handle as an avatar change.
|
|
notificationBody = NotificationService.localizedString(forKey: "USER_UPDATED_AVATAR", eventSenderName)
|
|
}
|
|
} else {
|
|
// No known reports of having reached this situation for a membership notification
|
|
// So use a generic membership updated fallback.
|
|
notificationBody = NotificationService.localizedString(forKey: "USER_MEMBERSHIP_UPDATED", eventSenderName)
|
|
}
|
|
// Otherwise treat the notification as an invite.
|
|
// This is the expected notification content for a membership event.
|
|
} else {
|
|
if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
|
|
notificationBody = NotificationService.localizedString(forKey: "USER_INVITE_TO_NAMED_ROOM", eventSenderName, roomDisplayName)
|
|
} else {
|
|
notificationBody = NotificationService.localizedString(forKey: "USER_INVITE_TO_CHAT", eventSenderName)
|
|
}
|
|
}
|
|
|
|
case .sticker:
|
|
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
|
notificationBody = NotificationService.localizedString(forKey: "STICKER_FROM_USER", eventSenderName)
|
|
|
|
// Reactions are unexpected notification types, but have been seen in some circumstances.
|
|
case .reaction:
|
|
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
|
if let reactionKey = event.relatesTo?.key {
|
|
// Try to show the reaction key in the notification.
|
|
notificationBody = NotificationService.localizedString(forKey: "REACTION_FROM_USER", eventSenderName, reactionKey)
|
|
} else {
|
|
// Otherwise show a generic reaction.
|
|
notificationBody = NotificationService.localizedString(forKey: "GENERIC_REACTION_FROM_USER", eventSenderName)
|
|
}
|
|
|
|
case .custom:
|
|
if (event.type == kWidgetMatrixEventTypeString || event.type == kWidgetModularEventTypeString),
|
|
let type = event.content?["type"] as? String,
|
|
(type == kWidgetTypeJitsiV1 || type == kWidgetTypeJitsiV2) {
|
|
notificationBody = NotificationService.localizedString(forKey: "GROUP_CALL_STARTED")
|
|
notificationTitle = roomDisplayName
|
|
|
|
// call notifications should stand out from normal messages, so we don't stack them
|
|
threadIdentifier = nil
|
|
// only send VoIP pushes if ringing is enabled for group calls
|
|
if RiotSettings.shared.enableRingingForGroupCalls {
|
|
self.sendVoipPush(forEvent: event)
|
|
} else {
|
|
additionalUserInfo = [Constants.userInfoKeyPresentNotificationOnForeground: true]
|
|
}
|
|
}
|
|
case .pollStart:
|
|
notificationTitle = self.messageTitle(for: eventSenderName, in: roomDisplayName)
|
|
notificationBody = MXEventContentPollStart(fromJSON: event.content)?.question
|
|
default:
|
|
break
|
|
}
|
|
|
|
|
|
self.validateNotificationContentAndComplete(
|
|
notificationTitle: notificationTitle,
|
|
notificationBody: notificationBody,
|
|
additionalUserInfo: additionalUserInfo,
|
|
ignoreBadgeUpdate: ignoreBadgeUpdate,
|
|
threadIdentifier: threadIdentifier,
|
|
currentUserId: currentUserId,
|
|
event: event,
|
|
pushRule: pushRule,
|
|
onComplete: onComplete
|
|
)
|
|
case .failure(let error):
|
|
MXLog.debug("[NotificationService] notificationContentForEvent: error: \(error)")
|
|
onComplete(nil, false)
|
|
}
|
|
})
|
|
}
|
|
|
|
private func validateNotificationContentAndComplete(
|
|
notificationTitle: String?,
|
|
notificationBody: String?,
|
|
additionalUserInfo: [AnyHashable: Any]?,
|
|
ignoreBadgeUpdate: Bool,
|
|
threadIdentifier: String?,
|
|
currentUserId: String?,
|
|
event: MXEvent,
|
|
pushRule: MXPushRule?,
|
|
onComplete: @escaping (UNNotificationContent?, Bool) -> Void
|
|
) {
|
|
|
|
var validatedNotificationBody: String? = notificationBody
|
|
var validatedNotificationTitle: String? = notificationTitle
|
|
if self.localAuthenticationService.isProtectionSet {
|
|
MXLog.debug("[NotificationService] validateNotificationContentAndComplete: Resetting title and body because app protection is set")
|
|
validatedNotificationBody = NotificationService.localizedString(forKey: "MESSAGE_PROTECTED")
|
|
validatedNotificationTitle = nil
|
|
}
|
|
|
|
guard validatedNotificationBody != nil else {
|
|
MXLog.debug("[NotificationService] validateNotificationContentAndComplete: notificationBody is nil")
|
|
onComplete(nil, false)
|
|
return
|
|
}
|
|
|
|
let notificationContent = self.notificationContent(withTitle: validatedNotificationTitle,
|
|
body: validatedNotificationBody,
|
|
threadIdentifier: threadIdentifier,
|
|
userId: currentUserId,
|
|
event: event,
|
|
pushRule: pushRule,
|
|
additionalInfo: additionalUserInfo)
|
|
|
|
MXLog.debug("[NotificationService] validateNotificationContentAndComplete: Calling onComplete.")
|
|
onComplete(notificationContent, ignoreBadgeUpdate)
|
|
}
|
|
|
|
/// Returns the default title for message notifications.
|
|
/// - Parameters:
|
|
/// - eventSenderName: The displayname of the sender.
|
|
/// - roomDisplayName: The displayname of the room the message was sent in.
|
|
/// - Returns: A string to be used for the notification's title.
|
|
private func messageTitle(for eventSenderName: String, in roomDisplayName: String?) -> String {
|
|
// Display the room name only if it is different than the sender name
|
|
if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
|
|
return NotificationService.localizedString(forKey: "MSG_FROM_USER_IN_ROOM_TITLE", eventSenderName, roomDisplayName)
|
|
} else {
|
|
return eventSenderName
|
|
}
|
|
}
|
|
|
|
private func replyTitle(for eventSenderName: String, in roomDisplayName: String?) -> String {
|
|
// Display the room name only if it is different than the sender name
|
|
if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
|
|
return NotificationService.localizedString(forKey: "REPLY_FROM_USER_IN_ROOM_TITLE", eventSenderName, roomDisplayName)
|
|
} else {
|
|
return NotificationService.localizedString(forKey: "REPLY_FROM_USER_TITLE", eventSenderName)
|
|
}
|
|
}
|
|
|
|
/// Get the context of an event.
|
|
/// - Parameters:
|
|
/// - event: the event
|
|
/// - roomId: the id of the room of the event.
|
|
/// - completion: Completion block that will return the room state and the sender display name.
|
|
private func context(ofEvent event: MXEvent, inRoom roomId: String,
|
|
completion: @escaping (MXResponse<(MXRoomState, String)>) -> Void) {
|
|
// First get the room state
|
|
NotificationService.backgroundSyncService.roomState(forRoomId: roomId) { (response) in
|
|
switch response {
|
|
case .success(let roomState):
|
|
// Extract the member name from room state member
|
|
let eventSender = event.sender!
|
|
let eventSenderName = roomState.members.memberName(eventSender) ?? eventSender
|
|
|
|
// Check if we are happy with it
|
|
if eventSenderName != eventSender
|
|
|| roomState.members.member(withUserId: eventSender) != nil {
|
|
completion(.success((roomState, eventSenderName)))
|
|
return
|
|
}
|
|
|
|
// Else, if the room member is not known, use the user profile to avoid to display a Matrix id
|
|
NotificationService.backgroundSyncService.profile(ofMember: eventSender, inRoom: roomId) { (response) in
|
|
switch response {
|
|
case .success((let displayName, _)):
|
|
guard let displayName = displayName else {
|
|
completion(.success((roomState, eventSender)))
|
|
return
|
|
}
|
|
completion(.success((roomState, displayName)))
|
|
|
|
case .failure(_):
|
|
completion(.success((roomState, eventSender)))
|
|
}
|
|
}
|
|
case .failure(let error):
|
|
completion(.failure(error))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func notificationContent(withTitle title: String?,
|
|
body: String?,
|
|
threadIdentifier: String?,
|
|
userId: String?,
|
|
event: MXEvent,
|
|
pushRule: MXPushRule?,
|
|
additionalInfo: [AnyHashable: Any]? = nil) -> UNNotificationContent {
|
|
let notificationContent = UNMutableNotificationContent()
|
|
|
|
if let title = title {
|
|
notificationContent.title = title
|
|
}
|
|
if let body = body {
|
|
notificationContent.body = body
|
|
}
|
|
if let threadIdentifier = threadIdentifier {
|
|
notificationContent.threadIdentifier = threadIdentifier
|
|
}
|
|
if let categoryIdentifier = self.notificationCategoryIdentifier(forEvent: event) {
|
|
notificationContent.categoryIdentifier = categoryIdentifier
|
|
}
|
|
if let soundName = notificationSoundName(fromPushRule: pushRule) {
|
|
notificationContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: soundName))
|
|
}
|
|
notificationContent.userInfo = notificationUserInfo(forEvent: event,
|
|
andUserId: userId,
|
|
additionalInfo: additionalInfo)
|
|
|
|
return notificationContent
|
|
}
|
|
|
|
private func notificationUserInfo(forEvent event: MXEvent,
|
|
andUserId userId: String?,
|
|
additionalInfo: [AnyHashable: Any]? = nil) -> [AnyHashable: Any] {
|
|
var notificationUserInfo: [AnyHashable: Any] = [
|
|
"type": "full",
|
|
"room_id": event.roomId as Any,
|
|
"event_id": event.eventId as Any
|
|
]
|
|
if let threadId = event.threadId {
|
|
notificationUserInfo["thread_id"] = threadId
|
|
}
|
|
if let userId = userId {
|
|
notificationUserInfo["user_id"] = userId
|
|
}
|
|
if let additionalInfo = additionalInfo {
|
|
for (key, value) in additionalInfo {
|
|
notificationUserInfo[key] = value
|
|
}
|
|
}
|
|
return notificationUserInfo
|
|
}
|
|
|
|
private func notificationSoundName(fromPushRule pushRule: MXPushRule?) -> String? {
|
|
var soundName: String?
|
|
|
|
// Set sound name based on the value provided in action of MXPushRule
|
|
for ruleAction in pushRule?.actions ?? [] {
|
|
guard let action = ruleAction as? MXPushRuleAction else { continue }
|
|
guard action.actionType == MXPushRuleActionTypeSetTweak else { continue }
|
|
guard action.parameters["set_tweak"] as? String == "sound" else { continue }
|
|
soundName = action.parameters["value"] as? String
|
|
if soundName == "default" {
|
|
soundName = "message.caf"
|
|
}
|
|
}
|
|
|
|
MXLog.debug("Sound name: \(String(describing: soundName))")
|
|
|
|
return soundName
|
|
}
|
|
|
|
private func notificationCategoryIdentifier(forEvent event: MXEvent) -> String? {
|
|
let isNotificationContentShown = (!event.isEncrypted || self.showDecryptedContentInNotifications)
|
|
&& !localAuthenticationService.isProtectionSet
|
|
|
|
guard isNotificationContentShown else {
|
|
return Constants.toBeRemovedNotificationCategoryIdentifier
|
|
}
|
|
|
|
if event.eventType == .callInvite {
|
|
return Constants.callInviteNotificationCategoryIdentifier
|
|
}
|
|
|
|
guard event.eventType == .roomMessage || event.eventType == .roomEncrypted else {
|
|
return Constants.toBeRemovedNotificationCategoryIdentifier
|
|
}
|
|
|
|
return "QUICK_REPLY"
|
|
}
|
|
|
|
/// Attempts to send trigger a VoIP push for the given event
|
|
/// - Parameter event: The call invite event.
|
|
private func sendVoipPush(forEvent event: MXEvent) {
|
|
guard let token = pushNotificationStore.pushKitToken else {
|
|
return
|
|
}
|
|
|
|
if #available(iOS 13.0, *) {
|
|
if event.isEncrypted {
|
|
guard let clearEvent = event.clear else {
|
|
MXLog.debug("[NotificationService] sendVoipPush: Do not send a VoIP push for undecrypted event, it'll cause a crash.")
|
|
return
|
|
}
|
|
|
|
// Add some original data on the clear event
|
|
clearEvent.eventId = event.eventId
|
|
clearEvent.originServerTs = event.originServerTs
|
|
clearEvent.sender = event.sender
|
|
clearEvent.roomId = event.roomId
|
|
pushNotificationStore.storeCallInvite(clearEvent)
|
|
} else {
|
|
pushNotificationStore.storeCallInvite(event)
|
|
}
|
|
}
|
|
|
|
ongoingVoIPPushRequests[event.eventId] = true
|
|
|
|
let appId = BuildSettings.pushKitAppId
|
|
|
|
pushGatewayRestClient.notifyApp(withId: appId,
|
|
pushToken: token,
|
|
eventId: event.eventId,
|
|
roomId: event.roomId,
|
|
eventType: nil,
|
|
sender: event.sender,
|
|
timeout: NSE.Constants.voipPushRequestTimeout,
|
|
success: { [weak self] (rejected) in
|
|
MXLog.debug("[NotificationService] sendVoipPush succeeded, rejected tokens: \(rejected)")
|
|
|
|
guard let self = self else { return }
|
|
self.ongoingVoIPPushRequests.removeValue(forKey: event.eventId)
|
|
|
|
self.fallbackToBestAttemptContent(forEventId: event.eventId)
|
|
}) { [weak self] (error) in
|
|
MXLog.debug("[NotificationService] sendVoipPush failed with error: \(error)")
|
|
|
|
guard let self = self else { return }
|
|
self.ongoingVoIPPushRequests.removeValue(forKey: event.eventId)
|
|
|
|
self.fallbackToBestAttemptContent(forEventId: event.eventId)
|
|
}
|
|
}
|
|
|
|
private func sendReadReceipt(forEvent event: MXEvent) {
|
|
guard let mxRestClient = mxRestClient else {
|
|
MXLog.error("[NotificationService] sendReadReceipt: Missing mxRestClient for read receipt request.")
|
|
return
|
|
}
|
|
guard let eventId = event.eventId,
|
|
let roomId = event.roomId else {
|
|
MXLog.error("[NotificationService] sendReadReceipt: Event information missing for read receipt request.")
|
|
return
|
|
}
|
|
|
|
mxRestClient.sendReadReceipt(toRoom: roomId, forEvent: eventId) { response in
|
|
if response.isSuccess {
|
|
MXLog.debug("[NotificationService] sendReadReceipt: Read receipt send successfully.")
|
|
} else if let error = response.error {
|
|
MXLog.error("[NotificationService] sendReadReceipt: Read receipt send failed with error \(error).")
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func localizedString(forKey key: String, _ args: CVarArg...) -> String {
|
|
// The bundle needs to be an MXKLanguageBundle and contain the lproj files.
|
|
// MatrixKit now sets the app bundle as the MXKLanguageBundle
|
|
let format = NSLocalizedString(key, bundle: Bundle.app, comment: "")
|
|
let locale = LocaleProvider.locale ?? Locale.current
|
|
|
|
return String(format: format, locale: locale, arguments: args)
|
|
}
|
|
}
|