/* Copyright 2018-2024 New Vector Ltd. Copyright 2017 Vector Creations Ltd Copyright 2015 OpenMarket Ltd Copyright (c) 2021 BWI GmbH SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ #import "SettingsViewController.h" #import "AvatarGenerator.h" #import "BugReportViewController.h" #import "WebViewViewController.h" #import "CountryPickerViewController.h" #import "LanguagePickerViewController.h" #import "DeactivateAccountViewController.h" #import "SecurityViewController.h" #import "NBPhoneNumberUtil.h" #import "RageShakeManager.h" #import "ThemeService.h" #import "TableViewCellWithPhoneNumberTextField.h" #import "GBDeviceInfo_iOS.h" #import "MediaPickerViewController.h" #import "GeneratedInterface-Swift.h" @import DesignKit; NSString* const kSettingsViewControllerPhoneBookCountryCellId = @"kSettingsViewControllerPhoneBookCountryCellId"; NSString *const BWISettingsErrorDomain = @"BWISettingsErrorDomain"; typedef NS_ENUM(NSUInteger, SECTION_TAG) { SECTION_TAG_SIGN_OUT = 0, SECTION_TAG_USER_SETTINGS, SECTION_TAG_NEW_FEATURES, SECTION_TAG_LOCATION_SHARING, SECTION_TAG_ACCOUNT, SECTION_TAG_SENDING_MEDIA, SECTION_TAG_LINKS, SECTION_TAG_SECURITY, SECTION_TAG_NOTIFICATIONS, SECTION_TAG_CALLS, SECTION_TAG_DISCOVERY, SECTION_TAG_IDENTITY_SERVER, SECTION_TAG_LOCAL_CONTACTS, SECTION_TAG_IGNORED_USERS, SECTION_TAG_INTEGRATIONS, SECTION_TAG_USER_INTERFACE, SECTION_TAG_TIMELINE, SECTION_TAG_PRESENCE, SECTION_TAG_ADVANCED, SECTION_TAG_ABOUT, SECTION_TAG_LABS, SECTION_TAG_DEACTIVATE_ACCOUNT, SECTION_TAG_DOWNTIME_WARNING, SECTION_TAG_OUTDATED_WARNING, SECTION_TAG_DEVELOPER }; typedef NS_ENUM(NSUInteger, USER_SETTINGS_INDEX) { USER_SETTINGS_PROFILE_PICTURE_INDEX = 0, USER_SETTINGS_DISPLAYNAME_INDEX, USER_SETTINGS_PERSONAL_STATE_INDEX, USER_SETTINGS_CHANGE_PASSWORD_INDEX, USER_SETTINGS_MY_QR_CODE_INDEX, USER_SETTINGS_FIRST_NAME_INDEX, USER_SETTINGS_SURNAME_INDEX, USER_SETTINGS_ADD_EMAIL_INDEX, USER_SETTINGS_ADD_PHONENUMBER_INDEX }; typedef NS_ENUM(NSUInteger, DEVELOPER_SETTINGS_INDEX) { DEVELOPER_SHOW_SETTINGS_INDEX = 0 }; typedef NS_ENUM(NSUInteger, USER_SETTINGS_OFFSET) { USER_SETTINGS_EMAILS_OFFSET = 2000, USER_SETTINGS_PHONENUMBERS_OFFSET = 1000 }; typedef NS_ENUM(NSUInteger, NEW_FEATURES) { SHOW_NEW_FEATURES }; typedef NS_ENUM(NSUInteger, LOCATION_SHARING) { LOCATION_SHARING_ENABLED }; typedef NS_ENUM(NSUInteger, SENDING_MEDIA) { SENDING_MEDIA_CONFIRM_SIZE = 0 }; typedef NS_ENUM(NSUInteger, LINKS_SHOW_URL_PREVIEWS) { LINKS_SHOW_URL_PREVIEWS_INDEX = 0, LINKS_SHOW_URL_PREVIEWS_DESCRIPTION_INDEX }; typedef NS_ENUM(NSUInteger, NOTIFICATION_SETTINGS) { NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX = 0, NOTIFICATION_SETTINGS_SYSTEM_SETTINGS, NOTIFICATION_SETTINGS_SHOW_IN_APP_INDEX, NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT, NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX, NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX, NOTIFICATION_SETTINGS_DEFAULT_SETTINGS_INDEX, NOTIFICATION_SETTINGS_MENTION_AND_KEYWORDS_SETTINGS_INDEX, NOTIFICATION_SETTINGS_NOTIFICATION_TIMES_INDEX, NOTIFICATION_SETTINGS_OTHER_SETTINGS_INDEX, }; typedef NS_ENUM(NSUInteger, IGNORED_USERS) { IGNORED_USERS_INDEX }; typedef NS_ENUM(NSUInteger, CALLS_ENABLE_STUN_SERVER) { CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX = 0 }; typedef NS_ENUM(NSUInteger, INTEGRATIONS) { INTEGRATIONS_INDEX }; typedef NS_ENUM(NSUInteger, LOCAL_CONTACTS) { LOCAL_CONTACTS_SYNC_INDEX, LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX }; typedef NS_ENUM(NSUInteger, USER_INTERFACE) { //USER_INTERFACE_LANGUAGE_INDEX = 0, USER_INTERFACE_THEME_INDEX = 0, TIMELINE_SIMPLE_INDEX, TIMELINE_TIMESTAMP_INDEX, TIMELINE_DELETED_MESSAGES_INDEX, TIMELINE_NAME_CHANGE_INDEX, TIMELINE_CHATEFFECTS_INDEX, TIMELINE_ROOM_AVATAR_INDEX, TIMELINE_USER_AVATAR_INDEX, TIMELINE_ENTER_ROOM_INDEX, USER_INTERFACE_LANGUAGE_INDEX, USER_INTERFACE_TIMELINE_STYLE_INDEX, PERSONAL_NOTES_INDEX, WYSIWYG_COMPOSER_INDEX }; typedef NS_ENUM(NSUInteger, TIMELINE) { TIMELINE_STYLE_INDEX, TIMELINE_SHOW_REDACTIONS_IN_ROOM_HISTORY_INDEX, TIMELINE_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX }; typedef NS_ENUM(NSUInteger, IDENTITY_SERVER) { IDENTITY_SERVER_INDEX }; typedef NS_ENUM(NSUInteger, PRESENCE) { PRESENCE_OFFLINE_MODE = 0, }; typedef NS_ENUM(NSUInteger, ADVANCED) { ADVANCED_CRASH_REPORT_INDEX = 0, ADVANCED_ENABLE_RAGESHAKE_INDEX, ADVANCED_MARK_ALL_AS_READ_INDEX, ADVANCED_CLEAR_CACHE_INDEX, ADVANCED_REPORT_BUG_INDEX, }; typedef NS_ENUM(NSUInteger, ABOUT) { ABOUT_VERSION_INDEX = 0, ABOUT_OLM_VERSION_INDEX, ABOUT_ELEMENT_VERSION_INDEX, ABOUT_COPYRIGHT_INDEX, ABOUT_TERM_CONDITIONS_INDEX, ABOUT_ACCEPTABLE_USE_INDEX, ABOUT_PRIVACY_INDEX, ABOUT_IMPRINT_INDEX, ABOUT_THIRD_PARTY_INDEX, ABOUT_SUPPORT_INDEX, ABOUT_CRASH_REPORT_INDEX, ABOUT_ENABLE_RAGESHAKE_INDEX, ABOUT_MARK_ALL_AS_READ_INDEX, ABOUT_CLEAR_CACHE_INDEX, ABOUT_REPORT_BUG_INDEX, ABOUT_NETIQUETTE_INDEX, ABOUT_ACCESSIBILITY_DECLARATION_INDEX, }; typedef NS_ENUM(NSUInteger, LABS_ENABLE) { LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX = 0, LABS_ENABLE_THREADS_INDEX, LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS, LABS_ENABLE_LIVE_LOCATION_SHARING, LABS_ENABLE_NEW_SESSION_MANAGER, LABS_ENABLE_NEW_CLIENT_INFO_FEATURE, LABS_ENABLE_WYSIWYG_COMPOSER, LABS_ENABLE_VOICE_BROADCAST, LABS_ENABLE_FEDERATION }; typedef NS_ENUM(NSUInteger, SECURITY) { SECURITY_BUTTON_INDEX = 0, DEVICE_MANAGER_INDEX }; enum { DOWNTIME_WARNING = 0, DOWNTIME_IGNORE_BLOCKING }; enum { OUTDATED_WARNING = 0 }; typedef NS_ENUM(NSUInteger, ACCOUNT) { ACCOUNT_MANAGE_INDEX = 0, }; typedef void (^blockSettingsViewController_onReadyToDestroy)(void); #pragma mark - SettingsViewController @interface SettingsViewController () { // Current alert (if any). __weak UIAlertController *currentAlert; // listener __weak id removedAccountObserver; __weak id accountUserInfoObserver; __weak id pushInfoUpdateObserver; __weak id notificationCenterWillUpdateObserver; __weak id notificationCenterDidUpdateObserver; __weak id notificationCenterDidFailObserver; // bwi: 4203 reference to profile image view __weak MXKImageView* bwiProfileImage; // profile updates // avatar UIImage* newAvatarImage; BOOL shouldRemoveAvatarImage; // the avatar image has been uploaded NSString* uploadedAvatarURL; // new display name NSString* newDisplayName; // New email address to bind UITextField* newEmailTextField; // New phone number to bind TableViewCellWithPhoneNumberTextField * newPhoneNumberCell; CountryPickerViewController *newPhoneNumberCountryPicker; NBPhoneNumber *newPhoneNumber; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. __weak id kAppDelegateDidTapStatusBarNotificationObserver; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. __weak id kThemeServiceDidChangeThemeNotificationObserver; // Postpone destroy operation when saving or email binding is in progress BOOL isSavingInProgress; BOOL is3PIDBindingInProgress; blockSettingsViewController_onReadyToDestroy onReadyToDestroyHandler; BOOL keepNewEmailEditing; BOOL keepNewPhoneNumberEditing; // The current pushed view controller UIViewController *pushedViewController; SettingsIdentityServerCoordinatorBridgePresenter *identityServerSettingsCoordinatorBridgePresenter; } /** Flag indicating whether the user is typing an email to bind. */ @property (nonatomic) BOOL newEmailEditingEnabled; /** Flag indicating whether the user is typing a phone number to bind. */ @property (nonatomic) BOOL newPhoneEditingEnabled; /** The current `UNUserNotificationCenter` notification settings for the app. */ @property (nonatomic) UNNotificationSettings *systemNotificationSettings; @property (nonatomic, weak) DeactivateAccountViewController *deactivateAccountViewController; @property (nonatomic, strong) NotificationSettingsCoordinatorBridgePresenter *notificationSettingsBridgePresenter; @property (nonatomic, strong) SignOutFlowPresenter *signOutFlowPresenter; @property (nonatomic, weak) UIButton *signOutButton; @property (nonatomic, strong) SingleImagePickerPresenter *imagePickerPresenter; @property (nonatomic, strong) SettingsDiscoveryViewModel *settingsDiscoveryViewModel; @property (nonatomic, strong) SettingsDiscoveryTableViewSection *settingsDiscoveryTableViewSection; @property (nonatomic, strong) SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter; @property (nonatomic, strong) TableViewSections *tableViewSections; @property (nonatomic, strong) ReauthenticationCoordinatorBridgePresenter *reauthenticationCoordinatorBridgePresenter; @property (nonatomic, strong) UserInteractiveAuthenticationService *userInteractiveAuthenticationService; @property (nonatomic, strong) ThreadsBetaCoordinatorBridgePresenter *threadsBetaBridgePresenter; @property (nonatomic, strong) ChangePasswordCoordinatorBridgePresenter *changePasswordBridgePresenter; @property (nonatomic, strong) UserSessionsFlowCoordinatorBridgePresenter *userSessionsFlowCoordinatorBridgePresenter; /** Whether or not to check for contacts access after the user accepts the service terms. The value of this property is set automatically when calling `prepareIdentityServiceAndPresentTermsWithSession:checkingAccessForContactsOnAccept` */ @property (nonatomic) BOOL serviceTermsModalShouldCheckAccessForContactsOnAccept; @property (nonatomic) BOOL isPreparingIdentityService; @property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter; @property (nonatomic, strong) SSOAuthenticationPresenter *ssoAuthenticationPresenter; @property (nonatomic) AnalyticsScreenTracker *screenTracker; @end @implementation SettingsViewController - (UserInteractiveAuthenticationService*)userInteractiveAuthenticationService { if (!_userInteractiveAuthenticationService) { _userInteractiveAuthenticationService = [self createUserInteractiveAuthenticationService]; } return _userInteractiveAuthenticationService; } + (instancetype)instantiate { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; SettingsViewController *settingsViewController = [storyboard instantiateViewControllerWithIdentifier:@"SettingsViewController"]; return settingsViewController; } - (void)finalizeInit { [super finalizeInit]; // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; isSavingInProgress = NO; is3PIDBindingInProgress = NO; self.screenTracker = [[AnalyticsScreenTracker alloc] initWithScreen:AnalyticsScreenSettings]; } - (void)dealloc { // Fix for destroy not being called [self destroy]; } - (void)updateSections { NSMutableArray *tmpSections = [NSMutableArray arrayWithCapacity:SECTION_TAG_DEVELOPER + 1]; ServerDowntimeDefaultService* service = [[ServerDowntimeDefaultService alloc] init]; if ( [service isDowntimePresentable]) { Section *sectionDowntime = [Section sectionWithTag:SECTION_TAG_DOWNTIME_WARNING]; [sectionDowntime addRowWithTag:DOWNTIME_WARNING]; // bwi #4982 special handling for blocking in Beta if( BWIBuildSettings.shared.ignoreBlockingMaintenance && [service isBlocking] && ![service isManuallyIgnored] ) { [sectionDowntime addRowWithTag:DOWNTIME_IGNORE_BLOCKING]; } [tmpSections addObject:sectionDowntime]; } if (BWIBuildSettings.shared.bwiCheckAppVersion && [ValidAppVersionsDefaultService.shared isCurrentAppVersionOutdated]) { Section *sectionOutdated = [Section sectionWithTag:SECTION_TAG_OUTDATED_WARNING]; [sectionOutdated addRowWithTag:OUTDATED_WARNING]; sectionOutdated.headerTitle = [BWIL10n bwiOutdatedSettingsHeader]; [tmpSections addObject:sectionOutdated]; } Section *sectionUserSettings = [Section sectionWithTag:SECTION_TAG_USER_SETTINGS]; [sectionUserSettings addRowWithTag:USER_SETTINGS_PROFILE_PICTURE_INDEX]; [sectionUserSettings addRowWithTag:USER_SETTINGS_DISPLAYNAME_INDEX]; if (BWIBuildSettings.shared.bwiEnablePersonalState) { [sectionUserSettings addRowWithTag:USER_SETTINGS_PERSONAL_STATE_INDEX]; } if (RiotSettings.shared.settingsScreenShowChangePassword) { [sectionUserSettings addRowWithTag:USER_SETTINGS_CHANGE_PASSWORD_INDEX]; } if (BWIBuildSettings.shared.showMyQRCode) { [sectionUserSettings addRowWithTag:USER_SETTINGS_MY_QR_CODE_INDEX]; } if (BuildSettings.settingsScreenShowUserFirstName) { [sectionUserSettings addRowWithTag:USER_SETTINGS_FIRST_NAME_INDEX]; } if (BuildSettings.settingsScreenShowUserSurname) { [sectionUserSettings addRowWithTag:USER_SETTINGS_SURNAME_INDEX]; } MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; // add linked emails if (BWIBuildSettings.shared.settingsScreenAllowAddingLinkedEmails) { for (NSInteger index = 0; index < account.linkedEmails.count; index++) { [sectionUserSettings addRowWithTag: USER_SETTINGS_EMAILS_OFFSET + index]; } } // add linked phone numbers if (BWIBuildSettings.shared.settingsScreenAllowAddingPhoneNumbers) { for (NSInteger index = 0; index < account.linkedPhoneNumbers.count; index++) { [sectionUserSettings addRowWithTag: USER_SETTINGS_PHONENUMBERS_OFFSET + index]; } } // If the threePidChanges is nil we assume the capability to be true if (!self.mainSession.homeserverCapabilities.threePidChanges || self.mainSession.homeserverCapabilities.threePidChanges.enabled) { if (BWIBuildSettings.shared.settingsScreenAllowAddingEmailThreepids && BuildSettings.settingsScreenAllowAddingEmailThreepids && // If the threePidChanges is nil we assume the capability to be true (!self.mainSession.homeserverCapabilities.threePidChanges || self.mainSession.homeserverCapabilities.threePidChanges.enabled)) { [sectionUserSettings addRowWithTag:USER_SETTINGS_ADD_EMAIL_INDEX]; } if (BWIBuildSettings.shared.settingsScreenAllowAddingPhoneThreepids) { [sectionUserSettings addRowWithTag:USER_SETTINGS_ADD_PHONENUMBER_INDEX]; } if (BWIBuildSettings.shared.settingsScreenShowThreepidExplanatory) { NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart1] attributes:@{}]; [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart2] attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.tintColor}]]; [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[VectorL10n settingsThreePidsManagementInformationPart3] attributes:@{}]]; sectionUserSettings.attributedFooterTitle = attributedString; } } sectionUserSettings.headerTitle = [VectorL10n settingsUserSettings]; [tmpSections addObject:sectionUserSettings]; if (BWIBuildSettings.shared.bwiShowNewFeatures) { Section *sectionNewFeatures = [Section sectionWithTag:SECTION_TAG_NEW_FEATURES]; [sectionNewFeatures addRowWithTag:SHOW_NEW_FEATURES]; sectionNewFeatures.headerTitle = BWIL10n.bwiSettingsNewFeaturesHeader; [tmpSections addObject:sectionNewFeatures]; } NSString *manageAccountURL = self.mainSession.homeserverWellknown.authentication.account; if (BWIBuildSettings.shared.isOIDCEnabled && manageAccountURL) { Section *account = [Section sectionWithTag: SECTION_TAG_ACCOUNT]; [account addRowWithTag:ACCOUNT_MANAGE_INDEX]; account.headerTitle = [VectorL10n settingsManageAccountTitle]; account.footerTitle = [VectorL10n settingsManageAccountDescription:manageAccountURL]; [tmpSections addObject:account]; } if (BWIBuildSettings.shared.settingsScreenShowConfirmMediaSize && BuildSettings.settingsScreenShowConfirmMediaSize) { Section *sectionMedia = [Section sectionWithTag:SECTION_TAG_SENDING_MEDIA]; [sectionMedia addRowWithTag:SENDING_MEDIA_CONFIRM_SIZE]; sectionMedia.headerTitle = [VectorL10n settingsSendingMedia]; sectionMedia.footerTitle = VectorL10n.settingsConfirmMediaSizeDescription; [tmpSections addObject:sectionMedia]; } if (BWIBuildSettings.shared.settingsScreenShowLinkPreviews) { Section *sectionLinks = [Section sectionWithTag:SECTION_TAG_LINKS]; [sectionLinks addRowWithTag:LINKS_SHOW_URL_PREVIEWS_INDEX]; sectionLinks.headerTitle = [VectorL10n settingsLinks]; sectionLinks.footerTitle = VectorL10n.settingsShowUrlPreviewsDescription; [tmpSections addObject:sectionLinks]; } Section *sectionSecurity = [Section sectionWithTag:SECTION_TAG_SECURITY]; [sectionSecurity addRowWithTag:SECURITY_BUTTON_INDEX]; if (RiotSettings.shared.enableNewSessionManager || BWIBuildSettings.shared.enableNewSessionManagerByDefault) { // NOTE: Add device manager entry point in the security section atm for debug purpose [sectionSecurity addRowWithTag:DEVICE_MANAGER_INDEX]; } sectionSecurity.headerTitle = [VectorL10n settingsSecurity]; [tmpSections addObject:sectionSecurity]; Section *sectionNotificationSettings = [Section sectionWithTag:SECTION_TAG_NOTIFICATIONS]; [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX]; if (RiotSettings.shared.settingsScreenShowSystemSettingsOption) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_SYSTEM_SETTINGS]; } if (BWIBuildSettings.shared.bwiSettingsShowInAppNotifications) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_SHOW_IN_APP_INDEX]; } if (RiotSettings.shared.settingsScreenShowNotificationDecodedContentOption) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT]; } if (BWIBuildSettings.shared.bwiShowPinnedNotificationSettings) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX]; [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX]; } if (RiotSettings.shared.settingsNotificationsShowDefault) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_DEFAULT_SETTINGS_INDEX]; } if (RiotSettings.shared.settingsNotificationsShowMentions) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_MENTION_AND_KEYWORDS_SETTINGS_INDEX]; } if (BWIBuildSettings.shared.bwiNotificationTimes) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_NOTIFICATION_TIMES_INDEX]; } if (RiotSettings.shared.settingsNotificationsShowAdvanced) { [sectionNotificationSettings addRowWithTag:NOTIFICATION_SETTINGS_OTHER_SETTINGS_INDEX]; } sectionNotificationSettings.headerTitle = [VectorL10n settingsNotifications]; [tmpSections addObject:sectionNotificationSettings]; if (BWIBuildSettings.shared.allowVoIPUsage && BWIBuildSettings.shared.stunServerFallbackUrlString && RiotSettings.shared.settingsScreenShowEnableStunServerFallback) { Section *sectionCalls = [Section sectionWithTag:SECTION_TAG_CALLS]; sectionCalls.headerTitle = [VectorL10n settingsCallsSettings]; // Remove "stun:" NSString* stunFallbackHost = [BWIBuildSettings.shared.stunServerFallbackUrlString componentsSeparatedByString:@":"].lastObject; sectionCalls.footerTitle = [VectorL10n settingsCallsStunServerFallbackDescription:stunFallbackHost]; [sectionCalls addRowWithTag:CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX]; [tmpSections addObject:sectionCalls]; } if (BWIBuildSettings.shared.settingsScreenShowDiscoverySettings) { Section *sectionDiscovery = [Section sectionWithTag:SECTION_TAG_DISCOVERY]; NSInteger count = self.settingsDiscoveryTableViewSection.numberOfRows; for (NSInteger index = 0; index < count; index++) { [sectionDiscovery addRowWithTag:index]; } sectionDiscovery.headerTitle = [VectorL10n settingsDiscoverySettings]; sectionDiscovery.attributedFooterTitle = self.settingsDiscoveryTableViewSection.attributedFooterTitle; [tmpSections addObject:sectionDiscovery]; } if (BWIBuildSettings.shared.settingsScreenAllowIdentityServerConfig) { Section *sectionIdentityServer = [Section sectionWithTag:SECTION_TAG_IDENTITY_SERVER]; [sectionIdentityServer addRowWithTag:IDENTITY_SERVER_INDEX]; sectionIdentityServer.headerTitle = [VectorL10n settingsIdentityServerSettings]; sectionIdentityServer.footerTitle = account.mxSession.identityService.identityServer ? VectorL10n.settingsIdentityServerDescription : VectorL10n.settingsIdentityServerNoIsDescription; [tmpSections addObject:sectionIdentityServer]; } if (BWIBuildSettings.shared.allowLocalContactsAccess) { Section *sectionLocalContacts = [Section sectionWithTag:SECTION_TAG_LOCAL_CONTACTS]; [sectionLocalContacts addRowWithTag:LOCAL_CONTACTS_SYNC_INDEX]; if (MXKAppSettings.standardAppSettings.syncLocalContacts) { [sectionLocalContacts addRowWithTag:LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX]; } NSString *headerTitle = UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone ? VectorL10n.settingsPhoneContacts : VectorL10n.settingsContacts; sectionLocalContacts.headerTitle = headerTitle; sectionLocalContacts.footerTitle = VectorL10n.settingsContactsEnableSyncDescription; [tmpSections addObject:sectionLocalContacts]; } MXSession *session = [AppDelegate theDelegate].mxSessions.firstObject; if (session.ignoredUsers.count) { Section *sectionIgnoredUsers = [Section sectionWithTag:SECTION_TAG_IGNORED_USERS]; if (BWIBuildSettings.shared.bwiBetterIgnoredUsers) { [sectionIgnoredUsers addRowWithTag:IGNORED_USERS_INDEX]; } else { for (NSInteger index = 0; index < session.ignoredUsers.count; index++) { [sectionIgnoredUsers addRowWithTag:index]; } } sectionIgnoredUsers.headerTitle = [VectorL10n settingsIgnoredUsers]; [tmpSections addObject:sectionIgnoredUsers]; } if (RiotSettings.shared.matrixApps) { Section *sectionIntegrations = [Section sectionWithTag:SECTION_TAG_INTEGRATIONS]; [sectionIntegrations addRowWithTag:INTEGRATIONS_INDEX]; sectionIntegrations.headerTitle = [VectorL10n settingsIntegrations]; NSString *integrationManager = [WidgetManager.sharedManager configForUser:session.myUser.userId].apiUrl; NSString *integrationManagerDomain = [NSURL URLWithString:integrationManager].host; sectionIntegrations.footerTitle = [VectorL10n settingsIntegrationsAllowDescription:integrationManagerDomain]; [tmpSections addObject:sectionIntegrations]; } Section *sectionUserInterface = [Section sectionWithTag:SECTION_TAG_USER_INTERFACE]; sectionUserInterface.headerTitle = [VectorL10n settingsUserInterface]; //[sectionUserInterface addRowWithTag:USER_INTERFACE_LANGUAGE_INDEX]; [sectionUserInterface addRowWithTag:USER_INTERFACE_THEME_INDEX]; if (BWIBuildSettings.shared.bwiPersonalNotesVisibilityInSettings) { [sectionUserInterface addRowWithTag:PERSONAL_NOTES_INDEX]; } sectionUserInterface = [self updateTimeLine:sectionUserInterface]; [tmpSections addObject: sectionUserInterface]; if (BWIBuildSettings.shared.bwiShowTimelineSettings) { Section *sectionTimeline = [Section sectionWithTag:SECTION_TAG_TIMELINE]; sectionTimeline.headerTitle = VectorL10n.settingsTimeline; if (BuildSettings.roomScreenAllowTimelineStyleConfiguration) { [sectionTimeline addRowWithTag:TIMELINE_STYLE_INDEX]; } [sectionTimeline addRowWithTag:TIMELINE_SHOW_REDACTIONS_IN_ROOM_HISTORY_INDEX]; [sectionTimeline addRowWithTag:TIMELINE_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX]; [tmpSections addObject:sectionTimeline]; } if(BuildSettings.settingsScreenPresenceAllowConfiguration) { Section *sectionPresence = [Section sectionWithTag:SECTION_TAG_PRESENCE]; [sectionPresence addRowWithTag:PRESENCE_OFFLINE_MODE]; sectionPresence.headerTitle = VectorL10n.settingsPresence; sectionPresence.footerTitle = VectorL10n.settingsPresenceOfflineModeDescription; [tmpSections addObject:sectionPresence]; } // bwi: #5358 the user should be able to switch back to the old composer if (BWIBuildSettings.shared.bwiWYSIWYGVisibilityInSettings) { [sectionUserInterface addRowWithTag:WYSIWYG_COMPOSER_INDEX]; }; Section *sectionAdvanced = [Section sectionWithTag:SECTION_TAG_ADVANCED]; sectionAdvanced.headerTitle = [VectorL10n settingsAdvanced]; if (BWIBuildSettings.shared.settingsScreenShowAdvancedSettings) { Section *sectionAdvanced = [Section sectionWithTag:SECTION_TAG_ADVANCED]; sectionAdvanced.headerTitle = [VectorL10n settingsAdvanced]; if (BWIBuildSettings.shared.settingsScreenAllowChangingCrashUsageDataSettings) { [sectionAdvanced addRowWithTag:ADVANCED_CRASH_REPORT_INDEX]; } if (BWIBuildSettings.shared.settingsScreenAllowChangingRageshakeSettings) { [sectionAdvanced addRowWithTag:ADVANCED_ENABLE_RAGESHAKE_INDEX]; } [sectionAdvanced addRowWithTag:ADVANCED_MARK_ALL_AS_READ_INDEX]; [sectionAdvanced addRowWithTag:ADVANCED_CLEAR_CACHE_INDEX]; if (BWIBuildSettings.shared.settingsScreenAllowBugReportingManually) { [sectionAdvanced addRowWithTag:ADVANCED_REPORT_BUG_INDEX]; } [tmpSections addObject:sectionAdvanced]; } Section *sectionAbout = [Section sectionWithTag:SECTION_TAG_ABOUT]; // bwi: add netiquette if feature is enabled and available if ([self isNetiquetteSettingsShown]) { [sectionAbout addRowWithTag:ABOUT_NETIQUETTE_INDEX]; } [sectionAbout addRowWithTag:ABOUT_VERSION_INDEX]; [sectionAbout addRowWithTag:ABOUT_OLM_VERSION_INDEX]; // bwi 2098 - show element base version if ([BWIBuildSettings.shared showElementBaseVersion]) { [sectionAbout addRowWithTag:ABOUT_ELEMENT_VERSION_INDEX]; } if (BWIBuildSettings.shared.settingsScreenShowSupportSetting) { [sectionAbout addRowWithTag:ABOUT_SUPPORT_INDEX]; } [sectionAbout addRowWithTag:ABOUT_COPYRIGHT_INDEX]; if (BWIBuildSettings.shared.applicationTermsConditionsUrlString.length) { [sectionAbout addRowWithTag:ABOUT_ACCEPTABLE_USE_INDEX]; } if (self.mainSession.homeserverWellknown.dataPrivacyURL != nil && BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink) { [sectionAbout addRowWithTag:ABOUT_PRIVACY_INDEX]; } else if (BWIBuildSettings.shared.applicationPrivacyPolicyUrlString.length && !BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink) { [sectionAbout addRowWithTag:ABOUT_PRIVACY_INDEX]; } // bwi 4772 - show accessibility declaration if (BWIBuildSettings.shared.bwiShowAccessibilityDeclaration) { [sectionAbout addRowWithTag:ABOUT_ACCESSIBILITY_DECLARATION_INDEX]; } [sectionAbout addRowWithTag:ABOUT_THIRD_PARTY_INDEX]; // bwi #4682 - Show Imprint if ([self.mainSession.homeserverWellknown imprintURL]) { [sectionAbout addRowWithTag:ABOUT_IMPRINT_INDEX]; } sectionAbout.headerTitle = VectorL10n.settingsAbout; [sectionAbout addRowWithTag:ABOUT_MARK_ALL_AS_READ_INDEX]; [sectionAbout addRowWithTag:ABOUT_CLEAR_CACHE_INDEX]; if (BWIBuildSettings.shared.bwiShowDeveloperSettings) { Section *sectionDeveloper = [Section sectionWithTag:SECTION_TAG_DEVELOPER]; [sectionDeveloper addRowWithTag:DEVELOPER_SHOW_SETTINGS_INDEX]; sectionDeveloper.headerTitle = BWIL10n.bwiSettingsDeveloper; [tmpSections addObject:sectionDeveloper]; } if (BWIBuildSettings.shared.settingsScreenShowAdvancedSettings) { sectionAbout.footerTitle = [self buildAboutSectionFooterTitleWithAccount:account]; } [tmpSections addObject:sectionAbout]; if (BWIBuildSettings.shared.settingsScreenShowLabSettings) { Section *sectionLabs = [Section sectionWithTag:SECTION_TAG_LABS]; if (!BWIBuildSettings.shared.enableNewSessionManagerByDefault) { [sectionLabs addRowWithTag:LABS_ENABLE_NEW_SESSION_MANAGER]; } /* bwi: disabled for our apps [sectionLabs addRowWithTag:LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX]; [sectionLabs addRowWithTag:LABS_ENABLE_THREADS_INDEX]; [sectionLabs addRowWithTag:LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS]; [sectionLabs addRowWithTag:LABS_ENABLE_NEW_CLIENT_INFO_FEATURE]; [sectionLabs addRowWithTag:LABS_ENABLE_LIVE_LOCATION_SHARING]; */ /* bwi: #4941 WYSIWYG is now live if (@available(iOS 15.0, *)) { [sectionLabs addRowWithTag:LABS_ENABLE_WYSIWYG_COMPOSER]; } */ /* bwi: #4941 disabled for our apps { [sectionLabs addRowWithTag:LABS_ENABLE_VOICE_BROADCAST]; */ // bwi: #5203 add federation feature [sectionLabs addRowWithTag:LABS_ENABLE_FEDERATION]; sectionLabs.headerTitle = [VectorL10n settingsLabs]; if (sectionLabs.hasAnyRows) { [tmpSections addObject:sectionLabs]; } } if (BWIBuildSettings.shared.settingsScreenAllowDeactivatingAccount && !BWIBuildSettings.shared.isOIDCEnabled && BuildSettings.settingsScreenAllowDeactivatingAccount && !self.mainSession.homeserverWellknown.authentication) { Section *sectionDeactivate = [Section sectionWithTag:SECTION_TAG_DEACTIVATE_ACCOUNT]; [sectionDeactivate addRowWithTag:0]; sectionDeactivate.headerTitle = [VectorL10n settingsDeactivateMyAccount]; [tmpSections addObject:sectionDeactivate]; } Section *sectionSignOut = [Section sectionWithTag:SECTION_TAG_SIGN_OUT]; [sectionSignOut addRowWithTag:0]; [tmpSections addObject:sectionSignOut]; // update sections _tableViewSections.sections = tmpSections; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.navigationItem.title = [VectorL10n settingsTitle]; [self vc_removeBackTitle]; [self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier]]; [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; [self.tableView registerClass:MXKTableViewCellWithLabelAndMXKImageView.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier]]; [self.tableView registerClass:BWITableViewCellWithLabelAndMXKImageView.class forCellReuseIdentifier:[BWITableViewCellWithLabelAndMXKImageView defaultReuseIdentifier]]; // bwi: 5610 [self.tableView registerClass:TableViewCellWithPhoneNumberTextField.class forCellReuseIdentifier:[TableViewCellWithPhoneNumberTextField defaultReuseIdentifier]]; [self.tableView registerNib:MXKTableViewCellWithTextView.nib forCellReuseIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]]; [self.tableView registerNib:SectionFooterView.nib forHeaderFooterViewReuseIdentifier:[SectionFooterView defaultReuseIdentifier]]; // Enable self sizing cells and footers self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = 50; self.tableView.sectionFooterHeight = UITableViewAutomaticDimension; self.tableView.estimatedSectionFooterHeight = 50; // bwi: #5799 show alert if user has changed the avatar without saving it self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[BWIL10n settingsCustomBackButton] style:UIBarButtonItemStylePlain target:self action:@selector(customBackButtonAction)]; MXWeakify(self); // Add observer to handle removed accounts removedAccountObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidRemoveAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { MXStrongifyAndReturnIfNil(self); if ([MXKAccountManager sharedManager].accounts.count) { // Refresh table to remove this account [self refreshSettings]; } }]; // Add observer to handle accounts update accountUserInfoObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountUserInfoDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { MXStrongifyAndReturnIfNil(self); [self stopActivityIndicator]; [self refreshSettings]; }]; // Add observer to push settings pushInfoUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountAPNSActivityDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { MXStrongifyAndReturnIfNil(self); [self stopActivityIndicator]; [self refreshSettings]; }]; [self registerAccountDataDidChangeIdentityServerNotification]; // Add each matrix session, to update the view controller appearance according to mx sessions state NSArray *sessions = [AppDelegate theDelegate].mxSessions; for (MXSession *mxSession in sessions) { [self addMatrixSession:mxSession]; } [self setupDiscoverySection]; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(onSave:)]; self.navigationItem.rightBarButtonItem.accessibilityIdentifier=@"SettingsVCNavBarSaveButton"; // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { MXStrongifyAndReturnIfNil(self); [self userInterfaceThemeDidChange]; }]; [self userInterfaceThemeDidChange]; _tableViewSections = [TableViewSections new]; _tableViewSections.delegate = self; [self updateSections]; } - (void)userInterfaceThemeDidChange { [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); self.view.backgroundColor = self.tableView.backgroundColor; self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor; if (self.tableView.dataSource) { [self refreshSettings]; } [self setNeedsStatusBarAppearanceUpdate]; } - (UIStatusBarStyle)preferredStatusBarStyle { return ThemeService.shared.theme.statusBarStyle; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)destroy { // Release the potential pushed view controller [self releasePushedViewController]; if (kThemeServiceDidChangeThemeNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; kThemeServiceDidChangeThemeNotificationObserver = nil; } if (isSavingInProgress || is3PIDBindingInProgress) { __weak typeof(self) weakSelf = self; onReadyToDestroyHandler = ^() { if (weakSelf) { typeof(self) self = weakSelf; [self destroy]; } }; } else { // Dispose all resources [self reset]; [super destroy]; } identityServerSettingsCoordinatorBridgePresenter = nil; } - (void)onMatrixSessionStateDidChange:(NSNotification *)notif { MXSession *mxSession = notif.object; // Check whether the concerned session is a new one which is not already associated with this view controller. if (mxSession.state == MXSessionStateInitialised && [self.mxSessions indexOfObject:mxSession] != NSNotFound) { // Store this new session [self addMatrixSession:mxSession]; } else { [super onMatrixSessionStateDidChange:notif]; } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.screenTracker trackScreen]; // Refresh display [self refreshSettings]; // Refresh linked emails and phone numbers in parallel [self loadAccount3PIDs]; MXWeakify(self); // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { MXStrongifyAndReturnIfNil(self); [self.tableView setContentOffset:CGPointMake(-self.tableView.adjustedContentInset.left, -self.tableView.adjustedContentInset.top) animated:YES]; }]; newPhoneNumberCountryPicker = nil; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // Release the potential pushed view controller [self releasePushedViewController]; [self.settingsDiscoveryTableViewSection reload]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (currentAlert) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } if (notificationCenterWillUpdateObserver) { [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterWillUpdateObserver]; notificationCenterWillUpdateObserver = nil; } if (notificationCenterDidUpdateObserver) { [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidUpdateObserver]; notificationCenterDidUpdateObserver = nil; } if (notificationCenterDidFailObserver) { [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidFailObserver]; notificationCenterDidFailObserver = nil; } if (kAppDelegateDidTapStatusBarNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; kAppDelegateDidTapStatusBarNotificationObserver = nil; } } #pragma mark - Internal methods - (void)pushViewController:(UIViewController*)viewController { // Keep ref on pushed view controller pushedViewController = viewController; [self.navigationController pushViewController:viewController animated:YES]; } - (void)releasePushedViewController { if (pushedViewController) { if ([pushedViewController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController*)pushedViewController; for (id subViewController in navigationController.viewControllers) { if ([subViewController respondsToSelector:@selector(destroy)]) { [subViewController destroy]; } } } else if ([pushedViewController respondsToSelector:@selector(destroy)]) { [(id)pushedViewController destroy]; } pushedViewController = nil; } } - (void)dismissKeyboard { [newEmailTextField resignFirstResponder]; [newPhoneNumberCell.mxkTextField resignFirstResponder]; } - (void)reset { // Remove observers if (removedAccountObserver) { [[NSNotificationCenter defaultCenter] removeObserver:removedAccountObserver]; removedAccountObserver = nil; } if (accountUserInfoObserver) { [[NSNotificationCenter defaultCenter] removeObserver:accountUserInfoObserver]; accountUserInfoObserver = nil; } if (pushInfoUpdateObserver) { [[NSNotificationCenter defaultCenter] removeObserver:pushInfoUpdateObserver]; pushInfoUpdateObserver = nil; } [[NSNotificationCenter defaultCenter] removeObserver:self]; onReadyToDestroyHandler = nil; } -(void)setNewEmailEditingEnabled:(BOOL)newEmailEditingEnabled { if (newEmailEditingEnabled != _newEmailEditingEnabled) { // Update the flag _newEmailEditingEnabled = newEmailEditingEnabled; if (!newEmailEditingEnabled) { // Dismiss the keyboard [newEmailTextField resignFirstResponder]; newEmailTextField = nil; } dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView beginUpdates]; // Refresh the corresponding table view cell with animation NSIndexPath *addEmailIndexPath = [self.tableViewSections exactIndexPathForRowTag:USER_SETTINGS_ADD_EMAIL_INDEX sectionTag:SECTION_TAG_USER_SETTINGS]; if (addEmailIndexPath) { [self.tableView reloadRowsAtIndexPaths:@[addEmailIndexPath] withRowAnimation:UITableViewRowAnimationFade]; } [self.tableView endUpdates]; }); } } -(void)setNewPhoneEditingEnabled:(BOOL)newPhoneEditingEnabled { if (newPhoneEditingEnabled != _newPhoneEditingEnabled) { // Update the flag _newPhoneEditingEnabled = newPhoneEditingEnabled; if (!newPhoneEditingEnabled) { // Dismiss the keyboard [newPhoneNumberCell.mxkTextField resignFirstResponder]; newPhoneNumberCell = nil; } dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView beginUpdates]; // Refresh the corresponding table view cell with animation NSIndexPath *addPhoneIndexPath = [self.tableViewSections exactIndexPathForRowTag:USER_SETTINGS_ADD_PHONENUMBER_INDEX sectionTag:SECTION_TAG_USER_SETTINGS]; if (addPhoneIndexPath) { [self.tableView reloadRowsAtIndexPaths:@[addPhoneIndexPath] withRowAnimation:UITableViewRowAnimationFade]; } [self.tableView endUpdates]; }); } } - (void)showValidationEmailDialogWithMessage:(NSString*)message for3PidAddSession:(MX3PidAddSession*)threePidAddSession threePidAddManager:(MX3PidAddManager*)threePidAddManager authenticationParameters:(NSDictionary*)authenticationParameters { MXWeakify(self); [currentAlert dismissViewControllerAnimated:NO completion:nil]; UIAlertController *validationAlert = [UIAlertController alertControllerWithTitle:[VectorL10n accountEmailValidationTitle] message:message preferredStyle:UIAlertControllerStyleAlert]; [validationAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; [self stopActivityIndicator]; // Reset new email adding self.newEmailEditingEnabled = NO; }]]; [validationAlert addAction:[UIAlertAction actionWithTitle:[BWIL10n continue] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); [self tryFinaliseAddEmailSession:threePidAddSession withAuthenticationParameters:authenticationParameters threePidAddManager:threePidAddManager]; }]]; [validationAlert mxk_setAccessibilityIdentifier:@"SettingsVCEmailValidationAlert"]; [self presentViewController:validationAlert animated:YES completion:nil]; currentAlert = validationAlert; } - (void)tryFinaliseAddEmailSession:(MX3PidAddSession*)threePidAddSession withAuthenticationParameters:(NSDictionary*)authParams threePidAddManager:(MX3PidAddManager*)threePidAddManager { self->is3PIDBindingInProgress = YES; [threePidAddManager tryFinaliseAddEmailSession:threePidAddSession authParams:authParams success:^{ self->is3PIDBindingInProgress = NO; // Check whether destroy has been called during email binding if (self->onReadyToDestroyHandler) { // Ready to destroy self->onReadyToDestroyHandler(); self->onReadyToDestroyHandler = nil; } else { self->currentAlert = nil; [self stopActivityIndicator]; // Reset new email adding self.newEmailEditingEnabled = NO; // Update linked emails [self loadAccount3PIDs]; } } failure:^(NSError * _Nonnull error) { MXLogDebug(@"[SettingsViewController] tryFinaliseAddEmailSession: Failed to bind email"); MXError *mxError = [[MXError alloc] initWithNSError:error]; if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringForbidden]) { MXLogDebug(@"[SettingsViewController] tryFinaliseAddEmailSession: Wrong credentials"); // Ask password again UIAlertController *passwordPrompt = [UIAlertController alertControllerWithTitle:nil message:[VectorL10n settingsAdd3pidInvalidPasswordMessage] preferredStyle:UIAlertControllerStyleAlert]; MXWeakify(self); [passwordPrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; [self showAuthenticationIfNeededForAdding:kMX3PIDMediumEmail withSession:self.mainSession completion:^(NSDictionary *authParams) { [self tryFinaliseAddEmailSession:threePidAddSession withAuthenticationParameters:authParams threePidAddManager:threePidAddManager]; }]; }]]; [self presentViewController:passwordPrompt animated:YES completion:nil]; self->currentAlert = passwordPrompt; return; } self->is3PIDBindingInProgress = NO; // Check whether destroy has been called during email binding if (self->onReadyToDestroyHandler) { // Ready to destroy self->onReadyToDestroyHandler(); self->onReadyToDestroyHandler = nil; } else { self->currentAlert = nil; // Display the same popup again if the error is M_THREEPID_AUTH_FAILED MXError *mxError = [[MXError alloc] initWithNSError:error]; if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringThreePIDAuthFailed]) { [self showValidationEmailDialogWithMessage:[VectorL10n accountEmailValidationError] for3PidAddSession:threePidAddSession threePidAddManager:threePidAddManager authenticationParameters:authParams]; } else { [self stopActivityIndicator]; // Notify user NSString *myUserId = self.mainSession.myUser.userId; // TODO: Hanlde multi-account [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; } } }]; } - (void)showValidationMsisdnDialogWithMessage:(NSString*)message for3PidAddSession:(MX3PidAddSession*)threePidAddSession threePidAddManager:(MX3PidAddManager*)threePidAddManager authenticationParameters:(NSDictionary*)authenticationParameters { MXWeakify(self); [currentAlert dismissViewControllerAnimated:NO completion:nil]; UIAlertController *validationAlert = [UIAlertController alertControllerWithTitle:[VectorL10n accountMsisdnValidationTitle] message:message preferredStyle:UIAlertControllerStyleAlert]; [validationAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; [self stopActivityIndicator]; // Reset new phone adding self.newPhoneEditingEnabled = NO; }]]; [validationAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { textField.secureTextEntry = NO; textField.placeholder = nil; textField.keyboardType = UIKeyboardTypeDecimalPad; }]; [validationAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n submit] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); NSString *smsCode = [self->currentAlert textFields].firstObject.text; self->currentAlert = nil; if (smsCode.length) { [self finaliseAddPhoneNumberSession:threePidAddSession withToken:smsCode andAuthenticationParameters:authenticationParameters message:message threePidAddManager:threePidAddManager]; } else { // Ask again the sms token [self showValidationMsisdnDialogWithMessage:message for3PidAddSession:threePidAddSession threePidAddManager:threePidAddManager authenticationParameters:authenticationParameters]; } }]]; [validationAlert mxk_setAccessibilityIdentifier: @"SettingsVCMsisdnValidationAlert"]; [self presentViewController:validationAlert animated:YES completion:nil]; currentAlert = validationAlert; } - (void)finaliseAddPhoneNumberSession:(MX3PidAddSession*)threePidAddSession withToken:(NSString*)token andAuthenticationParameters:(NSDictionary*)authParams message:(NSString*)message threePidAddManager:(MX3PidAddManager*)threePidAddManager { self->is3PIDBindingInProgress = YES; [threePidAddManager finaliseAddPhoneNumberSession:threePidAddSession withToken:token authParams:authParams success:^{ self->is3PIDBindingInProgress = NO; // Check whether destroy has been called during the binding if (self->onReadyToDestroyHandler) { // Ready to destroy self->onReadyToDestroyHandler(); self->onReadyToDestroyHandler = nil; } else { [self stopActivityIndicator]; // Reset new phone adding self.newPhoneEditingEnabled = NO; // Update linked 3pids [self loadAccount3PIDs]; } } failure:^(NSError * _Nonnull error) { MXLogDebug(@"[SettingsViewController] finaliseAddPhoneNumberSession: Failed to submit the sms token"); MXError *mxError = [[MXError alloc] initWithNSError:error]; if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringForbidden]) { MXLogDebug(@"[SettingsViewController] finaliseAddPhoneNumberSession: Wrong authentication credentials"); // Ask password again UIAlertController *passwordPrompt = [UIAlertController alertControllerWithTitle:nil message:[VectorL10n settingsAdd3pidInvalidPasswordMessage] preferredStyle:UIAlertControllerStyleAlert]; MXWeakify(self); [passwordPrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; [self showAuthenticationIfNeededForAdding:kMX3PIDMediumMSISDN withSession:self.mainSession completion:^(NSDictionary *authParams) { [self finaliseAddPhoneNumberSession:threePidAddSession withToken:token andAuthenticationParameters:authParams message:message threePidAddManager:threePidAddManager]; }]; }]]; [self presentViewController:passwordPrompt animated:YES completion:nil]; self->currentAlert = passwordPrompt; return; } self->is3PIDBindingInProgress = NO; // Check whether destroy has been called during phone binding if (self->onReadyToDestroyHandler) { // Ready to destroy self->onReadyToDestroyHandler(); self->onReadyToDestroyHandler = nil; } else { // Ignore connection cancellation error if (([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled)) { [self stopActivityIndicator]; return; } // Alert user NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; if (!title) { if (msg) { title = msg; msg = nil; } else { title = [VectorL10n error]; } } UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; MXWeakify(self); [errorAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; // Ask again the sms token [self showValidationMsisdnDialogWithMessage:message for3PidAddSession:threePidAddSession threePidAddManager:threePidAddManager authenticationParameters:authParams]; }]]; [errorAlert mxk_setAccessibilityIdentifier: @"SettingsVCErrorAlert"]; [self presentViewController:errorAlert animated:YES completion:nil]; self->currentAlert = errorAlert; } }]; } - (void)loadAccount3PIDs { // Refresh the account 3PIDs list MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; [account load3PIDs:^{ NSArray *thirdPartyIdentifiers = account.threePIDs ?: @[]; [self.settingsDiscoveryViewModel updateWithThirdPartyIdentifiers:thirdPartyIdentifiers]; // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). // Note: The use of 'reloadData' handles the case where the account has been logged out. [self refreshSettings]; } failure:^(NSError *error) { // Display the data that has been loaded last time // Note: The use of 'reloadData' handles the case where the account has been logged out. [self refreshSettings]; }]; } - (void)editNewEmailTextField { if (newEmailTextField && ![newEmailTextField becomeFirstResponder]) { // Retry asynchronously dispatch_async(dispatch_get_main_queue(), ^{ [self editNewEmailTextField]; }); } } - (void)editNewPhoneNumberTextField { if (newPhoneNumberCell && ![newPhoneNumberCell.mxkTextField becomeFirstResponder]) { // Retry asynchronously dispatch_async(dispatch_get_main_queue(), ^{ [self editNewPhoneNumberTextField]; }); } } - (void)refreshSettings { // Check whether a text input is currently edited keepNewEmailEditing = newEmailTextField ? newEmailTextField.isFirstResponder : NO; keepNewPhoneNumberEditing = newPhoneNumberCell ? newPhoneNumberCell.mxkTextField.isFirstResponder : NO; // Trigger a full table reloadData [self updateSections]; // Restore the previous edited field if (keepNewEmailEditing) { [self editNewEmailTextField]; keepNewEmailEditing = NO; } else if (keepNewPhoneNumberEditing) { [self editNewPhoneNumberTextField]; keepNewPhoneNumberEditing = NO; } // Update notification access [self refreshSystemNotificationSettings]; [[MXKAccountManager sharedManager].activeAccounts.firstObject loadCurrentPusher:nil failure:nil]; } - (void)refreshSystemNotificationSettings { MXWeakify(self); // Get the system notification settings to check authorization status and configuration. [UNUserNotificationCenter.currentNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { dispatch_async(dispatch_get_main_queue(), ^{ MXStrongifyAndReturnIfNil(self); self.systemNotificationSettings = settings; [self.tableView reloadData]; }); }]; } - (void)formatNewPhoneNumber { if (newPhoneNumber) { NSString *formattedNumber = [[NBPhoneNumberUtil sharedInstance] format:newPhoneNumber numberFormat:NBEPhoneNumberFormatINTERNATIONAL error:nil]; NSString *prefix = newPhoneNumberCell.mxkLabel.text; if ([formattedNumber hasPrefix:prefix]) { // Format the display phone number newPhoneNumberCell.mxkTextField.text = [formattedNumber substringFromIndex:prefix.length]; } } } - (void)setupDiscoverySection { MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; NSArray *thirdPartyIdentifiers = account.threePIDs ?: @[]; SettingsDiscoveryViewModel *viewModel = [[SettingsDiscoveryViewModel alloc] initWithSession:self.mainSession thirdPartyIdentifiers:thirdPartyIdentifiers]; viewModel.coordinatorDelegate = self; SettingsDiscoveryTableViewSection *discoverySection = [[SettingsDiscoveryTableViewSection alloc] initWithViewModel:viewModel]; discoverySection.delegate = self; self.settingsDiscoveryViewModel = viewModel; self.settingsDiscoveryTableViewSection = discoverySection; } - (UserInteractiveAuthenticationService*)createUserInteractiveAuthenticationService { MXSession *session = self.mainSession; UserInteractiveAuthenticationService *userInteractiveAuthenticationService; if (session) { userInteractiveAuthenticationService = [[UserInteractiveAuthenticationService alloc] initWithSession:session]; } return userInteractiveAuthenticationService; } - (void)scrollToDiscoverySection { // settingsDiscoveryTableViewSection is a dynamic section, so check number of rows before scroll to avoid crashes if (self.settingsDiscoveryTableViewSection.numberOfRows > 0) { NSIndexPath *discoveryIndexPath = [_tableViewSections exactIndexPathForRowTag:0 sectionTag:SECTION_TAG_DISCOVERY]; if (discoveryIndexPath) { [self.tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; } } else { // this won't be precise in scroll location, but seems the best option for now NSIndexPath *discoveryIndexPath = [_tableViewSections nearestIndexPathForRowTag:0 sectionTag:SECTION_TAG_DISCOVERY]; if (discoveryIndexPath) { [self.tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; } } } - (void)scrollToUserSettingsSection { NSIndexPath *discoveryIndexPath = [_tableViewSections exactIndexPathForRowTag:USER_SETTINGS_ADD_EMAIL_INDEX sectionTag:SECTION_TAG_USER_SETTINGS]; if (discoveryIndexPath) { [self.tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; } } - (NSString*)buildAboutSectionFooterTitleWithAccount:(MXKAccount*)account { NSMutableString *footerText = [NSMutableString new]; AppInfo *appInfo = AppInfo.current; NSString *appName = appInfo.displayName; NSString *appVersion = appInfo.appVersion.bundleShortVersion; NSString *buildVersion = appInfo.appVersion.bundleVersion; NSString *appVersionInfo = [NSString stringWithFormat:@"%@ %@ (%@)", appName, appVersion, buildVersion]; NSString *loggedUserInfo = [VectorL10n settingsConfigUserId:account.mxCredentials.userId]; NSString *homeserverInfo = [VectorL10n settingsConfigHomeServer:account.mxCredentials.homeServer]; NSString *sdkVersionInfo = [NSString stringWithFormat:@"Matrix SDK %@", MatrixSDKVersion]; [footerText appendFormat:@"%@\n", loggedUserInfo]; [footerText appendFormat:@"%@\n", homeserverInfo]; [footerText appendFormat:@"%@\n", appVersionInfo]; [footerText appendFormat:@"%@\n", sdkVersionInfo]; [footerText appendFormat:@"%@", self.mainSession.crypto.version]; return [footerText copy]; } - (UITableViewCell *)buildMessageBubblesCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath*)indexPath { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsEnableRoomMessageBubbles]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.roomScreenEnableMessageBubbles; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableRoomMessageBubbles:) forControlEvents:UIControlEventTouchUpInside]; return labelAndSwitchCell; } - (UITableViewCell *)buildAutoReportDecryptionErrorsCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath*)indexPath { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableAutoReportDecryptionErrors]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableUISIAutoReporting; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableAutoReportDecryptionErrors:) forControlEvents:UIControlEventTouchUpInside]; return labelAndSwitchCell; } - (UITableViewCell *)buildLiveLocationSharingCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath*)indexPath { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableLiveLocationSharing]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableLiveLocationSharing; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableLiveLocationSharing:) forControlEvents:UIControlEventTouchUpInside]; return labelAndSwitchCell; } #pragma mark - 3Pid Add - (void)showAuthenticationIfNeededForAdding:(MX3PIDMedium)medium withSession:(MXSession*)session completion:(void (^)(NSDictionary* authParams))completion { [self startActivityIndicator]; MXWeakify(self); void (^animationCompletion)(void) = ^void () { MXStrongifyAndReturnIfNil(self); [self stopActivityIndicator]; [self.reauthenticationCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{}]; self.reauthenticationCoordinatorBridgePresenter = nil; }; NSString *title; if ([medium isEqualToString:kMX3PIDMediumMSISDN]) { title = [VectorL10n settingsAdd3pidPasswordTitleMsidsn]; } else { title = [VectorL10n settingsAdd3pidPasswordTitleEmail]; } NSString *message = [VectorL10n settingsAdd3pidPasswordMessage]; [session.matrixRestClient add3PIDOnlyWithSessionId:@"" clientSecret:[MXTools generateSecret] authParams:nil success:^{ } failure:^(NSError *error) { if (error) { [self.userInteractiveAuthenticationService authenticationSessionFromRequestError:error success:^(MXAuthenticationSession * _Nullable authenticationSession) { if (authenticationSession) { ReauthenticationCoordinatorParameters *coordinatorParameters = [[ReauthenticationCoordinatorParameters alloc] initWithSession:self.mainSession presenter:self title:title message:message authenticationSession:authenticationSession]; ReauthenticationCoordinatorBridgePresenter *reauthenticationPresenter = [ReauthenticationCoordinatorBridgePresenter new]; [reauthenticationPresenter presentWith:coordinatorParameters animated:YES success:^(NSDictionary *_Nullable authParams) { completion(authParams); } cancel:^{ animationCompletion(); } failure:^(NSError * _Nonnull error) { animationCompletion(); [[AppDelegate theDelegate] showErrorAsAlert:error]; }]; self.reauthenticationCoordinatorBridgePresenter = reauthenticationPresenter; } else { animationCompletion(); completion(nil); } } failure:^(NSError * _Nonnull error) { animationCompletion(); [[AppDelegate theDelegate] showErrorAsAlert:error]; }]; } else { animationCompletion(); [[AppDelegate theDelegate] showErrorAsAlert:error]; } }]; } #pragma mark - Segues - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Keep ref on destinationViewController [super prepareForSegue:segue sender:sender]; // FIXME add night mode } #pragma mark - UITableView data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // update the save button if there is an update [self updateSaveButtonStatus]; return _tableViewSections.sections.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { Section *sectionObject = [_tableViewSections sectionAtIndex:section]; return sectionObject.rows.count; } - (MXKTableViewCellWithLabelAndTextField*)getLabelAndTextFieldCell:(UITableView*)tableView forIndexPath:(NSIndexPath *)indexPath { MXKTableViewCellWithLabelAndTextField *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier] forIndexPath:indexPath]; cell.mxkLabelLeadingConstraint.constant = tableView.vc_separatorInset.left; cell.mxkTextFieldLeadingConstraint.constant = 16; cell.mxkTextFieldTrailingConstraint.constant = 15; cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; cell.mxkTextField.userInteractionEnabled = YES; cell.mxkTextField.borderStyle = UITextBorderStyleNone; cell.mxkTextField.textAlignment = NSTextAlignmentRight; cell.mxkTextField.textColor = ThemeService.shared.theme.textSecondaryColor; cell.mxkTextField.tintColor = ThemeService.shared.theme.tintColor; cell.mxkTextField.font = [UIFont systemFontOfSize:16]; cell.mxkTextField.placeholder = nil; cell.accessoryType = UITableViewCellAccessoryNone; cell.accessoryView = nil; cell.alpha = 1.0f; cell.userInteractionEnabled = YES; [cell layoutIfNeeded]; return cell; } - (MXKTableViewCellWithLabelAndSwitch*)getLabelAndSwitchCell:(UITableView*)tableView forIndexPath:(NSIndexPath *)indexPath { MXKTableViewCellWithLabelAndSwitch *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier] forIndexPath:indexPath]; cell.mxkLabelLeadingConstraint.constant = tableView.vc_separatorInset.left; cell.mxkSwitchTrailingConstraint.constant = 15; cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; [cell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; // Force layout before reusing a cell (fix switch displayed outside the screen) [cell layoutIfNeeded]; return cell; } - (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView { MXKTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]]; if (!cell) { cell = [[MXKTableViewCell alloc] init]; } else { cell.selectionStyle = UITableViewCellSelectionStyleDefault; cell.accessoryType = UITableViewCellAccessoryNone; cell.accessoryView = nil; } cell.textLabel.accessibilityIdentifier = nil; cell.textLabel.font = [UIFont systemFontOfSize:17]; cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; cell.contentView.backgroundColor = UIColor.clearColor; return cell; } - (MXKTableViewCell*)getWarningTableViewCell:(UITableView*)tableView { MXKTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BwiWarningCell"]; if (!cell) { cell = [[MXKTableViewCell alloc] init]; } else { cell.selectionStyle = UITableViewCellSelectionStyleDefault; cell.accessoryType = UITableViewCellAccessoryNone; cell.accessoryView = nil; } cell.textLabel.accessibilityIdentifier = nil; cell.textLabel.font = [UIFont systemFontOfSize:17]; cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; return cell; } - (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath { MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath]; textViewCell.mxkTextView.textColor = ThemeService.shared.theme.textPrimaryColor; textViewCell.mxkTextView.font = [UIFont systemFontOfSize:17]; textViewCell.mxkTextView.backgroundColor = [UIColor clearColor]; textViewCell.mxkTextViewLeadingConstraint.constant = tableView.vc_separatorInset.left; textViewCell.mxkTextViewTrailingConstraint.constant = tableView.vc_separatorInset.right; textViewCell.mxkTextView.accessibilityIdentifier = nil; return textViewCell; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSIndexPath *tagsIndexPath = [_tableViewSections tagsIndexPathFromTableViewIndexPath:indexPath]; NSInteger section = tagsIndexPath.section; NSInteger row = tagsIndexPath.row; // set the cell to a default value to avoid application crashes UITableViewCell *cell = [[UITableViewCell alloc] init]; cell.backgroundColor = [UIColor redColor]; // check if there is a valid session if (([AppDelegate theDelegate].mxSessions.count == 0) || ([MXKAccountManager sharedManager].activeAccounts.count == 0)) { // else use a default cell return cell; } MXSession* session = self.mainSession; MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; if (section == SECTION_TAG_SIGN_OUT) { MXKTableViewCellWithButton *signOutCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!signOutCell) { signOutCell = [[MXKTableViewCellWithButton alloc] init]; } else { // Fix https://github.com/vector-im/riot-ios/issues/1354 // Do not move this line in prepareForReuse because of https://github.com/vector-im/riot-ios/issues/1323 signOutCell.mxkButton.titleLabel.text = nil; } NSString* title = [VectorL10n settingsSignOut]; [signOutCell.mxkButton setTitle:title forState:UIControlStateNormal]; [signOutCell.mxkButton setTitle:title forState:UIControlStateHighlighted]; [signOutCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; signOutCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [signOutCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; [signOutCell.mxkButton addTarget:self action:@selector(onSignout:) forControlEvents:UIControlEventTouchUpInside]; signOutCell.mxkButton.accessibilityIdentifier=@"SettingsVCSignOutButton"; cell = signOutCell; } else if (section == SECTION_TAG_USER_SETTINGS) { MXMyUser* myUser = session.myUser; if (row == USER_SETTINGS_PROFILE_PICTURE_INDEX) { BWITableViewCellWithLabelAndMXKImageView *profileCell = [tableView dequeueReusableCellWithIdentifier:[BWITableViewCellWithLabelAndMXKImageView defaultReuseIdentifier] forIndexPath:indexPath]; // bwi: keep reference to cell image view bwiProfileImage = profileCell.mxkImageView; profileCell.mxkLabelLeadingConstraint.constant = tableView.vc_separatorInset.left; profileCell.mxkImageViewTrailingConstraint.constant = 10; profileCell.bwiHintLeadingConstraint.constant = tableView.vc_separatorInset.left; if(![BWIBuildSettings.shared showChangeProfilePictureHint] || ![BWIBuildSettings.shared isFederationEnabled]) { // Change constraints to get the default spacing when no hint is shown profileCell.bwiHintTopConstraint.constant = 0; profileCell.bwiHintBottomConstraint.constant = 5.5; } profileCell.mxkImageViewWidthConstraint.constant = profileCell.mxkImageViewHeightConstraint.constant = 30; profileCell.mxkImageViewDisplayBoxType = MXKTableViewCellDisplayBoxTypeCircle; if (!profileCell.mxkImageView.gestureRecognizers.count) { // tap on avatar to update it UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onProfileAvatarTap:)]; [profileCell.mxkImageView addGestureRecognizer:tap]; } profileCell.mxkLabel.text = [VectorL10n settingsProfilePicture]; profileCell.accessibilityIdentifier=@"SettingsVCProfilPictureStaticText"; profileCell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; profileCell.mxkImageView.contentMode = UIViewContentModeScaleAspectFill; if([BWIBuildSettings.shared showChangeProfilePictureHint] && [BWIBuildSettings.shared isFederationEnabled]) { profileCell.bwiHint.text = [BWIL10n settingsChangeProfileAvatarHint]; profileCell.bwiHint.textColor = ThemeService.shared.theme.textSecondaryColor; } else { profileCell.bwiHint.text = nil; } // if the user defines a new avatar if (newAvatarImage) { profileCell.mxkImageView.image = newAvatarImage; } else if (shouldRemoveAvatarImage) { UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:myUser.userId withDisplayName:myUser.displayname]; profileCell.mxkImageView.image = avatarImage; } else { UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:myUser.userId withDisplayName:myUser.displayname]; if (myUser.avatarUrl) { profileCell.mxkImageView.enableInMemoryCache = YES; [profileCell.mxkImageView setImageURI:myUser.avatarUrl withType:nil andImageOrientation:UIImageOrientationUp toFitViewSize:profileCell.mxkImageView.frame.size withMethod:MXThumbnailingMethodScale previewImage:avatarImage mediaManager:session.mediaManager]; [profileCell.mxkImageView setContentMode:UIViewContentModeScaleAspectFill]; } else { profileCell.mxkImageView.image = avatarImage; } } cell = profileCell; } else if (row == USER_SETTINGS_DISPLAYNAME_INDEX) { MXKTableViewCellWithLabelAndTextField *displaynameCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; displaynameCell.mxkLabel.text = [VectorL10n settingsDisplayName]; displaynameCell.mxkTextField.text = myUser.displayname; displaynameCell.mxkTextField.tag = row; displaynameCell.mxkTextField.delegate = self; [displaynameCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; [displaynameCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; displaynameCell.mxkTextField.accessibilityIdentifier=@"SettingsVCDisplayNameTextField"; cell = displaynameCell; } else if (row == USER_SETTINGS_PERSONAL_STATE_INDEX) { cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; } cell.textLabel.text = [BWIL10n bwiEditPersonalStateMyState]; cell.detailTextLabel.text = account.userStatusMessage; } else if (row == USER_SETTINGS_FIRST_NAME_INDEX) { MXKTableViewCellWithLabelAndTextField *firstCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; firstCell.mxkLabel.text = [VectorL10n settingsFirstName]; firstCell.mxkTextField.userInteractionEnabled = NO; cell = firstCell; } else if (row == USER_SETTINGS_SURNAME_INDEX) { MXKTableViewCellWithLabelAndTextField *surnameCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; surnameCell.mxkLabel.text = [VectorL10n settingsSurname]; surnameCell.mxkTextField.userInteractionEnabled = NO; cell = surnameCell; } else if (row >= USER_SETTINGS_EMAILS_OFFSET) { NSInteger emailIndex = row - USER_SETTINGS_EMAILS_OFFSET; MXKTableViewCellWithLabelAndTextField *emailCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; emailCell.mxkLabel.text = [VectorL10n settingsEmailAddress]; emailCell.mxkTextField.text = account.linkedEmails[emailIndex]; emailCell.mxkTextField.userInteractionEnabled = NO; cell = emailCell; } else if (row >= USER_SETTINGS_PHONENUMBERS_OFFSET) { NSInteger phoneNumberIndex = row - USER_SETTINGS_PHONENUMBERS_OFFSET; MXKTableViewCellWithLabelAndTextField *phoneCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; phoneCell.mxkLabel.text = [VectorL10n settingsPhoneNumber]; phoneCell.mxkTextField.text = [MXKTools readableMSISDN:account.linkedPhoneNumbers[phoneNumberIndex]]; phoneCell.mxkTextField.userInteractionEnabled = NO; cell = phoneCell; } else if (row == USER_SETTINGS_ADD_EMAIL_INDEX) { MXKTableViewCellWithLabelAndTextField *newEmailCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; // Render the cell according to the `newEmailEditingEnabled` property if (!_newEmailEditingEnabled) { newEmailCell.mxkLabel.text = [VectorL10n settingsAddEmailAddress]; newEmailCell.mxkTextField.text = nil; newEmailCell.mxkTextField.userInteractionEnabled = NO; newEmailCell.accessoryView = [[UIImageView alloc] initWithImage:[AssetImages.plusIcon.image vc_tintedImageUsingColor:ThemeService.shared.theme.textPrimaryColor]]; } else { newEmailCell.mxkLabel.text = nil; newEmailCell.mxkTextField.placeholder = [VectorL10n settingsEmailAddressPlaceholder]; newEmailCell.mxkTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:newEmailCell.mxkTextField.placeholder attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.placeholderTextColor}]; newEmailCell.mxkTextField.text = newEmailTextField.text; newEmailCell.mxkTextField.userInteractionEnabled = YES; newEmailCell.mxkTextField.keyboardType = UIKeyboardTypeEmailAddress; newEmailCell.mxkTextField.autocorrectionType = UITextAutocorrectionTypeNo; newEmailCell.mxkTextField.spellCheckingType = UITextSpellCheckingTypeNo; newEmailCell.mxkTextField.delegate = self; newEmailCell.mxkTextField.accessibilityIdentifier=@"SettingsVCAddEmailTextField"; [newEmailCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; [newEmailCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; [newEmailCell.mxkTextField removeTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; [newEmailCell.mxkTextField addTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; // When displaying the textfield the 1st time, open the keyboard if (!newEmailTextField) { newEmailTextField = newEmailCell.mxkTextField; [self editNewEmailTextField]; } else { // Update the current text field. newEmailTextField = newEmailCell.mxkTextField; } UIImage *accessoryViewImage = [AssetImages.plusIcon.image vc_tintedImageUsingColor:ThemeService.shared.theme.tintColor]; newEmailCell.accessoryView = [[UIImageView alloc] initWithImage:accessoryViewImage]; } newEmailCell.mxkTextField.tag = row; cell = newEmailCell; } else if (row == USER_SETTINGS_ADD_PHONENUMBER_INDEX) { // Render the cell according to the `newPhoneEditingEnabled` property if (!_newPhoneEditingEnabled) { MXKTableViewCellWithLabelAndTextField *newPhoneCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; newPhoneCell.mxkLabel.text = [VectorL10n settingsAddPhoneNumber]; newPhoneCell.mxkTextField.text = nil; newPhoneCell.mxkTextField.userInteractionEnabled = NO; newPhoneCell.accessoryView = [[UIImageView alloc] initWithImage:[AssetImages.plusIcon.image vc_tintedImageUsingColor:ThemeService.shared.theme.textPrimaryColor]]; cell = newPhoneCell; } else { TableViewCellWithPhoneNumberTextField * newPhoneCell = [self.tableView dequeueReusableCellWithIdentifier:[TableViewCellWithPhoneNumberTextField defaultReuseIdentifier] forIndexPath:indexPath]; [newPhoneCell.countryCodeButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; [newPhoneCell.countryCodeButton addTarget:self action:@selector(selectPhoneNumberCountry:) forControlEvents:UIControlEventTouchUpInside]; newPhoneCell.countryCodeButton.accessibilityIdentifier = @"SettingsVCPhoneCountryButton"; newPhoneCell.mxkLabel.font = newPhoneCell.mxkTextField.font = [UIFont systemFontOfSize:16]; newPhoneCell.mxkTextField.userInteractionEnabled = YES; newPhoneCell.mxkTextField.keyboardType = UIKeyboardTypePhonePad; newPhoneCell.mxkTextField.autocorrectionType = UITextAutocorrectionTypeNo; newPhoneCell.mxkTextField.spellCheckingType = UITextSpellCheckingTypeNo; newPhoneCell.mxkTextField.delegate = self; newPhoneCell.mxkTextField.accessibilityIdentifier=@"SettingsVCAddPhoneTextField"; [newPhoneCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; [newPhoneCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; [newPhoneCell.mxkTextField removeTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; [newPhoneCell.mxkTextField addTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; newPhoneCell.mxkTextField.tag = row; // When displaying the textfield the 1st time, open the keyboard if (!newPhoneNumberCell) { NSString *countryCode = [MXKAppSettings standardAppSettings].phonebookCountryCode; if (!countryCode) { // If none, consider the preferred locale NSLocale *local = [[NSLocale alloc] initWithLocaleIdentifier:[[NSBundle mainBundle] preferredLocalizations][0]]; if ([local respondsToSelector:@selector(countryCode)]) { countryCode = local.countryCode; } if (!countryCode) { countryCode = @"GB"; } } newPhoneCell.isoCountryCode = countryCode; newPhoneCell.mxkTextField.text = nil; newPhoneNumberCell = newPhoneCell; [self editNewPhoneNumberTextField]; } else { newPhoneCell.isoCountryCode = newPhoneNumberCell.isoCountryCode; newPhoneCell.mxkTextField.text = newPhoneNumberCell.mxkTextField.text; newPhoneNumberCell = newPhoneCell; } UIImage *accessoryViewImage = [AssetImages.plusIcon.image vc_tintedImageUsingColor:ThemeService.shared.theme.tintColor]; newPhoneCell.accessoryView = [[UIImageView alloc] initWithImage:accessoryViewImage]; cell = newPhoneCell; } } else if (row == USER_SETTINGS_CHANGE_PASSWORD_INDEX) { MXKTableViewCellWithLabelAndTextField *passwordCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; passwordCell.mxkLabel.text = [VectorL10n settingsChangePassword]; passwordCell.mxkTextField.text = @"*********"; passwordCell.mxkTextField.userInteractionEnabled = NO; passwordCell.mxkLabel.accessibilityIdentifier=@"SettingsVCChangePwdStaticText"; cell = passwordCell; } else if (row == USER_SETTINGS_MY_QR_CODE_INDEX) { MXKTableViewCell *showMyQRCodeCell = [self getDefaultTableViewCell:tableView]; showMyQRCodeCell.textLabel.text = BWIL10n.showMyQrSettingsTitle; [showMyQRCodeCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = showMyQRCodeCell; } } else if (section == SECTION_TAG_NEW_FEATURES) { if (row == SHOW_NEW_FEATURES) { MXKTableViewCell *showNewFeaturesCell = [self getDefaultTableViewCell:tableView]; showNewFeaturesCell.textLabel.text = BWIL10n.bwiSettingsNewFeaturesShowFeatures; [showNewFeaturesCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = showNewFeaturesCell; } } else if (section == SECTION_TAG_LOCATION_SHARING) { if (row == LOCATION_SHARING_ENABLED) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = VectorL10n.locationSharingSettingsToggleTitle; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.roomScreenAllowLocationAction; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLocationSharing:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } } else if (section == SECTION_TAG_SENDING_MEDIA) { if (row == SENDING_MEDIA_CONFIRM_SIZE) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsConfirmMediaSize]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.showMediaCompressionPrompt; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleConfirmMediaSize:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } } else if (section == SECTION_TAG_LINKS) { if (row == LINKS_SHOW_URL_PREVIEWS_INDEX) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsShowUrlPreviews]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.roomScreenShowsURLPreviews; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableURLPreviews:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == LINKS_SHOW_URL_PREVIEWS_DESCRIPTION_INDEX) { MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; descriptionCell.textLabel.text = [VectorL10n settingsShowUrlPreviewsDescription]; descriptionCell.textLabel.numberOfLines = 0; descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; cell = descriptionCell; } } else if (section == SECTION_TAG_NOTIFICATIONS) { if (row == NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsEnablePushNotif]; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePushNotifications:) forControlEvents:UIControlEventTouchUpInside]; BOOL isPushEnabled = account.pushNotificationServiceIsActive; // Even if push is enabled for the account, the user may have turned off notifications in system settings if (isPushEnabled && self.systemNotificationSettings) { isPushEnabled = self.systemNotificationSettings.authorizationStatus == UNAuthorizationStatusAuthorized; } labelAndSwitchCell.mxkSwitch.on = isPushEnabled; cell = labelAndSwitchCell; } else if (row == NOTIFICATION_SETTINGS_SYSTEM_SETTINGS) { cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; } cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; cell.textLabel.text = [VectorL10n settingsDeviceNotifications]; cell.detailTextLabel.text = @""; [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell.selectionStyle = UITableViewCellSelectionStyleDefault; } else if (row == NOTIFICATION_SETTINGS_SHOW_IN_APP_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = VectorL10n.settingsEnableInappNotifications; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.showInAppNotifications; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = account.pushNotificationServiceIsActive; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleShowInAppNotifications:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsShowDecryptedContent]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.showDecryptedContentInNotifications; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = account.pushNotificationServiceIsActive; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleShowDecodedContent:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsPinRoomsWithMissedNotif]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePinRoomsWithMissedNotif:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsPinRoomsWithUnread]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePinRoomsWithUnread:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == NOTIFICATION_SETTINGS_DEFAULT_SETTINGS_INDEX || row == NOTIFICATION_SETTINGS_MENTION_AND_KEYWORDS_SETTINGS_INDEX || row == NOTIFICATION_SETTINGS_OTHER_SETTINGS_INDEX || row == NOTIFICATION_SETTINGS_NOTIFICATION_TIMES_INDEX) { cell = [self getDefaultTableViewCell:tableView]; if (row == NOTIFICATION_SETTINGS_DEFAULT_SETTINGS_INDEX) { cell.textLabel.text = [VectorL10n settingsDefault]; } else if (row == NOTIFICATION_SETTINGS_MENTION_AND_KEYWORDS_SETTINGS_INDEX) { cell.textLabel.text = [VectorL10n settingsMentionsAndKeywords]; } else if (row == NOTIFICATION_SETTINGS_NOTIFICATION_TIMES_INDEX) { cell.textLabel.text = [BWIL10n settingsEnableNotificationTimes]; } else if (row == NOTIFICATION_SETTINGS_OTHER_SETTINGS_INDEX) { cell.textLabel.text = [VectorL10n settingsOther]; } [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; } } else if (section == SECTION_TAG_CALLS) { if (row == CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsCallsStunServerFallbackButton]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.allowStunServerFallback; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleStunServerFallback:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } } else if (section == SECTION_TAG_DISCOVERY) { cell = [self.settingsDiscoveryTableViewSection cellForRowAtRow:row]; } else if (section == SECTION_TAG_IDENTITY_SERVER) { switch (row) { case IDENTITY_SERVER_INDEX: { MXKTableViewCell *isCell = [self getDefaultTableViewCell:tableView]; if (account.mxSession.identityService.identityServer) { isCell.textLabel.text = account.mxSession.identityService.identityServer; } else { isCell.textLabel.text = [VectorL10n settingsIdentityServerNoIs]; } [isCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = isCell; break; } default: break; } } else if (section == SECTION_TAG_INTEGRATIONS) { switch (row) { case INTEGRATIONS_INDEX: { RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session]; MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsIntegrationsAllowButton]; labelAndSwitchCell.mxkSwitch.on = sharedSettings.hasIntegrationProvisioningEnabled; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleAllowIntegrations:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; break; } default: break; } } else if (section == SECTION_TAG_USER_INTERFACE) { if (row == USER_INTERFACE_LANGUAGE_INDEX) { cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; } NSString *language = [NSBundle mxk_language]; if (!language) { language = [MXKLanguagePickerViewController defaultLanguage]; } NSString *languageDescription = [MXKLanguagePickerViewController languageDescription:language]; // Capitalise the description in the language locale NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:language]; languageDescription = [languageDescription capitalizedStringWithLocale:locale]; cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; cell.textLabel.text = [VectorL10n settingsUiLanguage]; cell.detailTextLabel.text = languageDescription; [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell.selectionStyle = UITableViewCellSelectionStyleDefault; } else if (row == USER_INTERFACE_THEME_INDEX) { cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; } NSString *theme = RiotSettings.shared.userInterfaceTheme; if (!theme) { theme = @"auto"; } // bwi: 4744 (map previous set black theme to dark) if([theme isEqualToString:@"black"]) { theme = @"dark"; } theme = [NSString stringWithFormat:@"settings_ui_theme_%@", theme]; NSString *i18nTheme = NSLocalizedStringFromTable(theme, @"Vector", nil); cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; cell.textLabel.text = [VectorL10n settingsUiTheme]; cell.detailTextLabel.text = i18nTheme; [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell.selectionStyle = UITableViewCellSelectionStyleDefault; } else if (row == USER_INTERFACE_TIMELINE_STYLE_INDEX) { cell = [self buildMessageBubblesCellForTableView:tableView atIndexPath:indexPath]; } else if (row == PERSONAL_NOTES_INDEX) { cell = [self cellForPersonalNotesVisibility:tableView indexPath:indexPath row:row]; } else if (row == WYSIWYG_COMPOSER_INDEX) { cell = [self toggleWYSIWYGComposer:tableView indexPath:indexPath row:row]; } else { cell = [self cellForTimeline:tableView indexPath:indexPath row:row]; } } else if (section == SECTION_TAG_TIMELINE) { if (row == TIMELINE_STYLE_INDEX) { cell = [self buildMessageBubblesCellForTableView:tableView atIndexPath:indexPath]; } else if (row == TIMELINE_SHOW_REDACTIONS_IN_ROOM_HISTORY_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = VectorL10n.settingsUiShowRedactionsInRoomHistory; labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].showRedactionsInRoomHistory; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleShowRedacted:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == TIMELINE_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = VectorL10n.settingsLabsUseOnlyLatestUserAvatarAndName; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.roomScreenUseOnlyLatestUserAvatarAndName; labelAndSwitchCell.mxkSwitch.enabled = YES; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleUseOnlyLatestUserAvatarAndName:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } } else if (section == SECTION_TAG_IGNORED_USERS) { MXKTableViewCell *ignoredUserCell = [self getDefaultTableViewCell:tableView]; if (BWIBuildSettings.shared.bwiBetterIgnoredUsers) { ignoredUserCell.textLabel.text = BWIL10n.bwiSettingsIgnoredUsersText; } else { ignoredUserCell.textLabel.text = session.ignoredUsers[row]; } [ignoredUserCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = ignoredUserCell; } else if (section == SECTION_TAG_LOCAL_CONTACTS) { if (row == LOCAL_CONTACTS_SYNC_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.numberOfLines = 0; labelAndSwitchCell.mxkLabel.text = VectorL10n.settingsContactsEnableSync; labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].syncLocalContacts; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLocalContactsSync:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX) { cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; } NSString* countryCode = [[MXKAppSettings standardAppSettings] phonebookCountryCode]; NSLocale *local = [[NSLocale alloc] initWithLocaleIdentifier:[[NSBundle mainBundle] preferredLocalizations][0]]; NSString *countryName = [local displayNameForKey:NSLocaleCountryCode value:countryCode]; cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; cell.textLabel.text = [VectorL10n settingsContactsPhonebookCountry]; cell.detailTextLabel.text = countryName; [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell.selectionStyle = UITableViewCellSelectionStyleDefault; } } else if (section == SECTION_TAG_PRESENCE) { if (row == PRESENCE_OFFLINE_MODE) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = VectorL10n.settingsPresenceOfflineMode; MXKAccount *account = MXKAccountManager.sharedManager.accounts.firstObject; labelAndSwitchCell.mxkSwitch.on = account.preferredSyncPresence == MXPresenceOffline; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePresenceOfflineMode:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } } else if (section == SECTION_TAG_ABOUT) { if (row == ABOUT_VERSION_INDEX) { MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; NSString* appVersion = [AppDelegate theDelegate].appVersion; NSString* build = [AppDelegate theDelegate].build; versionCell.textLabel.text = [VectorL10n settingsVersion:[NSString stringWithFormat:@"%@ %@", appVersion, build]]; versionCell.selectionStyle = UITableViewCellSelectionStyleNone; cell = versionCell; } else if (row == ABOUT_OLM_VERSION_INDEX) { MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; versionCell.textLabel.text = [VectorL10n settingsOlmVersion:[OLMKit versionString]]; versionCell.selectionStyle = UITableViewCellSelectionStyleNone; cell = versionCell; } else if (row == ABOUT_ELEMENT_VERSION_INDEX) { MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; versionCell.textLabel.text = [BWIL10n settingsElementVersion:[BWIBuildSettings.shared elementBaseVersion]]; versionCell.selectionStyle = UITableViewCellSelectionStyleNone; // bwi: #5938 activate lab if (!versionCell.gestureRecognizers.count) { UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onElementVersionTap)]; [tap setNumberOfTapsRequired:7]; [versionCell addGestureRecognizer:tap]; } cell = versionCell; } else if (row == ABOUT_TERM_CONDITIONS_INDEX) { MXKTableViewCell *termAndConditionCell = [self getDefaultTableViewCell:tableView]; termAndConditionCell.textLabel.text = [VectorL10n settingsTermConditions]; [termAndConditionCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = termAndConditionCell; } else if (row == ABOUT_SUPPORT_INDEX) { MXKTableViewCell *supportCell = [self getDefaultTableViewCell:tableView]; supportCell.textLabel.text = BWIL10n.settingsSupport; [supportCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = supportCell; } else if (row == ABOUT_COPYRIGHT_INDEX) { MXKTableViewCell *copyrightCell = [self getDefaultTableViewCell:tableView]; copyrightCell.textLabel.text = [BWIL10n settingsCopyright]; [copyrightCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = copyrightCell; } else if (row == ABOUT_PRIVACY_INDEX) { MXKTableViewCell *privacyPolicyCell = [self getDefaultTableViewCell:tableView]; privacyPolicyCell.textLabel.text = [VectorL10n settingsPrivacyPolicy]; [privacyPolicyCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = privacyPolicyCell; } else if (row == ABOUT_THIRD_PARTY_INDEX) { MXKTableViewCell *thirdPartyCell = [self getDefaultTableViewCell:tableView]; thirdPartyCell.textLabel.text = [VectorL10n settingsThirdPartyNotices]; [thirdPartyCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = thirdPartyCell; } else if (row == ABOUT_IMPRINT_INDEX) { MXKTableViewCell *imprintCell = [self getDefaultTableViewCell:tableView]; imprintCell.textLabel.text = [BWIL10n settingsImprint]; [imprintCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = imprintCell; } else if (row == ADVANCED_CRASH_REPORT_INDEX) { MXKTableViewCellWithLabelAndSwitch* sendCrashReportCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; sendCrashReportCell.mxkLabel.text = VectorL10n.settingsAnalyticsAndCrashData; sendCrashReportCell.mxkSwitch.on = RiotSettings.shared.enableAnalytics; sendCrashReportCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; sendCrashReportCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; sendCrashReportCell.mxkSwitch.enabled = YES; [sendCrashReportCell.mxkSwitch addTarget:self action:@selector(toggleAnalytics:) forControlEvents:UIControlEventTouchUpInside]; cell = sendCrashReportCell; } else if (row == ADVANCED_ENABLE_RAGESHAKE_INDEX) { MXKTableViewCellWithLabelAndSwitch* enableRageShakeCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; enableRageShakeCell.mxkLabel.text = [VectorL10n settingsEnableRageshake]; enableRageShakeCell.mxkSwitch.on = RiotSettings.shared.enableRageShake; enableRageShakeCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; enableRageShakeCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; enableRageShakeCell.mxkSwitch.enabled = YES; [enableRageShakeCell.mxkSwitch addTarget:self action:@selector(toggleEnableRageShake:) forControlEvents:UIControlEventTouchUpInside]; cell = enableRageShakeCell; } else if (row == ABOUT_MARK_ALL_AS_READ_INDEX) { MXKTableViewCellWithButton *markAllBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!markAllBtnCell) { markAllBtnCell = [[MXKTableViewCellWithButton alloc] init]; } else { // Fix https://github.com/vector-im/riot-ios/issues/1354 markAllBtnCell.mxkButton.titleLabel.text = nil; } NSString *btnTitle = [VectorL10n settingsMarkAllAsRead]; [markAllBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; [markAllBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; [markAllBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; markAllBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [markAllBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; [markAllBtnCell.mxkButton addTarget:self action:@selector(markAllAsRead:) forControlEvents:UIControlEventTouchUpInside]; markAllBtnCell.mxkButton.accessibilityIdentifier = nil; cell = markAllBtnCell; } else if (row == ABOUT_CLEAR_CACHE_INDEX) { MXKTableViewCellWithButton *clearCacheBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!clearCacheBtnCell) { clearCacheBtnCell = [[MXKTableViewCellWithButton alloc] init]; } else { // Fix https://github.com/vector-im/riot-ios/issues/1354 clearCacheBtnCell.mxkButton.titleLabel.text = nil; } NSString *btnTitle = [VectorL10n settingsClearCache]; [clearCacheBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; [clearCacheBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; [clearCacheBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; clearCacheBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [clearCacheBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; [clearCacheBtnCell.mxkButton addTarget:self action:@selector(clearCache:) forControlEvents:UIControlEventTouchUpInside]; clearCacheBtnCell.mxkButton.accessibilityIdentifier = nil; cell = clearCacheBtnCell; } else if (row == ADVANCED_REPORT_BUG_INDEX) { MXKTableViewCellWithButton *reportBugBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!reportBugBtnCell) { reportBugBtnCell = [[MXKTableViewCellWithButton alloc] init]; } else { // Fix https://github.com/vector-im/riot-ios/issues/1354 reportBugBtnCell.mxkButton.titleLabel.text = nil; } NSString *btnTitle = [VectorL10n settingsReportBug]; [reportBugBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; [reportBugBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; [reportBugBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; reportBugBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [reportBugBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; [reportBugBtnCell.mxkButton addTarget:self action:@selector(reportBug:) forControlEvents:UIControlEventTouchUpInside]; reportBugBtnCell.mxkButton.accessibilityIdentifier = nil; cell = reportBugBtnCell; } // bwi: show netiquette cell in about section else if (row == ABOUT_NETIQUETTE_INDEX) { MXKTableViewCell *netiquetteCell = [self getDefaultTableViewCell:tableView]; netiquetteCell.textLabel.text = BWIL10n.bwiSettingsNetiquette; [netiquetteCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = netiquetteCell; } else if (row == ABOUT_TERM_CONDITIONS_INDEX) { MXKTableViewCell *termAndConditionCell = [self getDefaultTableViewCell:tableView]; termAndConditionCell.textLabel.text = [BWIL10n settingsCopyright]; [termAndConditionCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = termAndConditionCell; } else if (row == ABOUT_PRIVACY_INDEX) { MXKTableViewCell *privacyPolicyCell = [self getDefaultTableViewCell:tableView]; privacyPolicyCell.textLabel.text = [VectorL10n settingsPrivacyPolicy]; [privacyPolicyCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = privacyPolicyCell; } else if (row == ABOUT_ACCESSIBILITY_DECLARATION_INDEX) { MXKTableViewCell *accessibilityDeclarationCell = [self getDefaultTableViewCell:tableView]; accessibilityDeclarationCell.textLabel.text = [BWIL10n bwiAccessibilityDeclarationButtonTitle]; [accessibilityDeclarationCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = accessibilityDeclarationCell; } else if (row == ABOUT_THIRD_PARTY_INDEX) { MXKTableViewCell *thirdPartyCell = [self getDefaultTableViewCell:tableView]; thirdPartyCell.textLabel.text = [VectorL10n settingsThirdPartyNotices]; [thirdPartyCell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; cell = thirdPartyCell; } } else if (section == SECTION_TAG_LABS) { if (row == LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableRingingForGroupCalls]; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableRingingForGroupCalls; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableRingingForGroupCalls:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == LABS_ENABLE_THREADS_INDEX) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableThreads]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableThreads; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableThreads:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS) { cell = [self buildAutoReportDecryptionErrorsCellForTableView:tableView atIndexPath:indexPath]; } else if (row == LABS_ENABLE_LIVE_LOCATION_SHARING) { cell = [self buildLiveLocationSharingCellForTableView:tableView atIndexPath:indexPath]; } else if (row == LABS_ENABLE_NEW_SESSION_MANAGER) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableNewSessionManager]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableNewSessionManager; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableNewSessionManager:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == LABS_ENABLE_NEW_CLIENT_INFO_FEATURE) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableNewClientInfoFeature]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableClientInformationFeature; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableNewClientInfoFeature:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == LABS_ENABLE_WYSIWYG_COMPOSER) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableWysiwygComposer]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableWysiwygComposer; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableWysiwygComposerFeature:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } // bwi: #5203 add federation feature else if (row == LABS_ENABLE_FEDERATION) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [BWIL10n bwiSettingsLabsFederationFeature]; labelAndSwitchCell.mxkSwitch.on = BWIBuildSettings.shared.isLabsFederationEnabled; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableFederationFeature:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } else if (row == LABS_ENABLE_VOICE_BROADCAST) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableVoiceBroadcast]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableVoiceBroadcast; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableVoiceBroadcastFeature:) forControlEvents:UIControlEventTouchUpInside]; cell = labelAndSwitchCell; } } else if (section == SECTION_TAG_SECURITY) { switch (row) { case SECURITY_BUTTON_INDEX: cell = [self getDefaultTableViewCell:tableView]; cell.textLabel.text = [VectorL10n securitySettingsTitle]; [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; break; case DEVICE_MANAGER_INDEX: cell = [self getDefaultTableViewCell:tableView]; cell.textLabel.text = [VectorL10n userSessionsSettings]; [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; break; } } else if (section == SECTION_TAG_DEACTIVATE_ACCOUNT) { MXKTableViewCellWithButton *deactivateAccountBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!deactivateAccountBtnCell) { deactivateAccountBtnCell = [[MXKTableViewCellWithButton alloc] init]; } else { // Fix https://github.com/vector-im/riot-ios/issues/1354 deactivateAccountBtnCell.mxkButton.titleLabel.text = nil; } NSString *btnTitle = [VectorL10n settingsDeactivateMyAccount]; [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; [deactivateAccountBtnCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; deactivateAccountBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [deactivateAccountBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; [deactivateAccountBtnCell.mxkButton addTarget:self action:@selector(deactivateAccountAction) forControlEvents:UIControlEventTouchUpInside]; deactivateAccountBtnCell.mxkButton.accessibilityIdentifier = nil; cell = deactivateAccountBtnCell; } else if (section == SECTION_TAG_DOWNTIME_WARNING) { if (row == DOWNTIME_WARNING) { MXKTableViewCell *downtimeCell = [self getWarningTableViewCell:tableView]; downtimeCell.contentView.backgroundColor = [self bwiDowntimeColor]; downtimeCell.textLabel.text = [self bwiDowntimeCellText]; downtimeCell.textLabel.textColor = [self bwiDowntimeTextColor]; downtimeCell.textLabel.lineBreakMode = NSLineBreakByWordWrapping; downtimeCell.textLabel.numberOfLines = 0; cell = downtimeCell; } else if (row == DOWNTIME_IGNORE_BLOCKING) { cell = [self cellForDowntimeIgnore:tableView indexPath:indexPath row:indexPath.row]; } } else if (section == SECTION_TAG_OUTDATED_WARNING) { if (row == OUTDATED_WARNING) { MXKTableViewCell *downtimeCell = [self getWarningTableViewCell:tableView]; downtimeCell.contentView.backgroundColor = [UIColor yellowColor]; downtimeCell.textLabel.text = [BWIL10n bwiOutdatedSettingsMessage:AppInfo.current.displayName]; downtimeCell.textLabel.textColor = [UIColor blackColor]; downtimeCell.textLabel.lineBreakMode = NSLineBreakByWordWrapping; downtimeCell.textLabel.numberOfLines = 0; cell = downtimeCell; } } else if (section == SECTION_TAG_DEVELOPER) { if (row == DEVELOPER_SHOW_SETTINGS_INDEX) { MXKTableViewCellWithButton *showSettingsBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!showSettingsBtnCell) { showSettingsBtnCell = [[MXKTableViewCellWithButton alloc] init]; } else { // Fix https://github.com/vector-im/riot-ios/issues/1354 showSettingsBtnCell.mxkButton.titleLabel.text = nil; } NSString *btnTitle = BWIL10n.bwiSettingsShowDeveloperSettings; [showSettingsBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; [showSettingsBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; [showSettingsBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; showSettingsBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [showSettingsBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; [showSettingsBtnCell.mxkButton addTarget:self action:@selector(showDeveloperSettings) forControlEvents:UIControlEventTouchUpInside]; showSettingsBtnCell.mxkButton.accessibilityIdentifier = nil; cell = showSettingsBtnCell; } } else if (section == SECTION_TAG_ACCOUNT) { switch (row) { case ACCOUNT_MANAGE_INDEX: cell = [self getDefaultTableViewCell:tableView]; cell.textLabel.text = [VectorL10n settingsManageAccountAction]; [cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme]; break; } } return cell; } - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { Section *sectionObj = [_tableViewSections sectionAtIndex:section]; return sectionObj.headerTitle; } - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section { if ([view isKindOfClass:UITableViewHeaderFooterView.class]) { // Customize label style UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView*)view; tableViewHeaderFooterView.textLabel.textColor = ThemeService.shared.theme.colors.secondaryContent; tableViewHeaderFooterView.textLabel.font = ThemeService.shared.theme.fonts.footnote; } } - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { NSAttributedString *attributedFooterTitle = [_tableViewSections sectionAtIndex:section].attributedFooterTitle; if (!attributedFooterTitle) { return nil; } SectionFooterView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SectionFooterView.defaultReuseIdentifier]; [view updateWithTheme:ThemeService.shared.theme]; view.leadingInset = tableView.vc_separatorInset.left; [view updateWithAttributedText:attributedFooterTitle]; if (section == SECTION_TAG_USER_SETTINGS) { UIGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollToDiscoverySection)]; [view addGestureRecognizer:recognizer]; } else if (section == SECTION_TAG_DISCOVERY && self.settingsDiscoveryTableViewSection.footerShouldScrollToUserSettings) { UIGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollToUserSettingsSection)]; [view addGestureRecognizer:recognizer]; } return view; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { NSIndexPath *tagsIndexPath = [_tableViewSections tagsIndexPathFromTableViewIndexPath:indexPath]; NSInteger section = tagsIndexPath.section; NSInteger row = tagsIndexPath.row; if (section == SECTION_TAG_USER_SETTINGS) { return row >= USER_SETTINGS_PHONENUMBERS_OFFSET; } return NO; } - (void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath { // iOS8 requires this method to enable editing (see editActionsForRowAtIndexPath). } #pragma mark - UITableView delegate - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; { cell.backgroundColor = ThemeService.shared.theme.backgroundColor; if (cell.selectionStyle != UITableViewCellSelectionStyleNone) { // Update the selected background view if (ThemeService.shared.theme.selectedBackgroundColor) { cell.selectedBackgroundView = [[UIView alloc] init]; cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; } else { if (tableView.style == UITableViewStylePlain) { cell.selectedBackgroundView = nil; } else { cell.selectedBackgroundView.backgroundColor = nil; } } } } - (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath { NSIndexPath *tagsIndexPath = [_tableViewSections tagsIndexPathFromTableViewIndexPath:indexPath]; NSInteger section = tagsIndexPath.section; NSInteger row = tagsIndexPath.row; NSMutableArray* actions; // Add the swipe to delete user's email or phone number if (section == SECTION_TAG_USER_SETTINGS) { if (row >= USER_SETTINGS_PHONENUMBERS_OFFSET) { actions = [[NSMutableArray alloc] init]; UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; CGFloat cellHeight = cell ? cell.frame.size.height : 50; // Patch: Force the width of the button by adding whitespace characters into the title string. UITableViewRowAction *leaveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ [self onRemove3PID:indexPath]; }]; leaveAction.backgroundColor = [MXKTools convertImageToPatternColor:@"remove_icon_pink" backgroundColor:ThemeService.shared.theme.headerBackgroundColor patternSize:CGSizeMake(50, cellHeight) resourceSize:CGSizeMake(24, 24)]; [actions insertObject:leaveAction atIndex:0]; } } return actions; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (self.tableView == tableView) { NSIndexPath *tagsIndexPath = [_tableViewSections tagsIndexPathFromTableViewIndexPath:indexPath]; NSInteger section = tagsIndexPath.section; NSInteger row = tagsIndexPath.row; if (section == SECTION_TAG_USER_INTERFACE) { if (row == USER_INTERFACE_LANGUAGE_INDEX) { // Display the language picker LanguagePickerViewController *languagePickerViewController = [LanguagePickerViewController languagePickerViewController]; languagePickerViewController.selectedLanguage = [NSBundle mxk_language]; languagePickerViewController.delegate = self; [self pushViewController:languagePickerViewController]; } else if (row == USER_INTERFACE_THEME_INDEX) { [self showThemePicker]; } } else if (section == SECTION_TAG_NOTIFICATIONS && row == NOTIFICATION_SETTINGS_SYSTEM_SETTINGS) { [self openSystemSettingsApp]; } else if (section == SECTION_TAG_DISCOVERY) { [self.settingsDiscoveryTableViewSection selectRow:row]; } else if (section == SECTION_TAG_IDENTITY_SERVER) { switch (row) { case IDENTITY_SERVER_INDEX: [self showIdentityServerSettingsScreen]; break; } } else if (section == SECTION_TAG_IGNORED_USERS) { if (BWIBuildSettings.shared.bwiBetterIgnoredUsers) { [self bwiShowIgnoredUsersScreen]; } else { MXSession* session = self.mainSession; NSString *ignoredUserId = session.ignoredUsers[row]; if (ignoredUserId) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; __weak typeof(self) weakSelf = self; UIAlertController *unignorePrompt = [UIAlertController alertControllerWithTitle:[VectorL10n settingsUnignoreUser:ignoredUserId] message:nil preferredStyle:UIAlertControllerStyleAlert]; [unignorePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n yes] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; MXSession* session = self.mainSession; // Remove the member from the ignored user list [self startActivityIndicator]; [session unIgnoreUsers:@[ignoredUserId] success:^{ [self stopActivityIndicator]; } failure:^(NSError *error) { [self stopActivityIndicator]; MXLogDebug(@"[SettingsViewController] Unignore %@ failed", ignoredUserId); NSString *myUserId = session.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; } }]]; [unignorePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n no] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; } }]]; [unignorePrompt mxk_setAccessibilityIdentifier: @"SettingsVCUnignoreAlert"]; [self presentViewController:unignorePrompt animated:YES completion:nil]; currentAlert = unignorePrompt; } } } else if (section == SECTION_TAG_ABOUT) { // bwi: show netiquette infos if (row == ABOUT_NETIQUETTE_INDEX) { [self showNetiquetteInfos]; } else if (row == ABOUT_COPYRIGHT_INDEX) { [[UIApplication sharedApplication] vc_open:[NSURL URLWithString:BWIBuildSettings.shared.applicationCopyrightUrlString] completionHandler:nil]; } else if (row == ABOUT_ACCEPTABLE_USE_INDEX) { [[UIApplication sharedApplication] vc_open:[NSURL URLWithString: BuildSettings.applicationAcceptableUsePolicyUrlString] completionHandler:nil]; } else if (row == ABOUT_SUPPORT_INDEX) { NSString *htmlFile = [[NSBundle mainBundle] pathForResource:BWIBuildSettings.shared.settingsScreenSupportSettingHTML ofType:@"html" inDirectory:nil]; WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithLocalHTMLFile:htmlFile]; webViewViewController.title = BWIL10n.settingsSupport; [webViewViewController vc_setLargeTitleDisplayMode:UINavigationItemLargeTitleDisplayModeNever]; [self pushViewController:webViewViewController]; } else if (row == ABOUT_PRIVACY_INDEX) { if (BWIBuildSettings.shared.bwiUseWellKnownPrivacyPolicyLink) { [[UIApplication sharedApplication] vc_open:[NSURL URLWithString: self.mainSession.homeserverWellknown.dataPrivacyURL] completionHandler:nil]; } else { [[UIApplication sharedApplication] vc_open:[NSURL URLWithString: BWIBuildSettings.shared.applicationPrivacyPolicyUrlString] completionHandler:nil]; } } else if (row == ABOUT_ACCESSIBILITY_DECLARATION_INDEX) { [self showAccessibilityDeclaration]; } else if (row == ABOUT_THIRD_PARTY_INDEX) { NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"third_party_licenses" ofType:@"html" inDirectory:nil]; WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithLocalHTMLFile:htmlFile]; webViewViewController.title = [VectorL10n settingsThirdPartyNotices]; [webViewViewController vc_setLargeTitleDisplayMode:UINavigationItemLargeTitleDisplayModeNever]; [self pushViewController:webViewViewController]; } else if (row == ABOUT_IMPRINT_INDEX) { [[UIApplication sharedApplication] vc_open:[NSURL URLWithString: [self.mainSession.homeserverWellknown imprintURL]] completionHandler:nil]; } } else if (section == SECTION_TAG_USER_SETTINGS) { if (row == USER_SETTINGS_PROFILE_PICTURE_INDEX) { [self onProfileAvatarTap:nil]; } if (row == USER_SETTINGS_PERSONAL_STATE_INDEX) { [self showPersonalStateSettings]; } else if (row == USER_SETTINGS_CHANGE_PASSWORD_INDEX) { [self displayPasswordAlert]; } else if (row == USER_SETTINGS_MY_QR_CODE_INDEX) { [self showMyQRCode]; } else if (row == USER_SETTINGS_ADD_EMAIL_INDEX) { if (!self.newEmailEditingEnabled) { // Enable the new email text field self.newEmailEditingEnabled = YES; } else if (newEmailTextField) { [self onAddNewEmail:newEmailTextField]; } } else if (row == USER_SETTINGS_ADD_PHONENUMBER_INDEX) { if (!self.newPhoneEditingEnabled) { // Enable the new phone text field self.newPhoneEditingEnabled = YES; } else if (newPhoneNumberCell.mxkTextField) { [self onAddNewPhone:newPhoneNumberCell.mxkTextField]; } } } else if (section == SECTION_TAG_LOCAL_CONTACTS) { if (row == LOCAL_CONTACTS_PHONEBOOK_COUNTRY_INDEX) { CountryPickerViewController *countryPicker = [CountryPickerViewController countryPickerViewController]; countryPicker.view.tag = SECTION_TAG_LOCAL_CONTACTS; countryPicker.delegate = self; countryPicker.showCountryCallingCode = YES; [self pushViewController:countryPicker]; } } else if (section == SECTION_TAG_SECURITY) { switch (row) { case SECURITY_BUTTON_INDEX: { SecurityViewController *securityViewController = [SecurityViewController instantiateWithMatrixSession:self.mainSession]; [self pushViewController:securityViewController]; break; } case DEVICE_MANAGER_INDEX: { [self showUserSessionsFlow]; break; } } } else if (section == SECTION_TAG_NOTIFICATIONS) { switch (row) { case NOTIFICATION_SETTINGS_DEFAULT_SETTINGS_INDEX: [self showNotificationSettings:NotificationSettingsScreenDefaultNotifications]; break; case NOTIFICATION_SETTINGS_MENTION_AND_KEYWORDS_SETTINGS_INDEX: [self showNotificationSettings:NotificationSettingsScreenMentionsAndKeywords]; break; case NOTIFICATION_SETTINGS_NOTIFICATION_TIMES_INDEX: [self showNotificationTimesSettings]; break; case NOTIFICATION_SETTINGS_OTHER_SETTINGS_INDEX: [self showNotificationSettings:NotificationSettingsScreenOther]; break; } } else if (section == SECTION_TAG_NEW_FEATURES) { if (row == SHOW_NEW_FEATURES) { // bwi: replace link and show webview NSString *htmlFile = BWIBuildSettings.shared.bwiFeatureHistoryFilePath; WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithLocalHTMLFile: htmlFile]; webViewViewController.title = BWIL10n.bwiSettingsNewFeaturesHeader; [self pushViewController:webViewViewController]; } } else if (section == SECTION_TAG_ACCOUNT) { switch(row) { case ACCOUNT_MANAGE_INDEX: [self onManageAccountTap]; break; } } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } } #pragma mark - actions - (void)onSignout:(id)sender { // bwi (#3539) Check connectivity before login out. Logout can have strange effects when occuring without network connection if ([AppDelegate theDelegate].isOffline) { NSError *error = [NSError errorWithDomain:BWISettingsErrorDomain code:BWISettingsOfflineLogoutErrorCode userInfo:@{ NSLocalizedDescriptionKey : BWIL10n.bwiErrorLogoutOffline }]; [[AppDelegate theDelegate] showErrorAsAlert:error]; } else { self.signOutButton = (UIButton*)sender; SignOutFlowPresenter *flowPresenter = [[SignOutFlowPresenter alloc] initWithSession:self.mainSession presentingViewController:self]; flowPresenter.delegate = self; [flowPresenter startWithSourceView:self.signOutButton]; self.signOutFlowPresenter = flowPresenter; } } - (void)onRemove3PID:(NSIndexPath*)indexPath { NSIndexPath *tagsIndexPath = [_tableViewSections tagsIndexPathFromTableViewIndexPath:indexPath]; NSInteger section = tagsIndexPath.section; NSInteger row = tagsIndexPath.row; if (section == SECTION_TAG_USER_SETTINGS) { NSString *address, *medium; MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; NSString *promptMsg; if (row >= USER_SETTINGS_EMAILS_OFFSET) { medium = kMX3PIDMediumEmail; row = row - USER_SETTINGS_EMAILS_OFFSET; NSArray *linkedEmails = account.linkedEmails; if (row < linkedEmails.count) { address = linkedEmails[row]; promptMsg = [VectorL10n settingsRemoveEmailPromptMsg:address]; } } else if (row >= USER_SETTINGS_PHONENUMBERS_OFFSET) { medium = kMX3PIDMediumMSISDN; row = row - USER_SETTINGS_PHONENUMBERS_OFFSET; NSArray *linkedPhones = account.linkedPhoneNumbers; if (row < linkedPhones.count) { address = linkedPhones[row]; NSString *e164 = [NSString stringWithFormat:@"+%@", address]; NBPhoneNumber *phoneNb = [[NBPhoneNumberUtil sharedInstance] parse:e164 defaultRegion:nil error:nil]; NSString *phoneNumber = [[NBPhoneNumberUtil sharedInstance] format:phoneNb numberFormat:NBEPhoneNumberFormatINTERNATIONAL error:nil]; promptMsg = [VectorL10n settingsRemovePhonePromptMsg:phoneNumber]; } } if (address && medium) { __weak typeof(self) weakSelf = self; if (currentAlert) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } // Remove ? UIAlertController *removePrompt = [UIAlertController alertControllerWithTitle:[VectorL10n settingsRemovePromptTitle] message:promptMsg preferredStyle:UIAlertControllerStyleAlert]; [removePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; } }]]; [removePrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n remove] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; [self startActivityIndicator]; [self.mainSession.matrixRestClient remove3PID:address medium:medium success:^{ if (weakSelf) { typeof(self) self = weakSelf; [self stopActivityIndicator]; // Update linked 3pids [self loadAccount3PIDs]; } } failure:^(NSError *error) { MXLogDebug(@"[SettingsViewController] Remove 3PID: %@ failed", address); if (weakSelf) { typeof(self) self = weakSelf; [self stopActivityIndicator]; NSString *myUserId = self.mainSession.myUser.userId; // TODO: Hanlde multi-account [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; } }]; } }]]; [removePrompt mxk_setAccessibilityIdentifier: @"SettingsVCRemove3PIDAlert"]; [self presentViewController:removePrompt animated:YES completion:nil]; currentAlert = removePrompt; } } } - (void)toggleLocationSharing:(UISwitch *)sender { RiotSettings.shared.roomScreenAllowLocationAction = sender.on; } - (void)toggleConfirmMediaSize:(UISwitch *)sender { RiotSettings.shared.showMediaCompressionPrompt = sender.on; } - (void)togglePushNotifications:(UISwitch *)sender { // Check first whether the user allow notification from system settings if (self.systemNotificationSettings.authorizationStatus == UNAuthorizationStatusDenied) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; __weak typeof(self) weakSelf = self; NSString *title = [VectorL10n settingsNotificationsDisabledAlertTitle]; NSString *message = [VectorL10n settingsNotificationsDisabledAlertMessage]; UIAlertController *showSettingsPrompt = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; [showSettingsPrompt addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; } }]]; UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:[VectorL10n settings] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; [self openSystemSettingsApp]; } }]; [showSettingsPrompt addAction:settingsAction]; showSettingsPrompt.preferredAction = settingsAction; [showSettingsPrompt mxk_setAccessibilityIdentifier: @"SettingsVCPushNotificationsAlert"]; [self presentViewController:showSettingsPrompt animated:YES completion:nil]; currentAlert = showSettingsPrompt; // Keep the the switch off. sender.on = NO; } else if ([MXKAccountManager sharedManager].activeAccounts.count) { [self startActivityIndicator]; MXKAccountManager *accountManager = [MXKAccountManager sharedManager]; MXKAccount* account = accountManager.activeAccounts.firstObject; if (accountManager.apnsDeviceToken) { [account enablePushNotifications:!account.pushNotificationServiceIsActive success:^{ [self stopActivityIndicator]; } failure:^(NSError *error) { [self stopActivityIndicator]; }]; } else { // Obtain device token when user has just enabled access to notifications from system settings [[AppDelegate theDelegate] registerForRemoteNotificationsWithCompletion:^(NSError * error) { if (error) { [sender setOn:NO animated:YES]; [self stopActivityIndicator]; } else { [account enablePushNotifications:YES success:^{ [self stopActivityIndicator]; } failure:^(NSError *error) { [self stopActivityIndicator]; }]; } }]; } } } - (void)toggleShowInAppNotifications:(UISwitch *)sender { RiotSettings.shared.showInAppNotifications = sender.isOn; } - (void)openSystemSettingsApp { NSURL *settingsAppURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; [[UIApplication sharedApplication] openURL:settingsAppURL options:@{} completionHandler:nil]; } - (void)toggleCallKit:(UISwitch *)sender { [MXKAppSettings standardAppSettings].enableCallKit = sender.isOn; } - (void)toggleStunServerFallback:(UISwitch *)sender { RiotSettings.shared.allowStunServerFallback = sender.isOn; self.mainSession.callManager.fallbackSTUNServer = RiotSettings.shared.allowStunServerFallback ? BWIBuildSettings.shared.stunServerFallbackUrlString : nil; } - (void)toggleAllowIntegrations:(UISwitch *)sender { MXSession *session = self.mainSession; [self startActivityIndicator]; __block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session]; [sharedSettings setIntegrationProvisioningWithEnabled:sender.isOn success:^{ sharedSettings = nil; [self stopActivityIndicator]; } failure:^(NSError * _Nullable error) { sharedSettings = nil; [sender setOn:!sender.isOn animated:YES]; [self stopActivityIndicator]; }]; } - (void)toggleShowDecodedContent:(UISwitch *)sender { RiotSettings.shared.showDecryptedContentInNotifications = sender.isOn; } - (void)toggleLocalContactsSync:(UISwitch *)sender { if (sender.on) { // First check if the service terms have already been accepted MXSession *session = self.mxSessions.firstObject; if (session.identityService.areAllTermsAgreed) { // If they have we only require local contacts access. [self checkAccessForContacts]; } else { [self prepareIdentityServiceAndPresentTermsWithSession:session checkingAccessForContactsOnAccept:YES]; } } else { [MXKAppSettings standardAppSettings].syncLocalContacts = NO; [self updateSections]; } } - (void)toggleEnableURLPreviews:(UISwitch *)sender { RiotSettings.shared.roomScreenShowsURLPreviews = sender.on; // Any loaded cell data is now invalid and should be refreshed for the new value. [[MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession] reset]; } - (void)toggleAnalytics:(UISwitch *)sender { if (sender.isOn) { MXLogDebug(@"[SettingsViewController] enable automatic crash report and analytics sending"); [Analytics.shared optInWith:self.mainSession]; } else { MXLogDebug(@"[SettingsViewController] disable automatic crash report and analytics sending"); [Analytics.shared optOut]; // Remove potential crash file. [MXLogger deleteCrashLog]; } } - (void)toggleEnableRageShake:(UISwitch *)sender { RiotSettings.shared.enableRageShake = sender.isOn; [self updateSections]; } - (void)toggleEnableRingingForGroupCalls:(UISwitch *)sender { RiotSettings.shared.enableRingingForGroupCalls = sender.isOn; } - (void)toggleEnableThreads:(UISwitch *)sender { if (sender.isOn && !self.mainSession.store.supportedMatrixVersions.supportsThreads) { // user wants to turn on the threads setting but the server does not support it if (self.threadsBetaBridgePresenter) { [self.threadsBetaBridgePresenter dismissWithAnimated:YES completion:nil]; self.threadsBetaBridgePresenter = nil; } self.threadsBetaBridgePresenter = [[ThreadsBetaCoordinatorBridgePresenter alloc] initWithThreadId:@"" infoText:VectorL10n.threadsDiscourageInformation1 additionalText:VectorL10n.threadsDiscourageInformation2]; self.threadsBetaBridgePresenter.delegate = self; [self.threadsBetaBridgePresenter presentFrom:self.presentedViewController?:self animated:YES]; return; } [self enableThreads:sender.isOn]; } - (void)enableThreads:(BOOL)enable { RiotSettings.shared.enableThreads = enable; MXSDKOptions.sharedInstance.enableThreads = enable; [[MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession] reset]; [[AppDelegate theDelegate] restoreEmptyDetailsViewController]; } - (void)toggleEnableNewSessionManager:(UISwitch *)sender { RiotSettings.shared.enableNewSessionManager = sender.isOn; [self updateSections]; } - (void)toggleEnableNewClientInfoFeature:(UISwitch *)sender { BOOL isEnabled = sender.isOn; RiotSettings.shared.enableClientInformationFeature = isEnabled; MXSDKOptions.sharedInstance.enableNewClientInformationFeature = isEnabled; [self.mainSession updateClientInformation]; } - (void)toggleEnableWysiwygComposerFeature:(UISwitch *)sender { RiotSettings.shared.enableWysiwygComposer = sender.isOn; } - (void)toggleEnableVoiceBroadcastFeature:(UISwitch *)sender { RiotSettings.shared.enableVoiceBroadcast = sender.isOn; } - (void)togglePinRoomsWithMissedNotif:(UISwitch *)sender { RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome = sender.isOn; } - (void)togglePinRoomsWithUnread:(UISwitch *)sender { RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome = sender.on; } - (void)toggleUseOnlyLatestUserAvatarAndName:(UISwitch *)sender { RiotSettings.shared.roomScreenUseOnlyLatestUserAvatarAndName = sender.isOn; } // bwi: #5203 add federation feature - (void)toggleEnableFederationFeature:(UISwitch *)sender { BWIBuildSettings.shared.isLabsFederationEnabled = sender.isOn; } - (void)markAllAsRead:(id)sender { // Feedback: disable button and run activity indicator UIButton *button = (UIButton*)sender; button.enabled = NO; [self startActivityIndicator]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [[AppDelegate theDelegate] markAllMessagesAsRead]; [self stopActivityIndicator]; button.enabled = YES; }); } - (void)clearCache:(id)sender { // Feedback: disable button and run activity indicator UIButton *button = (UIButton*)sender; button.enabled = NO; [self launchClearCache]; } - (void)launchClearCache { [self startActivityIndicator]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [[AppDelegate theDelegate] reloadMatrixSessions:YES]; }); } - (void)reportBug:(id)sender { BugReportViewController *bugReportViewController = [BugReportViewController bugReportViewController]; [bugReportViewController showInViewController:self]; } - (void)selectPhoneNumberCountry:(id)sender { newPhoneNumberCountryPicker = [CountryPickerViewController countryPickerViewController]; newPhoneNumberCountryPicker.view.tag = SECTION_TAG_USER_SETTINGS; newPhoneNumberCountryPicker.delegate = self; newPhoneNumberCountryPicker.showCountryCallingCode = YES; [self pushViewController:newPhoneNumberCountryPicker]; } - (void)onSave:(id)sender { // sanity check if ([MXKAccountManager sharedManager].activeAccounts.count == 0) { return; } self.navigationItem.rightBarButtonItem.enabled = NO; [self startActivityIndicator]; isSavingInProgress = YES; __weak typeof(self) weakSelf = self; MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; MXMyUser* myUser = account.mxSession.myUser; if (newDisplayName && ![myUser.displayname isEqualToString:newDisplayName]) { // Save display name [account setUserDisplayName:newDisplayName success:^{ if (weakSelf) { // Update the current displayname typeof(self) self = weakSelf; self->newDisplayName = nil; // Go to the next change saving step [self onSave:nil]; } } failure:^(NSError *error) { MXLogDebug(@"[SettingsViewController] Failed to set displayName"); if (weakSelf) { typeof(self) self = weakSelf; [self handleErrorDuringProfileChangeSaving:error]; } }]; return; } if (shouldRemoveAvatarImage) { self->uploadedAvatarURL = @""; self->newAvatarImage = nil; self->shouldRemoveAvatarImage = false; dispatch_async(dispatch_get_main_queue(), ^{ [self onSave:nil]; }); return; } else if (newAvatarImage) { // Retrieve the current picture and make sure its orientation is up MXMediaLoader *uploader = [MXMediaManager prepareUploaderWithMatrixSession:account.mxSession initialRange:0 andRange:1.0]; // Upload picture UIImage *updatedPicture = [MXKTools forceImageOrientationUp:newAvatarImage]; [uploader uploadData:UIImageJPEGRepresentation(updatedPicture, 0.5) filename:nil mimeType:@"image/jpeg" success:^(NSString *url) { if (weakSelf) { typeof(self) self = weakSelf; // Store uploaded picture url and trigger picture saving self->uploadedAvatarURL = url; self->newAvatarImage = nil; [self onSave:nil]; } } failure:^(NSError *error) { MXLogDebug(@"[SettingsViewController] Failed to upload image"); if (weakSelf) { typeof(self) self = weakSelf; [self handleErrorDuringProfileChangeSaving:error]; } }]; return; } else if (uploadedAvatarURL) { [account setUserAvatarUrl:uploadedAvatarURL success:^{ if (weakSelf) { typeof(self) self = weakSelf; self->uploadedAvatarURL = nil; [self onSave:nil]; } } failure:^(NSError *error) { MXLogDebug(@"[SettingsViewController] Failed to set avatar url"); if (weakSelf) { typeof(self) self = weakSelf; [self handleErrorDuringProfileChangeSaving:error]; } }]; return; } // Backup is complete isSavingInProgress = NO; [self stopActivityIndicator]; // Check whether destroy has been called durign saving if (onReadyToDestroyHandler) { // Ready to destroy onReadyToDestroyHandler(); onReadyToDestroyHandler = nil; } else { [self updateSections]; } } - (void)handleErrorDuringProfileChangeSaving:(NSError*)error { // Sanity check: retrieve the current root view controller UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; if (rootViewController) { __weak typeof(self) weakSelf = self; // Alert user NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; if (!title) { title = [VectorL10n settingsFailToUpdateProfile]; } NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; [currentAlert dismissViewControllerAnimated:NO completion:nil]; UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; [errorAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; // Reset the updated displayname self->newDisplayName = nil; // Discard picture change self->uploadedAvatarURL = nil; self->newAvatarImage = nil; self->shouldRemoveAvatarImage = false; // Loop to end saving [self onSave:nil]; } }]]; [errorAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n retry] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; // Loop to retry saving [self onSave:nil]; } }]]; [errorAlert mxk_setAccessibilityIdentifier: @"SettingsVCSaveChangesFailedAlert"]; [rootViewController presentViewController:errorAlert animated:YES completion:nil]; currentAlert = errorAlert; } } - (IBAction)onAddNewEmail:(id)sender { // Ignore empty field if (!newEmailTextField.text.length) { // Reset new email adding self.newEmailEditingEnabled = NO; return; } // Email check if (![MXTools isEmailAddress:newEmailTextField.text]) { __weak typeof(self) weakSelf = self; [currentAlert dismissViewControllerAnimated:NO completion:nil]; UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:[VectorL10n accountErrorEmailWrongTitle] message:[VectorL10n accountErrorEmailWrongDescription] preferredStyle:UIAlertControllerStyleAlert]; [errorAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; } }]]; [errorAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddEmailAlert"]; [self presentViewController:errorAlert animated:YES completion:nil]; currentAlert = errorAlert; return; } // Dismiss the keyboard [newEmailTextField resignFirstResponder]; MXSession* session = self.mainSession; [self showAuthenticationIfNeededForAdding:kMX3PIDMediumEmail withSession:session completion:^(NSDictionary *authParams) { [self startActivityIndicator]; __block MX3PidAddSession *thirdPidAddSession; thirdPidAddSession = [session.threePidAddManager startAddEmailSessionWithEmail:self->newEmailTextField.text nextLink:nil success:^{ [self showValidationEmailDialogWithMessage:[VectorL10n accountEmailValidationMessage] for3PidAddSession:thirdPidAddSession threePidAddManager:session.threePidAddManager authenticationParameters:authParams]; } failure:^(NSError * _Nonnull error) { [self stopActivityIndicator]; MXLogDebug(@"[SettingsViewController] Failed to request email token"); // Translate the potential MX error. MXError *mxError = [[MXError alloc] initWithNSError:error]; if (mxError && ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse] || [mxError.errcode isEqualToString:kMXErrCodeStringServerNotTrusted])) { NSMutableDictionary *userInfo; if (error.userInfo) { userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; } else { userInfo = [NSMutableDictionary dictionary]; } userInfo[NSLocalizedFailureReasonErrorKey] = nil; if ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse]) { userInfo[NSLocalizedDescriptionKey] = [VectorL10n authEmailInUse]; userInfo[@"error"] = [VectorL10n authEmailInUse]; } else { userInfo[NSLocalizedDescriptionKey] = [VectorL10n authUntrustedIdServer]; userInfo[@"error"] = [VectorL10n authUntrustedIdServer]; } error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; } else if ([error.domain isEqualToString:MX3PidAddManagerErrorDomain] && error.code == MX3PidAddManagerErrorDomainIdentityServerRequired) { error = [NSError errorWithDomain:error.domain code:error.code userInfo:@{ NSLocalizedDescriptionKey: [VectorL10n authEmailIsRequired] }]; } // Notify user NSString *myUserId = session.myUser.userId; // TODO: Hanlde multi-account [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; }]; } - (IBAction)onAddNewPhone:(id)sender { // Ignore empty field if (!newPhoneNumberCell.mxkTextField.text.length) { // Disable the new phone edition if the text field is empty self.newPhoneEditingEnabled = NO; return; } // Phone check if (![[NBPhoneNumberUtil sharedInstance] isValidNumber:newPhoneNumber]) { [currentAlert dismissViewControllerAnimated:NO completion:nil]; __weak typeof(self) weakSelf = self; UIAlertController *errorAlert = [UIAlertController alertControllerWithTitle:[VectorL10n accountErrorMsisdnWrongTitle] message:[VectorL10n accountErrorMsisdnWrongDescription] preferredStyle:UIAlertControllerStyleAlert]; [errorAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; } }]]; [errorAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddMsisdnAlert"]; [self presentViewController:errorAlert animated:YES completion:nil]; currentAlert = errorAlert; return; } // Dismiss the keyboard [newPhoneNumberCell.mxkTextField resignFirstResponder]; MXSession* session = self.mainSession; NSString *e164 = [[NBPhoneNumberUtil sharedInstance] format:newPhoneNumber numberFormat:NBEPhoneNumberFormatE164 error:nil]; NSString *msisdn; if ([e164 hasPrefix:@"+"]) { msisdn = e164; } else if ([e164 hasPrefix:@"00"]) { msisdn = [NSString stringWithFormat:@"+%@", [e164 substringFromIndex:2]]; } NSString *countryCode = newPhoneNumberCell.isoCountryCode; [self showAuthenticationIfNeededForAdding:kMX3PIDMediumMSISDN withSession:session completion:^(NSDictionary *authParams) { [self startActivityIndicator]; __block MX3PidAddSession *new3Pid; new3Pid = [session.threePidAddManager startAddPhoneNumberSessionWithPhoneNumber:msisdn countryCode:countryCode success:^{ [self showValidationMsisdnDialogWithMessage:[VectorL10n accountMsisdnValidationMessage] for3PidAddSession:new3Pid threePidAddManager:session.threePidAddManager authenticationParameters:authParams]; } failure:^(NSError *error) { [self stopActivityIndicator]; MXLogDebug(@"[SettingsViewController] Failed to request msisdn token"); // Translate the potential MX error. MXError *mxError = [[MXError alloc] initWithNSError:error]; if (mxError && ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse] || [mxError.errcode isEqualToString:kMXErrCodeStringServerNotTrusted])) { NSMutableDictionary *userInfo; if (error.userInfo) { userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; } else { userInfo = [NSMutableDictionary dictionary]; } userInfo[NSLocalizedFailureReasonErrorKey] = nil; if ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse]) { userInfo[NSLocalizedDescriptionKey] = [VectorL10n authPhoneInUse]; userInfo[@"error"] = [VectorL10n authPhoneInUse]; } else { userInfo[NSLocalizedDescriptionKey] = [VectorL10n authUntrustedIdServer]; userInfo[@"error"] = [VectorL10n authUntrustedIdServer]; } error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; } else if ([error.domain isEqualToString:MX3PidAddManagerErrorDomain] && error.code == MX3PidAddManagerErrorDomainIdentityServerRequired) { error = [NSError errorWithDomain:error.domain code:error.code userInfo:@{ NSLocalizedDescriptionKey: [VectorL10n authPhoneIsRequired] }]; } // Notify user NSString *myUserId = session.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; }]; } - (void)updateSaveButtonStatus { if ([AppDelegate theDelegate].mxSessions.count > 0) { MXSession* session = self.mainSession; MXMyUser* myUser = session.myUser; BOOL saveButtonEnabled = (nil != newAvatarImage || shouldRemoveAvatarImage); if (!saveButtonEnabled) { if (newDisplayName) { saveButtonEnabled = ![myUser.displayname isEqualToString:newDisplayName]; } } self.navigationItem.rightBarButtonItem.enabled = saveButtonEnabled; } } - (void)onProfileAvatarTap:(UITapGestureRecognizer *)recognizer { SingleImagePickerPresenter *singleImagePickerPresenter = [[SingleImagePickerPresenter alloc] initWithSession:self.mainSession]; singleImagePickerPresenter.delegate = self; singleImagePickerPresenter.allowsRemoveImage = self.mainSession.myUser.avatarUrl != nil; NSIndexPath *indexPath = [_tableViewSections exactIndexPathForRowTag:USER_SETTINGS_PROFILE_PICTURE_INDEX sectionTag:SECTION_TAG_USER_SETTINGS]; if (indexPath) { UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; UIView *sourceView = cell; [singleImagePickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES]; self.imagePickerPresenter = singleImagePickerPresenter; } } - (void)onManageAccountTap { NSURL *url = [NSURL URLWithString: self.mainSession.homeserverWellknown.authentication.account]; if (url) { SSOAccountService *service = [[SSOAccountService alloc] initWithAccountURL:url]; SSOAuthenticationPresenter *presenter = [[SSOAuthenticationPresenter alloc] initWithSsoAuthenticationService:service]; presenter.delegate = self; self.ssoAuthenticationPresenter = presenter; [presenter presentForIdentityProvider:nil with:@"" from:self animated:YES]; } } // bwi: #5938 activate lab - (void)onElementVersionTap { if (!BWIBuildSettings.shared.settingsScreenShowLabSettings) { BWIBuildSettings.shared.settingsScreenShowLabSettings = true; [self updateSections]; MXWeakify(self); [currentAlert dismissViewControllerAnimated:NO completion:nil]; UIAlertController *successAlert = [UIAlertController alertControllerWithTitle:[BWIL10n bwiActivateLabsAlertTitle] message:nil preferredStyle:UIAlertControllerStyleAlert]; [successAlert addAction:[UIAlertAction actionWithTitle:[VectorL10n ok] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); self->currentAlert = nil; }]]; [self presentViewController:successAlert animated:YES completion:nil]; currentAlert = successAlert; } } - (void)showThemePicker { __weak typeof(self) weakSelf = self; __block UIAlertAction *autoAction, *lightAction, *darkAction/* bwi: 4744 , *blackAction*/; NSString *themePickerMessage; void (^actionBlock)(UIAlertAction *action) = ^(UIAlertAction * action) { if (weakSelf) { typeof(self) self = weakSelf; NSString *newTheme; if (action == autoAction) { newTheme = @"auto"; } else if (action == lightAction) { newTheme = @"light"; } else if (action == darkAction) { newTheme = @"dark"; } /* bwi: 4744 else if (action == blackAction) { newTheme = @"black"; } */ NSString *theme = RiotSettings.shared.userInterfaceTheme; if (newTheme && ![newTheme isEqualToString:theme]) { // Clear fake Riot Avatars based on the previous theme. [AvatarGenerator clear]; // The user wants to select this theme RiotSettings.shared.userInterfaceTheme = newTheme; ThemeService.shared.themeId = newTheme; // This is a hack to force the background colour of the container view of the navigation controller // This is needed only for hot theme update as the UIViewControllerWrapperView of the RioNavigationController is not updated self.view.superview.backgroundColor = ThemeService.shared.theme.backgroundColor; [self updateSections]; } } }; // Show "auto" only from iOS 11 autoAction = [UIAlertAction actionWithTitle:[VectorL10n settingsUiThemeAuto] style:UIAlertActionStyleDefault handler:actionBlock]; // Explain what is "auto" if (@available(iOS 13, *)) { // Observe application did become active for iOS appearance setting changes themePickerMessage = [VectorL10n settingsUiThemePickerMessageMatchSystemTheme]; } else { // Observe "Invert Colours" settings changes (available since iOS 11) themePickerMessage = [VectorL10n settingsUiThemePickerMessageInvertColours]; } lightAction = [UIAlertAction actionWithTitle:[VectorL10n settingsUiThemeLight] style:UIAlertActionStyleDefault handler:actionBlock]; darkAction = [UIAlertAction actionWithTitle:[VectorL10n settingsUiThemeDark] style:UIAlertActionStyleDefault handler:actionBlock]; /* bwi: 4744 blackAction = [UIAlertAction actionWithTitle:[VectorL10n settingsUiThemeBlack] style:UIAlertActionStyleDefault handler:actionBlock]; */ UIAlertController *themePicker = [UIAlertController alertControllerWithTitle:[VectorL10n settingsUiThemePickerTitle] message:themePickerMessage preferredStyle:UIAlertControllerStyleActionSheet]; if (autoAction) { [themePicker addAction:autoAction]; } [themePicker addAction:lightAction]; [themePicker addAction:darkAction]; /* bwi: 4744 [themePicker addAction:blackAction]; */ // Cancel button [themePicker addAction:[UIAlertAction actionWithTitle:[VectorL10n cancel] style:UIAlertActionStyleCancel handler:nil]]; NSIndexPath *indexPath = [_tableViewSections exactIndexPathForRowTag:USER_INTERFACE_THEME_INDEX sectionTag:SECTION_TAG_USER_INTERFACE]; if (indexPath) { UIView *fromCell = [self.tableView cellForRowAtIndexPath:indexPath]; [themePicker popoverPresentationController].sourceView = fromCell; [themePicker popoverPresentationController].sourceRect = fromCell.bounds; [self presentViewController:themePicker animated:YES completion:nil]; } } - (void)deactivateAccountAction { DeactivateAccountViewController *deactivateAccountViewController = [DeactivateAccountViewController instantiateWithMatrixSession:self.mainSession]; UINavigationController *navigationController = [[RiotNavigationController alloc] initWithRootViewController:deactivateAccountViewController]; navigationController.modalPresentationStyle = UIModalPresentationFormSheet; [self presentViewController:navigationController animated:YES completion:nil]; deactivateAccountViewController.delegate = self; self.deactivateAccountViewController = deactivateAccountViewController; } - (void)toggleShowRedacted:(UISwitch *)sender { [MXKAppSettings standardAppSettings].showRedactionsInRoomHistory = sender.isOn; } - (void)togglePresenceOfflineMode:(UISwitch *)sender { MXKAccount *account = MXKAccountManager.sharedManager.accounts.firstObject; if (sender.isOn) { account.preferredSyncPresence = MXPresenceOffline; } else { account.preferredSyncPresence = MXPresenceOnline; } } - (void)showDeveloperSettings { UIViewController *developerSettingsViewController = [DeveloperSettingsViewController makeViewControllerWithSession:self.mainSession]; [self pushViewController:developerSettingsViewController]; } - (void)showAccessibilityDeclaration { UIViewController *accessibilityDeclarationViewController = [AccessibilityDeclarationViewController makeViewController]; [self pushViewController:accessibilityDeclarationViewController]; } - (void)showPersonalStateSettings { UIViewController *personalStateViewController = [PersonalStateViewController makeViewControllerWithSession:self.mainSession]; [self pushViewController:personalStateViewController]; } - (void)showNotificationTimesSettings { UIViewController *notificationTimesViewController = [NotificationTimesViewController makeViewControllerWithSession:self.mainSession]; [self pushViewController:notificationTimesViewController]; } - (void)toggleEnableRoomMessageBubbles:(UISwitch *)sender { RiotSettings.shared.roomScreenEnableMessageBubbles = sender.isOn; [[RoomTimelineConfiguration shared] updateStyleWithIdentifier:RiotSettings.shared.roomTimelineStyleIdentifier]; // Close all room data sources // Be sure to use new room timeline style configurations MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; [roomDataSourceManager reset]; } - (void)toggleEnableAutoReportDecryptionErrors:(UISwitch *)sender { RiotSettings.shared.enableUISIAutoReporting = sender.isOn; } - (void)toggleEnableLiveLocationSharing:(UISwitch *)sender { RiotSettings.shared.enableLiveLocationSharing = sender.isOn; } // bwi: show netiquette infos in webview - (void)showNetiquetteInfos { // Get local HTML content in preferred language (default language is german, if preferred language is not available) NSString* htmlFile; if ([[self bwiGetPreferredLanguageCode] isEqual: @"en"]) { htmlFile = [[NSBundle mainBundle] pathForResource:BWIBuildSettings.shared.netiquetteEnHTML ofType:@"html" inDirectory:nil]; } else { htmlFile = [[NSBundle mainBundle] pathForResource:BWIBuildSettings.shared.netiquetteDeHTML ofType:@"html" inDirectory:nil]; } WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithLocalHTMLFile:htmlFile]; webViewViewController.title = BWIL10n.bwiSettingsNetiquette; [webViewViewController vc_setLargeTitleDisplayMode:UINavigationItemLargeTitleDisplayModeNever]; [self pushViewController:webViewViewController]; } #pragma mark - TextField listener - (IBAction)textFieldDidChange:(id)sender { UITextField* textField = (UITextField*)sender; if (textField.tag == USER_SETTINGS_DISPLAYNAME_INDEX) { // Remove white space from both ends newDisplayName = [textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; [self updateSaveButtonStatus]; } else if (textField.tag == USER_SETTINGS_ADD_PHONENUMBER_INDEX) { newPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:textField.text defaultRegion:newPhoneNumberCell.isoCountryCode error:nil]; [self formatNewPhoneNumber]; } } - (IBAction)textFieldDidEnd:(id)sender { UITextField* textField = (UITextField*)sender; // Disable the new email edition if the user leaves the text field empty if (textField.tag == USER_SETTINGS_ADD_EMAIL_INDEX && textField.text.length == 0 && !keepNewEmailEditing) { self.newEmailEditingEnabled = NO; } else if (textField.tag == USER_SETTINGS_ADD_PHONENUMBER_INDEX && textField.text.length == 0 && !keepNewPhoneNumberEditing && !newPhoneNumberCountryPicker) { // Disable the new phone edition if the user leaves the text field empty self.newPhoneEditingEnabled = NO; } } #pragma mark - UITextField delegate - (void)textFieldDidBeginEditing:(UITextField *)textField { if (textField.tag == USER_SETTINGS_DISPLAYNAME_INDEX) { textField.textAlignment = NSTextAlignmentLeft; } } - (void)textFieldDidEndEditing:(UITextField *)textField { if (textField.tag == USER_SETTINGS_DISPLAYNAME_INDEX) { textField.textAlignment = NSTextAlignmentRight; } } - (BOOL)textFieldShouldReturn:(UITextField *)textField { if (textField.tag == USER_SETTINGS_DISPLAYNAME_INDEX) { [textField resignFirstResponder]; } else if (textField.tag == USER_SETTINGS_ADD_EMAIL_INDEX) { [self onAddNewEmail:textField]; } return YES; } #pragma password update management - (void)displayPasswordAlert { self.changePasswordBridgePresenter = [[ChangePasswordCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; self.changePasswordBridgePresenter.delegate = self; [self.changePasswordBridgePresenter presentFrom:self animated:YES]; } // bwi: 4203 - (void)showMyQRCode { MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; MXMyUser* myUser = account.mxSession.myUser; UIImage* avatarImage = [bwiProfileImage image]; UIViewController *myQRCodeViewController; if( avatarImage ) { myQRCodeViewController = [MyQRCodeViewController createFromSwiftUIViewWithMatrixID: myUser.userId displayName: myUser.displayname avatarUIImage: bwiProfileImage.image]; } else { myQRCodeViewController = [MyQRCodeViewController createFromSwiftUIViewWithMatrixID: myUser.userId displayName: myUser.displayname]; } [[self.navigationController navigationBar] setPrefersLargeTitles:NO]; [self pushViewController:myQRCodeViewController]; } #pragma mark - MXKCountryPickerViewControllerDelegate - (void)countryPickerViewController:(MXKCountryPickerViewController *)countryPickerViewController didSelectCountry:(NSString *)isoCountryCode { if (countryPickerViewController.view.tag == SECTION_TAG_LOCAL_CONTACTS) { [MXKAppSettings standardAppSettings].phonebookCountryCode = isoCountryCode; } else if (countryPickerViewController.view.tag == SECTION_TAG_USER_SETTINGS) { if (newPhoneNumberCell) { newPhoneNumberCell.isoCountryCode = isoCountryCode; newPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:newPhoneNumberCell.mxkTextField.text defaultRegion:isoCountryCode error:nil]; [self formatNewPhoneNumber]; } } [countryPickerViewController withdrawViewControllerAnimated:YES completion:nil]; } #pragma mark - MXKCountryPickerViewControllerDelegate - (void)languagePickerViewController:(MXKLanguagePickerViewController *)languagePickerViewController didSelectLangugage:(NSString *)language { if (![language isEqualToString:[NSBundle mxk_language]] || (language == nil && [NSBundle mxk_language])) { [NSBundle mxk_setLanguage:language]; UIApplication.sharedApplication.accessibilityLanguage = language; // Store user settings NSUserDefaults *sharedUserDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; [sharedUserDefaults setObject:language forKey:@"appLanguage"]; // Do a reload in order to recompute strings in the new language // Note that "reloadMatrixSessions:NO" will reset room summaries [self startActivityIndicator]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ [[AppDelegate theDelegate] reloadMatrixSessions:NO]; }); } } #pragma mark - DeactivateAccountViewControllerDelegate - (void)deactivateAccountViewControllerDidDeactivateWithSuccess:(DeactivateAccountViewController *)deactivateAccountViewController { MXLogDebug(@"[SettingsViewController] Deactivate account with success"); [[AppDelegate theDelegate] logoutSendingRequestServer:NO completion:^(BOOL isLoggedOut) { MXLogDebug(@"[SettingsViewController] Complete clear user data after account deactivation"); }]; } - (void)deactivateAccountViewControllerDidCancel:(DeactivateAccountViewController *)deactivateAccountViewController { [deactivateAccountViewController dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - NotificationSettingsCoordinatorBridgePresenter - (void)showNotificationSettings: (NotificationSettingsScreen)screen API_AVAILABLE(ios(14.0)) { NotificationSettingsCoordinatorBridgePresenter *notificationSettingsBridgePresenter = [[NotificationSettingsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; notificationSettingsBridgePresenter.delegate = self; MXWeakify(self); [notificationSettingsBridgePresenter pushFrom:self.navigationController animated:YES screen:screen popCompletion:^{ MXStrongifyAndReturnIfNil(self); self.notificationSettingsBridgePresenter = nil; }]; self.notificationSettingsBridgePresenter = notificationSettingsBridgePresenter; } #pragma mark - NotificationSettingsCoordinatorBridgePresenterDelegate - (void)notificationSettingsCoordinatorBridgePresenterDelegateDidComplete:(NotificationSettingsCoordinatorBridgePresenter *)coordinatorBridgePresenter API_AVAILABLE(ios(14.0)) { [self.notificationSettingsBridgePresenter dismissWithAnimated:YES completion:nil]; self.notificationSettingsBridgePresenter = nil; } #pragma mark - SignOutFlowPresenterDelegate - (void)signOutFlowPresenterDidStartLoading:(SignOutFlowPresenter *)presenter { [self startActivityIndicator]; self.view.userInteractionEnabled = NO; self.signOutButton.enabled = NO; } - (void)signOutFlowPresenterDidStopLoading:(SignOutFlowPresenter *)presenter { [self stopActivityIndicator]; self.view.userInteractionEnabled = YES; self.signOutButton.enabled = YES; } - (void)signOutFlowPresenter:(SignOutFlowPresenter *)presenter didFailWith:(NSError *)error { [[AppDelegate theDelegate] showErrorAsAlert:error]; } #pragma mark - SingleImagePickerPresenterDelegate - (void)singleImagePickerPresenterDidCancel:(SingleImagePickerPresenter *)presenter { [presenter dismissWithAnimated:YES completion:nil]; self.imagePickerPresenter = nil; } - (void)singleImagePickerPresenter:(SingleImagePickerPresenter *)presenter didSelectImageData:(NSData *)imageData withUTI:(MXKUTI *)uti { [presenter dismissWithAnimated:YES completion:nil]; self.imagePickerPresenter = nil; NSData *finalImageData = imageData ? [Tools mediaConvertMaxImageData:imageData withUTI:uti] : nil; if( finalImageData != nil ) { newAvatarImage = [UIImage imageWithData:finalImageData]; NSLog(@"Reduced avantar image width = %.0f and height = %.0f", newAvatarImage.size.width, newAvatarImage.size.height); } else { newAvatarImage = nil; NSLog(@"Removed avantar image"); } [self updateSections]; } - (void)singleImagePickerPresenterDidRemoveImage:(SingleImagePickerPresenter *)presenter { [presenter dismissWithAnimated:YES completion:nil]; self.imagePickerPresenter = nil; shouldRemoveAvatarImage = true; [self updateSections]; [self.settingsDiscoveryTableViewSection reload]; [self updateSaveButtonStatus]; } #pragma mark - Identity server updates - (void)registerAccountDataDidChangeIdentityServerNotification { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAccountDataDidChangeIdentityServerNotification:) name:kMXSessionAccountDataDidChangeIdentityServerNotification object:nil]; } - (void)handleAccountDataDidChangeIdentityServerNotification:(NSNotification*)notification { [self refreshSettings]; } #pragma mark - SettingsDiscoveryTableViewSectionDelegate - (void)settingsDiscoveryTableViewSectionDidUpdate:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection { [self updateSections]; } - (MXKTableViewCell *)settingsDiscoveryTableViewSection:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection tableViewCellClass:(Class)tableViewCellClass forRow:(NSInteger)forRow { MXKTableViewCell *tableViewCell; if ([tableViewCellClass isEqual:[MXKTableViewCell class]]) { tableViewCell = [self getDefaultTableViewCell:self.tableView]; } else if ([tableViewCellClass isEqual:[MXKTableViewCellWithTextView class]]) { NSIndexPath *indexPath = [_tableViewSections exactIndexPathForRowTag:forRow sectionTag:SECTION_TAG_DISCOVERY]; if (indexPath) { tableViewCell = [self textViewCellForTableView:self.tableView atIndexPath:indexPath]; } } else if ([tableViewCellClass isEqual:[MXKTableViewCellWithButton class]]) { MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!cell) { cell = [[MXKTableViewCellWithButton alloc] init]; } else { // Fix https://github.com/vector-im/riot-ios/issues/1354 cell.mxkButton.titleLabel.text = nil; } cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; tableViewCell = cell; } else if ([tableViewCellClass isEqual:[MXKTableViewCellWithLabelAndSwitch class]]) { NSIndexPath *indexPath = [_tableViewSections exactIndexPathForRowTag:forRow sectionTag:SECTION_TAG_DISCOVERY]; if (indexPath) { tableViewCell = [self getLabelAndSwitchCell:self.tableView forIndexPath:indexPath]; } } return tableViewCell; } #pragma mark - SettingsDiscoveryViewModelCoordinatorDelegate - (void)settingsDiscoveryViewModel:(SettingsDiscoveryViewModel *)viewModel didSelectThreePidWith:(NSString *)medium and:(NSString *)address { SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter = [[SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession medium:medium adress:address]; MXWeakify(self); [discoveryThreePidDetailsPresenter pushFrom:self.navigationController animated:YES popCompletion:^{ MXStrongifyAndReturnIfNil(self); self.discoveryThreePidDetailsPresenter = nil; }]; self.discoveryThreePidDetailsPresenter = discoveryThreePidDetailsPresenter; } - (void)settingsDiscoveryViewModelDidTapAcceptIdentityServerTerms:(SettingsDiscoveryViewModel *)viewModel { MXSession *session = self.mainSession; if (!session.identityService.areAllTermsAgreed) { [self prepareIdentityServiceAndPresentTermsWithSession:session checkingAccessForContactsOnAccept:NO]; } } #pragma mark - Local Contacts Sync - (void)checkAccessForContacts { MXWeakify(self); // Check for contacts access, showing a pop-up if necessary. [MXKTools checkAccessForContacts:VectorL10n.contactsAddressBookPermissionDeniedAlertTitle withManualChangeMessage:VectorL10n.contactsAddressBookPermissionDeniedAlertMessage showPopUpInViewController:self completionHandler:^(BOOL granted) { MXStrongifyAndReturnIfNil(self); if (granted) { // When granted, local contacts can be shown. [MXKAppSettings standardAppSettings].syncLocalContacts = YES; [self updateSections]; } }]; } #pragma mark - Identity server - (void)showIdentityServerSettingsScreen { identityServerSettingsCoordinatorBridgePresenter = [[SettingsIdentityServerCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; [identityServerSettingsCoordinatorBridgePresenter pushFrom:self.navigationController animated:YES popCompletion:nil]; identityServerSettingsCoordinatorBridgePresenter.delegate = self; } - (void)prepareIdentityServiceAndPresentTermsWithSession:(MXSession *)session checkingAccessForContactsOnAccept:(BOOL)checkAccessForContacts { if (self.isPreparingIdentityService) { return; } self.isPreparingIdentityService = YES; self.serviceTermsModalShouldCheckAccessForContactsOnAccept = checkAccessForContacts; MXWeakify(self); // The preparation can take some time so indicate this to the user [self startActivityIndicator]; [session prepareIdentityServiceForTermsWithDefault:RiotSettings.shared.identityServerUrlString success:^(MXSession *session, NSString *baseURL, NSString *accessToken) { MXStrongifyAndReturnIfNil(self); [self stopActivityIndicator]; self.isPreparingIdentityService = NO; // Present the terms of the identity server. [self presentIdentityServerTermsWithSession:session baseURL:baseURL andAccessToken:accessToken]; } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); [self stopActivityIndicator]; self.isPreparingIdentityService = NO; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:VectorL10n.findYourContactsIdentityServiceError message:nil preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:VectorL10n.ok style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; [MXKAppSettings standardAppSettings].syncLocalContacts = NO; [self updateSections]; }]; } - (void)presentIdentityServerTermsWithSession:(MXSession*)mxSession baseURL:(NSString*)baseURL andAccessToken:(NSString*)accessToken { if (!mxSession || !baseURL || !accessToken || self.serviceTermsModalCoordinatorBridgePresenter.isPresenting) { return; } self.serviceTermsModalCoordinatorBridgePresenter = [[ServiceTermsModalCoordinatorBridgePresenter alloc] initWithSession:mxSession baseUrl:baseURL serviceType:MXServiceTypeIdentityService accessToken:accessToken]; self.serviceTermsModalCoordinatorBridgePresenter.delegate = self; [self.serviceTermsModalCoordinatorBridgePresenter presentFrom:self animated:YES]; } #pragma mark SettingsIdentityServerCoordinatorBridgePresenterDelegate - (void)settingsIdentityServerCoordinatorBridgePresenterDelegateDidComplete:(SettingsIdentityServerCoordinatorBridgePresenter *)coordinatorBridgePresenter { identityServerSettingsCoordinatorBridgePresenter = nil; [self refreshSettings]; } #pragma mark ServiceTermsModalCoordinatorBridgePresenterDelegate - (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidAccept:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter { [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{ [self.settingsDiscoveryTableViewSection reload]; if (self.serviceTermsModalShouldCheckAccessForContactsOnAccept) { [self checkAccessForContacts]; } }]; self.serviceTermsModalCoordinatorBridgePresenter = nil; } - (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidDecline:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter session:(MXSession *)session { // Terms weren't accepted: disable contacts toggle and refresh discovery [self updateSections]; [self.settingsDiscoveryTableViewSection reload]; [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; self.serviceTermsModalCoordinatorBridgePresenter = nil; } - (void)serviceTermsModalCoordinatorBridgePresenterDelegateDidClose:(ServiceTermsModalCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter { // Terms weren't accepted: disable contacts toggle and refresh discovery [self updateSections]; [self.settingsDiscoveryTableViewSection reload]; self.serviceTermsModalCoordinatorBridgePresenter = nil; } #pragma mark - TableViewSectionsDelegate - (void)tableViewSectionsDidUpdateSections:(TableViewSections *)sections { [self.tableView reloadData]; } #pragma mark - Timeline Additions - (Section*) updateTimeLine:(Section*)timelineSection { if (RiotSettings.shared.settingsScreenShowSimpleTimeLineOptions) { [timelineSection addRowWithTag:TIMELINE_SIMPLE_INDEX]; } else { if (BWIBuildSettings.shared.bwiShowMessageBubbleSettings) { [timelineSection addRowWithTag:USER_INTERFACE_TIMELINE_STYLE_INDEX]; } if (RiotSettings.shared.settingsScreenShowTimeStampOption) { [timelineSection addRowWithTag:TIMELINE_TIMESTAMP_INDEX]; } if (RiotSettings.shared.settingsScreenShowDeletedMessagesOption) { [timelineSection addRowWithTag:TIMELINE_DELETED_MESSAGES_INDEX]; } if (RiotSettings.shared.settingsScreenShowNameChangeOption) { [timelineSection addRowWithTag:TIMELINE_NAME_CHANGE_INDEX]; } if (RiotSettings.shared.settingsScreenShowChatEffectsOption) { [timelineSection addRowWithTag:TIMELINE_CHATEFFECTS_INDEX]; } if (RiotSettings.shared.settingsScreenShowRoomAvatarChangeOption) { [timelineSection addRowWithTag:TIMELINE_ROOM_AVATAR_INDEX]; } if (RiotSettings.shared.settingsScreenShowUserAvatarChangeOption) { [timelineSection addRowWithTag:TIMELINE_USER_AVATAR_INDEX]; } if (RiotSettings.shared.settingsScreenShowEnterRoomOption) { [timelineSection addRowWithTag:TIMELINE_ENTER_ROOM_INDEX]; } } return timelineSection; } - (UITableViewCell*) cellForTimeline:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath row:(NSInteger)row { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [self titleForTimeline:row]; labelAndSwitchCell.mxkSwitch.on = [self enabledForTimeLine:row]; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:[self actionForTimeLine:row] forControlEvents:UIControlEventTouchUpInside]; return labelAndSwitchCell; } - (NSString*) titleForTimeline:(NSInteger)row { switch (row) { case TIMELINE_SIMPLE_INDEX: return BWIL10n.settingsEnableSimpleTimeLine; case TIMELINE_TIMESTAMP_INDEX: return BWIL10n.settingsEnableTimeStamp; case TIMELINE_DELETED_MESSAGES_INDEX: return BWIL10n.settingsEnableDeletedMessages; case TIMELINE_NAME_CHANGE_INDEX: return BWIL10n.settingsEnableNameChange; case TIMELINE_CHATEFFECTS_INDEX: return BWIL10n.settingsEnableChatEffects; case TIMELINE_ROOM_AVATAR_INDEX: return BWIL10n.settingsEnableRoomAvatar; case TIMELINE_USER_AVATAR_INDEX: return BWIL10n.settingsEnableUserAvatar; case TIMELINE_ENTER_ROOM_INDEX: return BWIL10n.settingsEnableEnterRoom; default: return @""; } } - (BOOL) enabledForTimeLine:(NSInteger)row { switch (row) { case TIMELINE_SIMPLE_INDEX: return RiotSettings.shared.settingSimpleTimelineEnabled; case TIMELINE_TIMESTAMP_INDEX: return RiotSettings.shared.settingTimeStampEnabled; case TIMELINE_DELETED_MESSAGES_INDEX: return RiotSettings.shared.settingDeletedMessagesEnabled; case TIMELINE_NAME_CHANGE_INDEX: return RiotSettings.shared.settingNameChangeEnabled; case TIMELINE_CHATEFFECTS_INDEX: return RiotSettings.shared.settingChatEffectsEnabled; case TIMELINE_ROOM_AVATAR_INDEX: return RiotSettings.shared.settingRoomAvatarChangeEnabled; case TIMELINE_USER_AVATAR_INDEX: return RiotSettings.shared.settingUserAvatarChangeEnabled; case TIMELINE_ENTER_ROOM_INDEX: return RiotSettings.shared.settingEnterRoomEnabled; default: return NO; } } - (SEL) actionForTimeLine:(NSInteger)row { switch (row) { case TIMELINE_SIMPLE_INDEX: return @selector(toggleTimeLineSimpleIndex:); case TIMELINE_TIMESTAMP_INDEX: return @selector(toggleTimeLineTimeStamp:); case TIMELINE_DELETED_MESSAGES_INDEX: return @selector(toggleTimeLineDeletedMessages:); case TIMELINE_NAME_CHANGE_INDEX: return @selector(toggleTimeLineNameChange:); case TIMELINE_CHATEFFECTS_INDEX: return @selector(toggleTimeLineChatEffects:); case TIMELINE_ROOM_AVATAR_INDEX: return @selector(toggleTimeLineRoomAvatar:); case TIMELINE_USER_AVATAR_INDEX: return @selector(toggleTimeLineUserAvatar:); case TIMELINE_ENTER_ROOM_INDEX: return @selector(toggleTimeLineEnterRoom:); default: return nil; } } - (void)toggleTimeLineSimpleIndex:(UISwitch*)theSwitch { RiotSettings.shared.settingSimpleTimelineEnabled = theSwitch.isOn; } - (void)toggleTimeLineTimeStamp:(UISwitch*)theSwitch { RiotSettings.shared.settingTimeStampEnabled = theSwitch.isOn; } - (void)toggleTimeLineDeletedMessages:(UISwitch*)theSwitch { RiotSettings.shared.settingDeletedMessagesEnabled = theSwitch.isOn; } - (void)toggleTimeLineNameChange:(UISwitch*)theSwitch { RiotSettings.shared.settingNameChangeEnabled = theSwitch.isOn; } - (void)toggleTimeLineChatEffects:(UISwitch*)theSwitch { RiotSettings.shared.settingChatEffectsEnabled = theSwitch.isOn; } - (void)toggleTimeLineRoomAvatar:(UISwitch*)theSwitch { RiotSettings.shared.settingRoomAvatarChangeEnabled = theSwitch.isOn; } - (void)toggleTimeLineUserAvatar:(UISwitch*)theSwitch { RiotSettings.shared.settingUserAvatarChangeEnabled = theSwitch.isOn; } - (void)toggleTimeLineEnterRoom:(UISwitch*)theSwitch { RiotSettings.shared.settingEnterRoomEnabled = theSwitch.isOn; } #pragma mark - bwi Messenger Additions - (NSString*) bwiDowntimeCellText { NSString *downtimeText = [NSString stringWithFormat:@"\n%@\n", [[[ServerDowntimeDefaultService alloc] init] downtimeText]]; return downtimeText; } - (UIColor*) bwiDowntimeColor { return [[[ServerDowntimeDefaultService alloc] init] downtimeColor]; } - (UIColor*) bwiDowntimeTextColor { return [[[ServerDowntimeDefaultService alloc] init] downtimeTextColor]; } - (void) bwiShowIgnoredUsersScreen { UIViewController *ignoredUsersViewController = [IgnoredUsersViewController makeViewControllerWithSession:self.mainSession]; [self pushViewController:ignoredUsersViewController]; } - (BOOL) isNetiquetteSettingsShown { if ([BWIBuildSettings.shared bwiShowNetiquetteInfos]) { if ([[self bwiGetPreferredLanguageCode] isEqual: @"en"]) { if ([[NSBundle mainBundle] pathForResource:BWIBuildSettings.shared.netiquetteEnHTML ofType:@"html" inDirectory:nil] != nil) { return true; } else { return false; } } else { if ([[NSBundle mainBundle] pathForResource:BWIBuildSettings.shared.netiquetteDeHTML ofType:@"html" inDirectory:nil] != nil) { return true; } else { return false; } } } else { return false; } } - (NSString*) bwiGetPreferredLanguageCode { return ([[NSLocale componentsFromLocaleIdentifier:[[NSLocale preferredLanguages] firstObject]] objectForKey:@"kCFLocaleLanguageCodeKey"]); } - (UITableViewCell*) cellForDowntimeIgnore:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath row:(NSInteger)row { MXKTableViewCellWithButton *downtimeIgnoreBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!downtimeIgnoreBtnCell) { downtimeIgnoreBtnCell = [[MXKTableViewCellWithButton alloc] init]; } else { downtimeIgnoreBtnCell.mxkButton.titleLabel.text = nil; } NSString *btnTitle = [BWIL10n blockingDowntimeAlertIgnoreButton]; [downtimeIgnoreBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; [downtimeIgnoreBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; [downtimeIgnoreBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; downtimeIgnoreBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; [downtimeIgnoreBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; [downtimeIgnoreBtnCell.mxkButton addTarget:self action:@selector(ignoreDowntime:) forControlEvents:UIControlEventTouchUpInside]; downtimeIgnoreBtnCell.mxkButton.accessibilityIdentifier = nil; return downtimeIgnoreBtnCell; } - (void) ignoreDowntime:(id)sender { [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"ServerDownTimeBlockingKey"]; [[[ServerDowntimeDefaultService alloc] init] setManuallyIgnored]; [self updateSections]; } - (UITableViewCell*) cellForPersonalNotesVisibility:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath row:(NSInteger)row { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [BWIL10n bwiSettingsEnableNotesRoomTitle]; labelAndSwitchCell.mxkSwitch.on = [[[PersonalNotesSettings alloc] init] personalNotesVisible]; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePersonalNotesVisibility:) forControlEvents:UIControlEventTouchUpInside]; return labelAndSwitchCell; } - (void) togglePersonalNotesVisibility:(UISwitch* )sender { PersonalNotesSettings* settings = [[PersonalNotesSettings alloc] init]; settings.personalNotesVisible = sender.on; } - (UITableViewCell*) toggleWYSIWYGComposer:(UITableView*)tableView indexPath:(NSIndexPath*)indexPath row:(NSInteger)row { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = [BWIL10n bwiSettingsEnableWysiwygComposer]; labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableWysiwygComposer; labelAndSwitchCell.mxkSwitch.thumbTintColor = ThemeService.shared.theme.backgroundColor; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableWysiwygComposerFeature:) forControlEvents:UIControlEventTouchUpInside]; return labelAndSwitchCell; } - (void) customBackButtonAction { // bwi: #5799 show alert if user has changed the avatar without saving it if ((nil != newAvatarImage || shouldRemoveAvatarImage) && !isSavingInProgress) { MXWeakify(self); [currentAlert dismissViewControllerAnimated:NO completion:nil]; UIAlertController *saveChangesAlert = [UIAlertController alertControllerWithTitle:[BWIL10n settingsSaveChangesAlertTitle] message:nil preferredStyle:UIAlertControllerStyleAlert]; [saveChangesAlert addAction:[UIAlertAction actionWithTitle:[BWIL10n settingsSaveChangesAlertDoNotSaveButton] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); dispatch_async(dispatch_get_main_queue(), ^{ [self.navigationController popViewControllerAnimated:true]; }); self->currentAlert = nil; }]]; [saveChangesAlert addAction:[UIAlertAction actionWithTitle:[BWIL10n settingsSaveChangesAlertSaveButton] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { MXStrongifyAndReturnIfNil(self); dispatch_async(dispatch_get_main_queue(), ^{ [self onSave:nil]; }); self->currentAlert = nil; }]]; [self presentViewController:saveChangesAlert animated:YES completion:nil]; currentAlert = saveChangesAlert; } else { [self.navigationController popViewControllerAnimated:true]; } } #pragma mark - ThreadsBetaCoordinatorBridgePresenterDelegate - (void)threadsBetaCoordinatorBridgePresenterDelegateDidTapEnable:(ThreadsBetaCoordinatorBridgePresenter *)coordinatorBridgePresenter { MXWeakify(self); [self.threadsBetaBridgePresenter dismissWithAnimated:YES completion:^{ MXStrongifyAndReturnIfNil(self); [self enableThreads:YES]; }]; } - (void)threadsBetaCoordinatorBridgePresenterDelegateDidTapCancel:(ThreadsBetaCoordinatorBridgePresenter *)coordinatorBridgePresenter { MXWeakify(self); [self.threadsBetaBridgePresenter dismissWithAnimated:YES completion:^{ MXStrongifyAndReturnIfNil(self); [self updateSections]; }]; } #pragma mark - ChangePasswordCoordinatorBridgePresenterDelegate - (void)changePasswordCoordinatorBridgePresenterDidComplete:(ChangePasswordCoordinatorBridgePresenter *)bridgePresenter { [bridgePresenter dismissWithAnimated:YES completion:^{ self.changePasswordBridgePresenter = nil; }]; } - (void)changePasswordCoordinatorBridgePresenterDidCancel:(ChangePasswordCoordinatorBridgePresenter *)bridgePresenter { [bridgePresenter dismissWithAnimated:YES completion:nil]; self.changePasswordBridgePresenter = nil; } #pragma mark - User sessions management - (void)showUserSessionsFlow { if (!self.mainSession) { MXLogError(@"[SettingsViewController] Cannot show user sessions flow, no user session available"); return; } if (!self.navigationController) { MXLogError(@"[SettingsViewController] Cannot show user sessions flow, no navigation controller available"); return; } UserSessionsFlowCoordinatorBridgePresenter *userSessionsFlowCoordinatorBridgePresenter = [[UserSessionsFlowCoordinatorBridgePresenter alloc] initWithMxSession:self.mainSession]; MXWeakify(self); userSessionsFlowCoordinatorBridgePresenter.completion = ^{ MXStrongifyAndReturnIfNil(self); self.userSessionsFlowCoordinatorBridgePresenter = nil; }; self.userSessionsFlowCoordinatorBridgePresenter = userSessionsFlowCoordinatorBridgePresenter; [self.userSessionsFlowCoordinatorBridgePresenter pushFrom:self.navigationController animated:YES]; } #pragma mark - SSOAuthenticationPresenterDelegate - (void)ssoAuthenticationPresenterDidCancel:(SSOAuthenticationPresenter *)presenter { self.ssoAuthenticationPresenter = nil; MXLogDebug(@"OIDC account management complete.") } - (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationDidFailWithError:(NSError *)error { self.ssoAuthenticationPresenter = nil; MXLogError(@"OIDC account management failed.") } - (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationSucceededWithToken:(NSString *)token usingIdentityProvider:(SSOIdentityProvider *)identityProvider { self.ssoAuthenticationPresenter = nil; MXLogWarning(@"Unexpected callback after OIDC account management.") } @end