/* Copyright 2018-2024 New Vector Ltd. Copyright (c) 2021 BWI GmbH SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ import Foundation /// Store Riot specific app settings. @objcMembers final class RiotSettings: NSObject { // MARK: - Constants public enum UserDefaultsKeys { static let enableCrashReport = "enableCrashReport" static let enableAnalytics = "enableAnalytics" static let matomoAnalytics = "enableCrashReport" static let notificationsShowDecryptedContent = "showDecryptedContent" static let allowStunServerFallback = "allowStunServerFallback" static let pinRoomsWithMissedNotificationsOnHome = "pinRoomsWithMissedNotif" static let pinRoomsWithUnreadMessagesOnHome = "pinRoomsWithUnread" static let showAllRoomsInHomeSpace = "showAllRoomsInHomeSpace" static let enableUISIAutoReporting = "enableUISIAutoReporting" static let enableLiveLocationSharing = "enableLiveLocationSharing" static let showIPAddressesInSessionsManager = "showIPAddressesInSessionsManager" } static let shared = RiotSettings() /// UserDefaults to be used on reads and writes. static var defaults: UserDefaults = { guard let userDefaults = UserDefaults(suiteName: BuildSettings.applicationGroupIdentifier) else { fatalError("[RiotSettings] Fail to load shared UserDefaults") } return userDefaults }() private override init() { super.init() RiotSettings.defaults.register( defaults: [ "settingTimeStampEnabled": true, "settingUserAvatarChangeEnabled": false, "settingEnterRoomEnabled": true ] ) } /// Indicate if UserDefaults suite has been migrated once. var isUserDefaultsMigrated: Bool { return RiotSettings.defaults.object(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) != nil } func migrate() { // read all values from standard let dictionary = UserDefaults.standard.dictionaryRepresentation() // write values to suite // remove redundant values from standard for (key, value) in dictionary { RiotSettings.defaults.set(value, forKey: key) UserDefaults.standard.removeObject(forKey: key) } } // MARK: Servers @UserDefault(key: "homeserverurl", defaultValue: BWIBuildSettings.shared.serverConfigDefaultHomeserverUrlString, storage: defaults) var homeserverUrlString @UserDefault(key: "identityserverurl", defaultValue: BWIBuildSettings.shared.serverConfigDefaultIdentityServerUrlString, storage: defaults) var identityServerUrlString // MARK: Notifications /// Indicate if `showDecryptedContentInNotifications` settings has been set once. var isShowDecryptedContentInNotificationsHasBeenSetOnce: Bool { return RiotSettings.defaults.object(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) != nil } /// Indicate if notifications should be shown whilst the app is in the foreground. @UserDefault(key: "showInAppNotifications", defaultValue: true, storage: defaults) var showInAppNotifications /// Indicate if encrypted messages content should be displayed in notifications. @UserDefault(key: UserDefaultsKeys.notificationsShowDecryptedContent, defaultValue: false, storage: defaults) var showDecryptedContentInNotifications /// Indicate if rooms with missed notifications should be displayed first on home screen. @UserDefault(key: UserDefaultsKeys.pinRoomsWithMissedNotificationsOnHome, defaultValue: false, storage: defaults) var pinRoomsWithMissedNotificationsOnHome /// Indicate if rooms with unread messages should be displayed first on home screen. @UserDefault(key: UserDefaultsKeys.pinRoomsWithUnreadMessagesOnHome, defaultValue: false, storage: defaults) var pinRoomsWithUnreadMessagesOnHome // MARK: User interface @UserDefault(key: "userInterfaceTheme", defaultValue: nil, storage: defaults) var userInterfaceTheme // MARK: Analytics & Rageshakes /// Whether the user was previously shown the Matomo analytics prompt. var hasSeenAnalyticsPrompt: Bool { RiotSettings.defaults.object(forKey: UserDefaultsKeys.enableAnalytics) != nil } @UserDefault(key: UserDefaultsKeys.enableCrashReport, defaultValue: false, storage: defaults) var enableCrashReport /// Whether the user has both seen the Matomo analytics prompt and declined it. var hasDeclinedMatomoAnalytics: Bool { RiotSettings.defaults.object(forKey: UserDefaultsKeys.matomoAnalytics) != nil && !RiotSettings.defaults.bool(forKey: UserDefaultsKeys.matomoAnalytics) } /// Whether the user previously accepted the Matomo analytics prompt. /// This allows these users to be shown a different prompt to explain the changes. var hasAcceptedMatomoAnalytics: Bool { RiotSettings.defaults.bool(forKey: UserDefaultsKeys.matomoAnalytics) } /// `true` when the user has opted in to send analytics. @UserDefault(key: UserDefaultsKeys.enableAnalytics, defaultValue: false, storage: defaults) var enableAnalytics /// Indicates if the device has already called identify for this session to PostHog. /// This is separate to `enableAnalytics` as logging out will leave analytics /// enabled but reset identification. @UserDefault(key: "isIdentifiedForAnalytics", defaultValue: false, storage: defaults) var isIdentifiedForAnalytics @UserDefault(key: "enableRageShake", defaultValue: false, storage: defaults) var enableRageShake // MARK: User /// A dictionary of dictionaries keyed by user ID for storage of the `UserSessionProperties` from any active `UserSession`s. @UserDefault(key: "userSessionProperties", defaultValue: [:], storage: defaults) var userSessionProperties: [String: [String: Any]] // MARK: Labs /// Indicates if CallKit ringing is enabled for group calls. This setting does not disable the CallKit integration for group calls, only relates to ringing. @UserDefault(key: "enableRingingForGroupCalls", defaultValue: false, storage: defaults) var enableRingingForGroupCalls /// Indicates if threads enabled in the timeline. @UserDefault(key: "enableThreads", defaultValue: false, storage: defaults) var enableThreads /// Indicates if threads should be forced enabled in the timeline. @UserDefault(key: "forceThreadsEnabled", defaultValue: true, storage: defaults) var forceThreadsEnabled /// Indicates if auto reporting of decryption errors is enabled @UserDefault(key: UserDefaultsKeys.enableUISIAutoReporting, defaultValue: BuildSettings.cryptoUISIAutoReportingEnabled, storage: defaults) var enableUISIAutoReporting /// Indicates if live location sharing is enabled @UserDefault(key: UserDefaultsKeys.enableLiveLocationSharing, defaultValue: false, storage: defaults) var enableLiveLocationSharing { didSet { NotificationCenter.default.post(name: RiotSettings.didUpdateLiveLocationSharingActivation, object: self) } } /// Flag indicating if the new session manager is enabled @UserDefault(key: "enableNewSessionManager", defaultValue: false, storage: defaults) var enableNewSessionManager /// Flag indicating if the new client information feature is enabled @UserDefault(key: "enableClientInformationFeature", defaultValue: false, storage: defaults) var enableClientInformationFeature /// Flag indicating if the wysiwyg composer feature is enabled @UserDefault(key: "enableWysiwygComposer", defaultValue: BWIBuildSettings.shared.enableFeatureWYSIWYGByDefault, storage: defaults) var enableWysiwygComposer @UserDefault(key: "enableWysiwygTextFormatting", defaultValue: true, storage: defaults) var enableWysiwygTextFormatting /// Flag indicating if the IP addresses should be shown in the new device manager @UserDefault(key: UserDefaultsKeys.showIPAddressesInSessionsManager, defaultValue: false, storage: defaults) var showIPAddressesInSessionsManager /// Flag indicating if the voice broadcast feature is enabled @UserDefault(key: "enableVoiceBroadcast", defaultValue: false, storage: defaults) var enableVoiceBroadcast /// Flag indicating if we are using rust-based `MatrixCryptoSDK` instead of `MatrixSDK`'s internal crypto module @UserDefault(key: "enableCryptoSDK", defaultValue: false, storage: defaults) var enableCryptoSDK // MARK: Calls /// Indicate if `allowStunServerFallback` settings has been set once. var isAllowStunServerFallbackHasBeenSetOnce: Bool { return RiotSettings.defaults.object(forKey: UserDefaultsKeys.allowStunServerFallback) != nil } @UserDefault(key: UserDefaultsKeys.allowStunServerFallback, defaultValue: false, storage: defaults) var allowStunServerFallback // MARK: Key verification @UserDefault(key: "hideVerifyThisSessionAlert", defaultValue: false, storage: defaults) var hideVerifyThisSessionAlert @UserDefault(key: "showVerificationUpgradeAlert", defaultValue: false, storage: defaults) var showVerificationUpgradeAlert @UserDefault(key: "matrixApps", defaultValue: false, storage: defaults) var matrixApps // MARK: - PIN & Biometrics @UserDefault(key: "biometricsEnabled", defaultValue: false, storage: defaults) var biometricsEnabled @UserDefault(key: "canUseBiometricsToUnlock", defaultValue: true, storage: defaults) var canUseBiometricsToUnlock @UserDefault(key: "numberOfPinFailures", defaultValue: 0, storage: defaults) var numberOfPinFailures @UserDefault(key: "numberOfBiometricsFailures", defaultValue: 0, storage: defaults) var numberOfBiometricsFailures // MARK: - Rooms Screen @UserDefault(key: "roomsAllowToJoinPublicRooms", defaultValue: BuildSettings.roomsAllowToJoinPublicRooms, storage: defaults) var roomsAllowToJoinPublicRooms @UserDefault(key: UserDefaultsKeys.showAllRoomsInHomeSpace, defaultValue: true, storage: defaults) var showAllRoomsInHomeSpace // MARK: - Room Screen @UserDefault(key: "roomScreenAllowVoIPForDirectRoom", defaultValue: BuildSettings.roomScreenAllowVoIPForDirectRoom, storage: defaults) var roomScreenAllowVoIPForDirectRoom @UserDefault(key: "roomScreenAllowVoIPForNonDirectRoom", defaultValue: BuildSettings.roomScreenAllowVoIPForNonDirectRoom, storage: defaults) var roomScreenAllowVoIPForNonDirectRoom @UserDefault(key: "roomScreenAllowCameraAction", defaultValue: BuildSettings.roomScreenAllowCameraAction, storage: defaults) var roomScreenAllowCameraAction @UserDefault(key: "roomScreenAllowMediaLibraryAction", defaultValue: BuildSettings.roomScreenAllowMediaLibraryAction, storage: defaults) var roomScreenAllowMediaLibraryAction @UserDefault(key: "roomScreenAllowStickerAction", defaultValue: BWIBuildSettings.shared.roomScreenAllowStickerAction, storage: defaults) var roomScreenAllowStickerAction @UserDefault(key: "roomScreenAllowFilesAction", defaultValue: BuildSettings.roomScreenAllowFilesAction, storage: defaults) var roomScreenAllowFilesAction @UserDefault(key: "roomScreenAllowPollsAction", defaultValue: BuildSettings.pollsEnabled, storage: defaults) var roomScreenAllowPollsAction @UserDefault(key: "roomScreenAllowLocationAction", defaultValue: false, storage: defaults) var roomScreenAllowLocationAction @UserDefault(key: "roomScreenShowsURLPreviews", defaultValue: true, storage: defaults) var roomScreenShowsURLPreviews @UserDefault(key: "roomScreenEnableMessageBubbles", defaultValue: BuildSettings.isRoomScreenEnableMessageBubblesByDefault, storage: defaults) var roomScreenEnableMessageBubbles var roomTimelineStyleIdentifier: RoomTimelineStyleIdentifier { return .bubble // bwi: on by default because there is no toggle in the settings // return self.roomScreenEnableMessageBubbles ? .bubble : .plain } /// A setting used to display the latest known display name and avatar in the timeline /// for both the sender and target, rather than the profile at the time of the event. /// /// Note: this is set up from Room perspective, which means that if a user updates their profile after /// leaving a Room, it will show up the latest profile used in the Room rather than the latest overall. // bwi: #5506 alway use the latest avatar @UserDefault(key: "roomScreenUseOnlyLatestUserAvatarAndName", defaultValue: BWIBuildSettings.shared.enableRoomScreenUseOnlyLatestUserAvatarAndNameByDefault, storage: defaults) var roomScreenUseOnlyLatestUserAvatarAndName // MARK: - Room Contextual Menu @UserDefault(key: "roomContextualMenuShowMoreOptionForMessages", defaultValue: BuildSettings.roomContextualMenuShowMoreOptionForMessages, storage: defaults) var roomContextualMenuShowMoreOptionForMessages @UserDefault(key: "roomContextualMenuShowMoreOptionForStates", defaultValue: BuildSettings.roomContextualMenuShowMoreOptionForStates, storage: defaults) var roomContextualMenuShowMoreOptionForStates @UserDefault(key: "roomContextualMenuShowReportContentOption", defaultValue: BuildSettings.roomContextualMenuShowReportContentOption, storage: defaults) var roomContextualMenuShowReportContentOption // MARK: - Room Info Screen @UserDefault(key: "roomInfoScreenShowIntegrations", defaultValue: BWIBuildSettings.shared.roomInfoScreenShowIntegrations, storage: defaults) var roomInfoScreenShowIntegrations // MARK: - Room Member Screen @UserDefault(key: "roomMemberScreenShowIgnore", defaultValue: BuildSettings.roomMemberScreenShowIgnore, storage: defaults) var roomMemberScreenShowIgnore // MARK: - Room Creation Screen @UserDefault(key: "roomCreationScreenAllowEncryptionConfiguration", defaultValue: BWIBuildSettings.shared.roomCreationScreenAllowEncryptionConfiguration, storage: defaults) var roomCreationScreenAllowEncryptionConfiguration @UserDefault(key: "roomCreationScreenRoomIsEncrypted", defaultValue: BuildSettings.roomCreationScreenRoomIsEncrypted, storage: defaults) var roomCreationScreenRoomIsEncrypted @UserDefault(key: "roomCreationScreenAllowRoomTypeConfiguration", defaultValue: BuildSettings.roomCreationScreenAllowRoomTypeConfiguration, storage: defaults) var roomCreationScreenAllowRoomTypeConfiguration @UserDefault(key: "roomCreationScreenRoomIsPublic", defaultValue: BuildSettings.roomCreationScreenRoomIsPublic, storage: defaults) var roomCreationScreenRoomIsPublic // MARK: Features @UserDefault(key: "allowInviteExernalUsers", defaultValue: BWIBuildSettings.shared.allowInviteExernalUsers, storage: defaults) var allowInviteExernalUsers /// When set to false the original image is sent and a 1080p preset is used for videos. /// If `BuildSettings.roomInputToolbarCompressionMode` has a value other than prompt, the build setting takes priority for images. @UserDefault(key: "showMediaCompressionPrompt", defaultValue: false, storage: defaults) var showMediaCompressionPrompt // MARK: - Main Tabs // bwi: hide home tab @UserDefault(key: "homeScreenShowHomeTab", defaultValue: BWIBuildSettings.shared.homeScreenShowHomeTab, storage: defaults) var homeScreenShowHomeTab @UserDefault(key: "homeScreenShowFavouritesTab", defaultValue: BuildSettings.homeScreenShowFavouritesTab, storage: defaults) var homeScreenShowFavouritesTab @UserDefault(key: "homeScreenShowPeopleTab", defaultValue: BuildSettings.homeScreenShowPeopleTab, storage: defaults) var homeScreenShowPeopleTab @UserDefault(key: "homeScreenShowRoomsTab", defaultValue: BuildSettings.homeScreenShowRoomsTab, storage: defaults) var homeScreenShowRoomsTab // MARK bwi Left Settings @UserDefault(key: "settingsScreenShowInviteFriends", defaultValue: BWIBuildSettings.shared.settingsScreenShowInviteFriends, storage: defaults) var settingsScreenShowInviteFriends @UserDefault(key: "settingsScreenShowSettings", defaultValue: BWIBuildSettings.shared.settingsScreenShowSettings, storage: defaults) var settingsScreenShowSettings @UserDefault(key: "settingsScreenShowFeedback", defaultValue: BWIBuildSettings.shared.settingsScreenShowFeedback, storage: defaults) var settingsScreenShowFeedback @UserDefault(key: "settingsScreenShowHelp", defaultValue: BWIBuildSettings.shared.settingsScreenShowHelp, storage: defaults) var settingsScreenShowHelp @UserDefault(key: "settingsScreenShowSystemSettingsOption", defaultValue: BWIBuildSettings.shared.settingsScreenShowSystemSettingsOption, storage: defaults) var settingsScreenShowSystemSettingsOption @UserDefault(key: "homeScreenShowHomeTab", defaultValue: BWIBuildSettings.shared.bwiMatomoTrackingDefaultState, storage: defaults) var bwiEnableMatomoTracking // MARK: General Settings @UserDefault(key: "settingsScreenShowChangePassword", defaultValue: BuildSettings.settingsScreenShowChangePassword, storage: defaults) var settingsScreenShowChangePassword @UserDefault(key: "settingsScreenShowEnableStunServerFallback", defaultValue: BuildSettings.settingsScreenShowEnableStunServerFallback, storage: defaults) var settingsScreenShowEnableStunServerFallback @UserDefault(key: "settingsScreenShowNotificationDecodedContentOption", defaultValue: BWIBuildSettings.shared.settingsScreenShowNotificationDecodedContentOption, storage: defaults) var settingsScreenShowNotificationDecodedContentOption @UserDefault(key: "settingsSecurityScreenShowSessions", defaultValue: BuildSettings.settingsSecurityScreenShowSessions, storage: defaults) var settingsSecurityScreenShowSessions @UserDefault(key: "settingsSecurityScreenShowSetupBackup", defaultValue: BuildSettings.settingsSecurityScreenShowSetupBackup, storage: defaults) var settingsSecurityScreenShowSetupBackup @UserDefault(key: "settingsSecurityScreenShowRestoreBackup", defaultValue: BuildSettings.settingsSecurityScreenShowRestoreBackup, storage: defaults) var settingsSecurityScreenShowRestoreBackup @UserDefault(key: "settingsSecurityScreenShowDeleteBackup", defaultValue: BuildSettings.settingsSecurityScreenShowDeleteBackup, storage: defaults) var settingsSecurityScreenShowDeleteBackup @UserDefault(key: "settingsSecurityScreenShowCryptographyInfo", defaultValue: BWIBuildSettings.shared.settingsSecurityScreenShowCryptographyInfo, storage: defaults) var settingsSecurityScreenShowCryptographyInfo @UserDefault(key: "settingsSecurityScreenShowCryptographyExport", defaultValue: BWIBuildSettings.shared.settingsSecurityScreenShowCryptographyExport, storage: defaults) var settingsSecurityScreenShowCryptographyExport @UserDefault(key: "settingsSecurityScreenShowAdvancedBlacklistUnverifiedDevices", defaultValue: BWIBuildSettings.shared.settingsSecurityScreenShowAdvancedUnverifiedDevices, storage: defaults) var settingsSecurityScreenShowAdvancedUnverifiedDevices @UserDefault(key: "settingsNotificationsShowDefault", defaultValue: BWIBuildSettings.shared.settingsNotificationsShowDefault, storage: defaults) var settingsNotificationsShowDefault @UserDefault(key: "settingsNotificationsShowMentions", defaultValue: BWIBuildSettings.shared.settingsNotificationsShowMentions, storage: defaults) var settingsNotificationsShowMentions @UserDefault(key: "settingsNotificationsShowAdvanced", defaultValue: BWIBuildSettings.shared.settingsNotificationsShowAdvanced, storage: defaults) var settingsNotificationsShowAdvanced // MARK: - Room Settings Screen @UserDefault(key: "roomSettingsScreenShowLowPriorityOption", defaultValue: BWIBuildSettings.shared.roomSettingsScreenShowLowPriorityOption, storage: defaults) var roomSettingsScreenShowLowPriorityOption @UserDefault(key: "roomSettingsScreenShowDirectChatOption", defaultValue: BWIBuildSettings.shared.roomSettingsScreenShowDirectChatOption, storage: defaults) var roomSettingsScreenShowDirectChatOption @UserDefault(key: "roomSettingsScreenAllowChangingAccessSettings", defaultValue: BWIBuildSettings.shared.roomSettingsScreenAllowChangingAccessSettings, storage: defaults) var roomSettingsScreenAllowChangingAccessSettings @UserDefault(key: "roomSettingsScreenAllowChangingHistorySettings", defaultValue: BWIBuildSettings.shared.roomSettingsScreenAllowChangingHistorySettings, storage: defaults) var roomSettingsScreenAllowChangingHistorySettings @UserDefault(key: "roomSettingsScreenShowAddressSettings", defaultValue: BWIBuildSettings.shared.roomSettingsScreenShowAddressSettings, storage: defaults) var roomSettingsScreenShowAddressSettings @UserDefault(key: "roomSettingsScreenShowAdvancedSettings", defaultValue: BWIBuildSettings.shared.roomSettingsScreenShowAdvancedSettings, storage: defaults) var roomSettingsScreenShowAdvancedSettings @UserDefault(key: "roomSettingsScreenAdvancedShowEncryptToVerifiedOption", defaultValue: BuildSettings.roomSettingsScreenAdvancedShowEncryptToVerifiedOption, storage: defaults) var roomSettingsScreenAdvancedShowEncryptToVerifiedOption // MARK: - Unified Search @UserDefault(key: "unifiedSearchScreenShowPublicDirectory", defaultValue: BWIBuildSettings .shared.unifiedSearchScreenShowPublicDirectory, storage: defaults) var unifiedSearchScreenShowPublicDirectory // MARK: - Secrets Recovery @UserDefault(key: "secretsRecoveryAllowReset", defaultValue: BuildSettings.secretsRecoveryAllowReset, storage: defaults) var secretsRecoveryAllowReset // MARK: - Beta @UserDefault(key: "hideSpaceBetaAnnounce", defaultValue: false, storage: defaults) var hideSpaceBetaAnnounce @UserDefault(key: "threadsNoticeDisplayed", defaultValue: false, storage: defaults) var threadsNoticeDisplayed // MARK: - Version check @UserDefault(key: "versionCheckNextDisplayDateTimeInterval", defaultValue: 0.0, storage: defaults) var versionCheckNextDisplayDateTimeInterval @UserDefault(key: "slideMenuRoomsCoachMessageHasBeenDisplayed", defaultValue: false, storage: defaults) var slideMenuRoomsCoachMessageHasBeenDisplayed // MARK: - Metrics /// Number of spaces previously tracked by the `AnalyticsSpaceTracker` instance. @UserDefault(key: "lastNumberOfTrackedSpaces", defaultValue: nil, storage: defaults) var lastNumberOfTrackedSpaces: Int? } // MARK: - RiotSettings notification constants extension RiotSettings { public static let didUpdateLiveLocationSharingActivation = Notification.Name("RiotSettingsDidUpdateLiveLocationSharingActivation") }