123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068 |
- //
- // SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
- // SPDX-License-Identifier: GPL-3.0-or-later
- //
- import UIKit
- import NextcloudKit
- import SafariServices
- import SwiftUI
- import ReplayKit
- import SDWebImage
- enum SettingsSection: Int {
- case kSettingsSectionUser = 0
- case kSettingsSectionUserStatus
- case kSettingsSectionAccountSettings
- case kSettingsSectionOtherAccounts
- case kSettingsSectionConfiguration
- case kSettingsSectionAdvanced
- case kSettingsSectionAbout
- }
- enum AccountSettingsOptions: Int {
- case kAccountSettingsReadStatusPrivacy = 0
- case kAccountSettingsTypingPrivacy
- case kAccountSettingsContactsSync
- }
- enum ConfigurationSectionOption: Int {
- case kConfigurationSectionOptionVideo = 0
- case kConfigurationSectionOptionRecents
- }
- enum AdvancedSectionOption: Int {
- case kAdvancedSectionOptionDiagnostics = 0
- case kAdvancedSectionOptionCachedImages
- case kAdvancedSectionOptionCachedFiles
- case kAdvancedSectionOptionCallFromOldAccount
- }
- enum AboutSection: Int {
- case kAboutSectionPrivacy = 0
- case kAboutSectionSourceCode
- }
- class SettingsTableViewController: UITableViewController, UITextFieldDelegate, UserStatusViewDelegate, CallsFromOldAccountViewControllerDelegate {
- let kPhoneTextFieldTag = 99
- let kUserSettingsCellIdentifier = "UserSettingsCellIdentifier"
- let kUserSettingsTableCellNibName = "UserSettingsTableViewCell"
- let kAccountCellIdentifier: String = "AccountCellIdentifier"
- let iconConfiguration = UIImage.SymbolConfiguration(pointSize: 18)
- var activeUserStatus: NCUserStatus?
- var readStatusSwitch = UISwitch()
- var typingIndicatorSwitch = UISwitch()
- var contactSyncSwitch = UISwitch()
- var setPhoneAction: UIAlertAction?
- var phoneUtil = NBPhoneNumberUtil()
- var includeInRecentsSwitch = UISwitch()
- var totalImageCacheSize = 0
- var totalFileCacheSize = 0
- var activeAccount = NCDatabaseManager.sharedInstance().activeAccount()
- var inactiveAccounts = NCDatabaseManager.sharedInstance().inactiveAccounts()
- var serverCapabilities: ServerCapabilities? {
- // Since NCDatabaseManager already caches the capabilities, we don't need a lazy var here
- NCDatabaseManager.sharedInstance().serverCapabilities(forAccountId: activeAccount.accountId)
- }
- lazy var profilePictures: [String: UIImage] = {
- var result: [String: UIImage] = [:]
- for account in NCDatabaseManager.sharedInstance().allAccounts() {
- guard let account = account as? TalkAccount else {
- continue
- }
- if let image = NCAPIController.sharedInstance().userProfileImage(for: account, with: self.traitCollection.userInterfaceStyle) {
- result[account.accountId] = image
- }
- }
- return result
- }()
- @IBOutlet weak var cancelButton: UIBarButtonItem!
- override func viewDidLoad() {
- super.viewDidLoad()
- self.navigationItem.title = NSLocalizedString("Settings", comment: "")
- self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: NCAppBranding.themeTextColor()]
- self.navigationController?.navigationBar.tintColor = NCAppBranding.themeColor()
- self.tabBarController?.tabBar.tintColor = NCAppBranding.themeColor()
- self.cancelButton.tintColor = NCAppBranding.themeTextColor()
- contactSyncSwitch.frame = .zero
- contactSyncSwitch.addTarget(self, action: #selector(contactSyncValueChanged(_:)), for: .valueChanged)
- readStatusSwitch.frame = .zero
- readStatusSwitch.addTarget(self, action: #selector(readStatusValueChanged(_:)), for: .valueChanged)
- includeInRecentsSwitch.frame = .zero
- includeInRecentsSwitch.addTarget(self, action: #selector(includeInRecentsValueChanged(_:)), for: .valueChanged)
- typingIndicatorSwitch.frame = .zero
- typingIndicatorSwitch.addTarget(self, action: #selector(typingIndicatorValueChanged(_:)), for: .valueChanged)
- let themeColor: UIColor = NCAppBranding.themeColor()
- let themeTextColor: UIColor = NCAppBranding.themeTextColor()
- let appearance = UINavigationBarAppearance()
- appearance.configureWithOpaqueBackground()
- appearance.titleTextAttributes = [.foregroundColor: themeTextColor]
- appearance.backgroundColor = themeColor
- self.navigationItem.standardAppearance = appearance
- self.navigationItem.compactAppearance = appearance
- self.navigationItem.scrollEdgeAppearance = appearance
- tableView.register(UINib(nibName: kUserSettingsTableCellNibName, bundle: nil), forCellReuseIdentifier: kUserSettingsCellIdentifier)
- NotificationCenter.default.addObserver(self, selector: #selector(appStateHasChanged(notification:)), name: NSNotification.Name.NCAppStateHasChanged, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(contactsHaveBeenUpdated(notification:)), name: NSNotification.Name.NCContactsManagerContactsUpdated, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(contactsAccessHasBeenUpdated(notification:)), name: NSNotification.Name.NCContactsManagerContactsAccessUpdated, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(userProfileImageUpdated), name: NSNotification.Name.NCUserProfileImageUpdated, object: nil)
- self.updateTotalImageCacheSize()
- self.updateTotalFileCacheSize()
- self.adaptInterfaceForAppState(appState: NCConnectionController.sharedInstance().appState)
- }
- override func didReceiveMemoryWarning() {
- super.didReceiveMemoryWarning()
- }
- @IBAction func cancelButtonPressed(_ sender: Any) {
- self.dismiss(animated: true, completion: nil)
- }
- func getSettingsSections() -> [Int] {
- var sections = [Int]()
- // Active user section
- sections.append(SettingsSection.kSettingsSectionUser.rawValue)
- // User status section
- if serverCapabilities?.userStatus ?? false {
- sections.append(SettingsSection.kSettingsSectionUserStatus.rawValue)
- }
- // Account settings section
- sections.append(SettingsSection.kSettingsSectionAccountSettings.rawValue)
- // Other accounts section
- if !inactiveAccounts.isEmpty {
- sections.append(SettingsSection.kSettingsSectionOtherAccounts.rawValue)
- }
- // Configuration section
- sections.append(SettingsSection.kSettingsSectionConfiguration.rawValue)
- // Advanced section
- sections.append(SettingsSection.kSettingsSectionAdvanced.rawValue)
- // About section
- sections.append(SettingsSection.kSettingsSectionAbout.rawValue)
- return sections
- }
- func getAccountSettingsSectionOptions() -> [Int] {
- var options = [Int]()
- // Read status privacy setting
- if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityChatReadStatus) {
- options.append(AccountSettingsOptions.kAccountSettingsReadStatusPrivacy.rawValue)
- }
- // Typing indicator privacy setting
- if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityTypingIndicators) {
- options.append(AccountSettingsOptions.kAccountSettingsTypingPrivacy.rawValue)
- }
- // Contacts sync
- if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityPhonebookSearch) {
- options.append(AccountSettingsOptions.kAccountSettingsContactsSync.rawValue)
- }
- return options
- }
- func getConfigurationSectionOptions() -> [Int] {
- var options = [Int]()
- // Video quality
- options.append(ConfigurationSectionOption.kConfigurationSectionOptionVideo.rawValue)
- // Calls in recents
- options.append(ConfigurationSectionOption.kConfigurationSectionOptionRecents.rawValue)
- return options
- }
- func getAdvancedSectionOptions() -> [Int] {
- var options = [Int]()
- // Diagnostics
- options.append(AdvancedSectionOption.kAdvancedSectionOptionDiagnostics.rawValue)
- // Caches
- options.append(AdvancedSectionOption.kAdvancedSectionOptionCachedImages.rawValue)
- options.append(AdvancedSectionOption.kAdvancedSectionOptionCachedFiles.rawValue)
- // Received calls from old accounts
- if NCSettingsController.sharedInstance().didReceiveCallsFromOldAccount() {
- options.append(AdvancedSectionOption.kAdvancedSectionOptionCallFromOldAccount.rawValue)
- }
- return options
- }
- func getAboutSectionOptions() -> [Int] {
- var options = [Int]()
- // Privacy
- options.append(AboutSection.kAboutSectionPrivacy.rawValue)
- // Source code
- if !isBrandedApp.boolValue {
- options.append(AboutSection.kAboutSectionSourceCode.rawValue)
- }
- return options
- }
- func getSectionForSettingsSection(section: SettingsSection) -> Int {
- let section = getSettingsSections().firstIndex(of: section.rawValue)
- return section ?? 0
- }
- func getIndexPathForConfigurationOption(option: ConfigurationSectionOption) -> IndexPath {
- let section = getSectionForSettingsSection(section: SettingsSection.kSettingsSectionConfiguration)
- let row = getConfigurationSectionOptions().firstIndex(of: option.rawValue)
- return IndexPath(row: row ?? 0, section: section)
- }
- // MARK: - User Profile
- func refreshUserProfile() {
- NCSettingsController.sharedInstance().getUserProfile(forAccountId: activeAccount.accountId) { _ in
- self.tableView.reloadData()
- }
- self.getActiveUserStatus()
- }
- func getActiveUserStatus() {
- NCAPIController.sharedInstance().getUserStatus(for: activeAccount) { userStatus, error in
- if let userStatus = userStatus, error == nil {
- self.activeUserStatus = NCUserStatus(dictionary: userStatus)
- self.tableView.reloadData()
- }
- }
- }
- // MARK: - Notifications
- @objc func appStateHasChanged(notification: NSNotification) {
- let appState = notification.userInfo?["appState"]
- if let rawAppState = appState as? Int, let appState = AppState(rawValue: rawAppState) {
- self.adaptInterfaceForAppState(appState: appState)
- }
- }
- @objc func contactsHaveBeenUpdated(notification: NSNotification) {
- DispatchQueue.main.async {
- self.activeAccount = NCDatabaseManager.sharedInstance().activeAccount()
- self.tableView.reloadData()
- }
- }
- @objc func contactsAccessHasBeenUpdated(notification: NSNotification) {
- DispatchQueue.main.async {
- self.activeAccount = NCDatabaseManager.sharedInstance().activeAccount()
- self.tableView.reloadData()
- }
- }
- @objc func userProfileImageUpdated(notification: NSNotification) {
- self.tableView.reloadSections(IndexSet(integer: SettingsSection.kSettingsSectionUser.rawValue), with: .none)
- }
- // MARK: - User Interface
- func adaptInterfaceForAppState(appState: AppState) {
- switch appState {
- case .ready:
- refreshUserProfile()
- default:
- break
- }
- }
- // MARK: - Profile actions
- func userProfilePressed() {
- let userProfileVC = UserProfileTableViewController(withAccount: activeAccount)
- self.navigationController?.pushViewController(userProfileVC, animated: true)
- }
- // MARK: - User Status (SwiftUI)
- func presentUserStatusOptions() {
- if let activeUserStatus = activeUserStatus {
- var userStatusView = UserStatusSwiftUIView(userStatus: activeUserStatus)
- userStatusView.delegate = self
- let hostingController = UIHostingController(rootView: userStatusView)
- self.present(hostingController, animated: true)
- }
- }
- func userStatusViewDidDisappear() {
- self.getActiveUserStatus()
- }
- // MARK: - User phone number
- func checkUserPhoneNumber() {
- NCSettingsController.sharedInstance().getUserProfile(forAccountId: activeAccount.accountId) { _ in
- if self.activeAccount.phone.isEmpty {
- self.presentSetPhoneNumberDialog()
- }
- }
- }
- func presentSetPhoneNumberDialog() {
- let alertTitle = NSLocalizedString("Phone number", comment: "")
- let alertMessage = NSLocalizedString("You can set your phone number so other users will be able to find you", comment: "")
- let setPhoneNumberDialog = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
- setPhoneNumberDialog.addTextField { [self] textField in
- let location = NSLocale.current.regionCode
- let countryCode = phoneUtil.getCountryCode(forRegion: location)
- if let countryCode = countryCode {
- textField.text = "+\(countryCode)"
- }
- if let exampleNumber = try? phoneUtil.getExampleNumber(location) {
- textField.placeholder = try? phoneUtil.format(exampleNumber, numberFormat: NBEPhoneNumberFormat.INTERNATIONAL)
- }
- textField.keyboardType = .phonePad
- textField.delegate = self
- textField.tag = kPhoneTextFieldTag
- }
- setPhoneAction = UIAlertAction(title: NSLocalizedString("Set", comment: ""), style: .default, handler: { _ in
- let phoneNumber = setPhoneNumberDialog.textFields?[0].text
- NCAPIController.sharedInstance().setUserProfileField(kUserProfilePhone, withValue: phoneNumber, for: self.activeAccount) { error, _ in
- if error != nil {
- if let phoneNumber = phoneNumber {
- self.presentPhoneNumberErrorDialog(phoneNumber: phoneNumber)
- }
- print("Error setting phone number ", error ?? "")
- } else {
- NotificationPresenter.shared().present(text: NSLocalizedString("Phone number set successfully", comment: ""), dismissAfterDelay: 5.0, includedStyle: .success)
- }
- self.refreshUserProfile()
- }
- })
- if let setPhoneAction = setPhoneAction {
- setPhoneAction.isEnabled = false
- setPhoneNumberDialog.addAction(setPhoneAction)
- }
- let cancelAction = UIAlertAction(title: NSLocalizedString("Skip", comment: ""), style: .default) { _ in
- self.refreshUserProfile()
- }
- setPhoneNumberDialog.addAction(cancelAction)
- self.present(setPhoneNumberDialog, animated: true, completion: nil)
- }
- func presentPhoneNumberErrorDialog(phoneNumber: String) {
- let alertTitle = NSLocalizedString("Could not set phone number", comment: "")
- var alertMessage = NSLocalizedString("An error occurred while setting phone number", comment: "")
- let failedPhoneNumber = try? phoneUtil.parse(phoneNumber, defaultRegion: nil)
- if let formattedPhoneNumber = try? phoneUtil.format(failedPhoneNumber, numberFormat: NBEPhoneNumberFormat.INTERNATIONAL) {
- alertMessage = NSLocalizedString("An error occurred while setting \(formattedPhoneNumber) as phone number", comment: "")
- }
- let failedPhoneNumberDialog = UIAlertController(
- title: alertTitle,
- message: alertMessage,
- preferredStyle: .alert)
- let retryAction = UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default) { _ in
- self.presentSetPhoneNumberDialog()
- }
- failedPhoneNumberDialog.addAction(retryAction)
- let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .default, handler: nil)
- failedPhoneNumberDialog.addAction(cancelAction)
- self.present(failedPhoneNumberDialog, animated: true, completion: nil)
- }
- // MARK: UITextField delegate
- func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
- if textField.tag == kPhoneTextFieldTag {
- let inputPhoneNumber = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
- let phoneNumber = try? phoneUtil.parse(inputPhoneNumber, defaultRegion: nil)
- setPhoneAction?.isEnabled = phoneUtil.isValidNumber(phoneNumber)
- }
- return true
- }
- // MARK: - Configuration
- func presentVideoResoultionsSelector() {
- let videoConfIndexPath = self.getIndexPathForConfigurationOption(option: ConfigurationSectionOption.kConfigurationSectionOptionVideo)
- let videoResolutions = NCSettingsController.sharedInstance().videoSettingsModel.availableVideoResolutions()
- let storedResolution = NCSettingsController.sharedInstance().videoSettingsModel.currentVideoResolutionSettingFromStore()
- let optionsActionSheet = UIAlertController(title: NSLocalizedString("Video quality", comment: ""), message: nil, preferredStyle: .actionSheet)
- for resolution in videoResolutions {
- let readableResolution = NCSettingsController.sharedInstance().videoSettingsModel.readableResolution(resolution)
- let isStoredResolution = resolution == storedResolution
- let action = UIAlertAction(title: readableResolution, style: .default) { _ in
- NCSettingsController.sharedInstance().videoSettingsModel.storeVideoResolutionSetting(resolution)
- self.tableView.beginUpdates()
- self.tableView.reloadRows(at: [videoConfIndexPath], with: .none)
- self.tableView.endUpdates()
- }
- if isStoredResolution {
- action.setValue(UIImage(named: "checkmark")?.withRenderingMode(_: .alwaysOriginal), forKey: "image")
- }
- optionsActionSheet.addAction(action)
- }
- optionsActionSheet.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
- // Presentation on iPads
- optionsActionSheet.popoverPresentationController?.sourceView = self.tableView
- optionsActionSheet.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: videoConfIndexPath)
- self.present(optionsActionSheet, animated: true, completion: nil)
- }
- @objc func contactSyncValueChanged(_ sender: Any?) {
- NCSettingsController.sharedInstance().setContactSync(contactSyncSwitch.isOn)
- if contactSyncSwitch.isOn {
- if !NCContactsManager.sharedInstance().isContactAccessDetermined() {
- NCContactsManager.sharedInstance().requestContactsAccess { granted in
- if granted {
- self.checkUserPhoneNumber()
- NCContactsManager.sharedInstance().searchInServer(forAddressBookContacts: true)
- }
- }
- } else if NCContactsManager.sharedInstance().isContactAccessAuthorized() {
- self.checkUserPhoneNumber()
- NCContactsManager.sharedInstance().searchInServer(forAddressBookContacts: true)
- }
- } else {
- NCContactsManager.sharedInstance().removeStoredContacts()
- }
- // Reload to update configuration section footer
- self.tableView.reloadData()
- }
- @objc func readStatusValueChanged(_ sender: Any?) {
- readStatusSwitch.isEnabled = false
- NCAPIController.sharedInstance().setReadStatusPrivacySettingEnabled(!readStatusSwitch.isOn, for: activeAccount) { error in
- if error == nil {
- NCSettingsController.sharedInstance().getCapabilitiesForAccountId(self.activeAccount.accountId) { error in
- if error == nil {
- self.readStatusSwitch.isEnabled = true
- self.tableView.reloadData()
- } else {
- self.showReadStatusModificationError()
- }
- }
- } else {
- self.showReadStatusModificationError()
- }
- }
- }
- func showReadStatusModificationError() {
- readStatusSwitch.isEnabled = true
- self.tableView.reloadData()
- let errorDialog = UIAlertController(
- title: NSLocalizedString("An error occurred changing read status setting", comment: ""),
- message: nil,
- preferredStyle: .alert)
- let okAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)
- errorDialog.addAction(okAction)
- self.present(errorDialog, animated: true, completion: nil)
- }
- @objc func typingIndicatorValueChanged(_ sender: Any?) {
- typingIndicatorSwitch.isEnabled = false
- NCAPIController.sharedInstance().setTypingPrivacySettingEnabled(!typingIndicatorSwitch.isOn, for: activeAccount) { error in
- if error == nil {
- NCSettingsController.sharedInstance().getCapabilitiesForAccountId(self.activeAccount.accountId) { error in
- if error == nil {
- self.typingIndicatorSwitch.isEnabled = true
- self.tableView.reloadData()
- } else {
- self.showTypeIndicatorModificationError()
- }
- }
- } else {
- self.showTypeIndicatorModificationError()
- }
- }
- }
- func showTypeIndicatorModificationError() {
- self.typingIndicatorSwitch.isEnabled = true
- self.tableView.reloadData()
- let errorDialog = UIAlertController(
- title: NSLocalizedString("An error occurred changing typing privacy setting", comment: ""),
- message: nil,
- preferredStyle: .alert)
- let okAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)
- errorDialog.addAction(okAction)
- self.present(errorDialog, animated: true, completion: nil)
- }
- @objc func includeInRecentsValueChanged(_ sender: Any?) {
- NCUserDefaults.setIncludeCallsInRecentsEnabled(includeInRecentsSwitch.isOn)
- CallKitManager.sharedInstance().setDefaultProviderConfiguration()
- }
- // MARK: - Advanced actions
- func diagnosticsPressed() {
- let diagnosticsVC = DiagnosticsTableViewController(withAccount: activeAccount)
- self.navigationController?.pushViewController(diagnosticsVC, animated: true)
- }
- func cachedImagesPressed() {
- let clearCacheDialog = UIAlertController(
- title: NSLocalizedString("Clear cache", comment: ""),
- message: NSLocalizedString("Do you really want to clear the image cache?", comment: ""),
- preferredStyle: .alert)
- let clearAction = UIAlertAction(title: NSLocalizedString("Clear cache", comment: ""), style: .destructive) { _ in
- NCImageSessionManager.shared.cache.removeAllCachedResponses()
- SDImageCache.shared.clearMemory()
- SDImageCache.shared.clearDisk {
- self.updateTotalImageCacheSize()
- self.tableView.reloadData()
- }
- }
- clearCacheDialog.addAction(clearAction)
- let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)
- clearCacheDialog.addAction(cancelAction)
- self.present(clearCacheDialog, animated: true, completion: nil)
- }
- func cachedFilesPressed() {
- let clearCacheDialog = UIAlertController(
- title: NSLocalizedString("Clear cache", comment: ""),
- message: NSLocalizedString("Do you really want to clear the file cache?", comment: ""),
- preferredStyle: .alert)
- let clearAction = UIAlertAction(title: NSLocalizedString("Clear cache", comment: ""), style: .destructive) { _ in
- let fileController = NCChatFileController()
- let talkAccounts = NCDatabaseManager.sharedInstance().allAccounts()
- if let talkAccounts = talkAccounts as? [TalkAccount] {
- for account in talkAccounts {
- fileController.clearDownloadDirectory(for: account)
- }
- }
- self.updateTotalFileCacheSize()
- self.tableView.reloadData()
- }
- clearCacheDialog.addAction(clearAction)
- let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)
- clearCacheDialog.addAction(cancelAction)
- self.present(clearCacheDialog, animated: true, completion: nil)
- }
- func callsFromOldAccountPressed() {
- let vc = CallsFromOldAccountViewController()
- vc.delegate = self
- self.navigationController?.pushViewController(vc, animated: true)
- }
- func callsFromOldAccountWarningAcknowledged() {
- self.tableView.reloadData()
- }
- // MARK: - Table view data source
- override func numberOfSections(in tableView: UITableView) -> Int {
- return getSettingsSections().count
- }
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- let sections = getSettingsSections()
- let settingsSection = sections[section]
- switch settingsSection {
- case SettingsSection.kSettingsSectionUser.rawValue:
- return 1
- case SettingsSection.kSettingsSectionUserStatus.rawValue:
- return 1
- case SettingsSection.kSettingsSectionAccountSettings.rawValue:
- return getAccountSettingsSectionOptions().count
- case SettingsSection.kSettingsSectionConfiguration.rawValue:
- return getConfigurationSectionOptions().count
- case SettingsSection.kSettingsSectionAdvanced.rawValue:
- return getAdvancedSectionOptions().count
- case SettingsSection.kSettingsSectionAbout.rawValue:
- return getAboutSectionOptions().count
- case SettingsSection.kSettingsSectionOtherAccounts.rawValue:
- return inactiveAccounts.count
- default:
- break
- }
- return 1
- }
- override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
- let sections = getSettingsSections()
- let settingsSection = sections[section]
- switch settingsSection {
- case SettingsSection.kSettingsSectionOtherAccounts.rawValue:
- return NSLocalizedString("Other Accounts", comment: "")
- case SettingsSection.kSettingsSectionConfiguration.rawValue:
- return NSLocalizedString("Configuration", comment: "")
- case SettingsSection.kSettingsSectionAdvanced.rawValue:
- return NSLocalizedString("Advanced", comment: "")
- case SettingsSection.kSettingsSectionAbout.rawValue:
- return NSLocalizedString("About", comment: "")
- default:
- break
- }
- return nil
- }
- override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
- let sections = getSettingsSections()
- let settingsSection = sections[section]
- if settingsSection == SettingsSection.kSettingsSectionAbout.rawValue {
- let appName = (Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String)!
- return "\(appName) \(NCAppBranding.getAppVersionString())\n\(copyright)"
- }
- if settingsSection == SettingsSection.kSettingsSectionAccountSettings.rawValue && contactSyncSwitch.isOn {
- if NCContactsManager.sharedInstance().isContactAccessDetermined() && !NCContactsManager.sharedInstance().isContactAccessAuthorized() {
- return NSLocalizedString("Contact access has been denied", comment: "")
- }
- if activeAccount.lastContactSync > 0 {
- let lastUpdate = Date(timeIntervalSince1970: TimeInterval(activeAccount.lastContactSync))
- let dateFormatter = DateFormatter()
- dateFormatter.dateStyle = .medium
- dateFormatter.timeStyle = .short
- return NSLocalizedString("Last sync", comment: "") + ": " + dateFormatter.string(from: lastUpdate)
- }
- }
- if settingsSection == SettingsSection.kSettingsSectionUser.rawValue && contactSyncSwitch.isOn {
- if activeAccount.phone.isEmpty {
- let missingPhoneString = NSLocalizedString("Missing phone number information", comment: "")
- return "⚠ " + missingPhoneString
- }
- }
- return nil
- }
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- var cell = UITableViewCell()
- let sections = getSettingsSections()
- let settingsSection = sections[indexPath.section]
- switch settingsSection {
- case SettingsSection.kSettingsSectionUser.rawValue:
- guard let cell = tableView.dequeueReusableCell(withIdentifier: kUserSettingsCellIdentifier, for: indexPath) as? UserSettingsTableViewCell else { return UITableViewCell() }
- cell.userDisplayNameLabel.text = activeAccount.userDisplayName
- let accountServer = activeAccount.server
- cell.serverAddressLabel.text = accountServer.replacingOccurrences(of: "https://", with: "")
- cell.userImageView.image = self.getProfilePicture(for: activeAccount)
- cell.accessoryType = .disclosureIndicator
- return cell
- case SettingsSection.kSettingsSectionUserStatus.rawValue:
- cell = UITableViewCell(style: .subtitle, reuseIdentifier: "UserStatusCellIdentifier")
- if activeUserStatus != nil {
- cell.textLabel?.text = activeUserStatus!.readableUserStatus()
- let statusMessage = activeUserStatus!.readableUserStatusMessage()
- if !statusMessage.isEmpty {
- cell.textLabel?.text = statusMessage
- }
- if activeUserStatus!.status == kUserStatusDND {
- cell.detailTextLabel?.text = NSLocalizedString("All notifications are muted", comment: "")
- cell.detailTextLabel?.numberOfLines = 0
- cell.detailTextLabel?.textColor = .secondaryLabel
- }
- let statusImage = activeUserStatus!.getSFUserStatusIcon()
- cell.imageView?.image = statusImage
- } else {
- cell.textLabel?.text = NSLocalizedString("Fetching status …", comment: "")
- }
- return cell
- case SettingsSection.kSettingsSectionAccountSettings.rawValue:
- cell = userSettingsCell(for: indexPath)
- case SettingsSection.kSettingsSectionOtherAccounts.rawValue:
- cell = userAccountsCell(for: indexPath)
- case SettingsSection.kSettingsSectionConfiguration.rawValue:
- cell = sectionConfigurationCell(for: indexPath)
- case SettingsSection.kSettingsSectionAdvanced.rawValue:
- cell = advancedCell(for: indexPath)
- case SettingsSection.kSettingsSectionAbout.rawValue:
- cell = sectionAboutCell(for: indexPath)
- default:
- break
- }
- return cell
- }
- func didSelectOtherAccountSectionCell(for indexPath: IndexPath) {
- if let account = inactiveAccounts[indexPath.row] as? TalkAccount {
- NCSettingsController.sharedInstance().setActiveAccountWithAccountId(account.accountId)
- }
- }
- func didSelectAccountSettingsSectionCell(for indexPath: IndexPath) {
- let options = getAccountSettingsSectionOptions()
- let option = options[indexPath.row]
- switch option {
- case AccountSettingsOptions.kAccountSettingsContactsSync.rawValue:
- NCContactsManager.sharedInstance().searchInServer(forAddressBookContacts: true)
- default:
- break
- }
- }
- func didSelectSettingsSectionCell(for indexPath: IndexPath) {
- let options = getConfigurationSectionOptions()
- let option = options[indexPath.row]
- switch option {
- case ConfigurationSectionOption.kConfigurationSectionOptionVideo.rawValue:
- self.presentVideoResoultionsSelector()
- default:
- break
- }
- }
- func didSelectAdvancedSectionCell(for indexPath: IndexPath) {
- let options = getAdvancedSectionOptions()
- let option = options[indexPath.row]
- switch option {
- case AdvancedSectionOption.kAdvancedSectionOptionDiagnostics.rawValue:
- self.diagnosticsPressed()
- case AdvancedSectionOption.kAdvancedSectionOptionCachedImages.rawValue:
- self.cachedImagesPressed()
- case AdvancedSectionOption.kAdvancedSectionOptionCachedFiles.rawValue:
- self.cachedFilesPressed()
- case AdvancedSectionOption.kAdvancedSectionOptionCallFromOldAccount.rawValue:
- self.callsFromOldAccountPressed()
- default:
- break
- }
- }
- func didSelectAboutSectionCell(for indexPath: IndexPath) {
- let options = getAboutSectionOptions()
- let option = options[indexPath.row]
- switch option {
- case AboutSection.kAboutSectionPrivacy.rawValue:
- if let url = URL(string: privacyURL), ["http", "https"].contains(url.scheme?.lowercased() ?? "") {
- let safariVC = SFSafariViewController(url: url)
- self.present(safariVC, animated: true, completion: nil)
- }
- case AboutSection.kAboutSectionSourceCode.rawValue:
- let safariVC = SFSafariViewController(url: URL(string: "https://github.com/nextcloud/talk-ios")!)
- self.present(safariVC, animated: true, completion: nil)
- default:
- break
- }
- }
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- let sections = getSettingsSections()
- let settingsSection = sections[indexPath.section]
- switch settingsSection {
- case SettingsSection.kSettingsSectionUser.rawValue:
- self.userProfilePressed()
- case SettingsSection.kSettingsSectionUserStatus.rawValue:
- self.presentUserStatusOptions()
- case SettingsSection.kSettingsSectionAccountSettings.rawValue:
- self.didSelectAccountSettingsSectionCell(for: indexPath)
- case SettingsSection.kSettingsSectionOtherAccounts.rawValue:
- self.didSelectOtherAccountSectionCell(for: indexPath)
- case SettingsSection.kSettingsSectionConfiguration.rawValue:
- self.didSelectSettingsSectionCell(for: indexPath)
- case SettingsSection.kSettingsSectionAdvanced.rawValue:
- self.didSelectAdvancedSectionCell(for: indexPath)
- case SettingsSection.kSettingsSectionAbout.rawValue:
- didSelectAboutSectionCell(for: indexPath)
- default:
- break
- }
- self.tableView.deselectRow(at: indexPath, animated: true)
- }
- }
- private extension UITableViewCell {
- func setSettingsImage(image: UIImage?, renderingMode: UIImage.RenderingMode = .alwaysTemplate) {
- // Render all images to a size of 20x20 so all cells have the same width for the imageView
- self.imageView?.image = NCUtils.renderAspectImage(image: image, ofSize: .init(width: 20, height: 20), centerImage: true)?.withRenderingMode(renderingMode)
- self.imageView?.tintColor = .secondaryLabel
- self.imageView?.contentMode = .scaleAspectFit
- }
- }
- extension SettingsTableViewController {
- // Cell configuration for every section
- func userSettingsCell(for indexPath: IndexPath) -> UITableViewCell {
- let readStatusCellIdentifier = "ReadStatusCellIdentifier"
- let typingIndicatorCellIdentifier = "TypingIndicatorCellIdentifier"
- let contactsSyncCellIdentifier = "ContactsSyncCellIdentifier"
- let options = getAccountSettingsSectionOptions()
- let option = options[indexPath.row]
- var cell = UITableViewCell()
- switch option {
- case AccountSettingsOptions.kAccountSettingsReadStatusPrivacy.rawValue:
- cell = tableView.dequeueReusableCell(withIdentifier: readStatusCellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: readStatusCellIdentifier)
- cell.textLabel?.text = NSLocalizedString("Read status", comment: "")
- cell.selectionStyle = .none
- cell.setSettingsImage(image: UIImage(named: "check-all"))
- cell.accessoryView = readStatusSwitch
- readStatusSwitch.isOn = !(serverCapabilities?.readStatusPrivacy ?? true)
- case AccountSettingsOptions.kAccountSettingsTypingPrivacy.rawValue:
- cell = tableView.dequeueReusableCell(withIdentifier: typingIndicatorCellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: typingIndicatorCellIdentifier)
- cell.textLabel?.text = NSLocalizedString("Typing indicator", comment: "")
- cell.selectionStyle = .none
- cell.setSettingsImage(image: UIImage(systemName: "rectangle.and.pencil.and.ellipsis")?.applyingSymbolConfiguration(iconConfiguration))
- cell.accessoryView = typingIndicatorSwitch
- typingIndicatorSwitch.isOn = !(serverCapabilities?.typingPrivacy ?? true)
- let externalSignalingController = NCSettingsController.sharedInstance().externalSignalingController(forAccountId: activeAccount.accountId)
- let externalSignalingServerUsed = externalSignalingController != nil
- if !externalSignalingServerUsed {
- cell.detailTextLabel?.text = NSLocalizedString("Typing indicators are only available when using a high performance backend (HPB)", comment: "")
- } else {
- cell.detailTextLabel?.text = nil
- }
- case AccountSettingsOptions.kAccountSettingsContactsSync.rawValue:
- cell = tableView.dequeueReusableCell(withIdentifier: contactsSyncCellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: contactsSyncCellIdentifier)
- cell.textLabel?.text = NSLocalizedString("Phone number integration", comment: "")
- cell.detailTextLabel?.text = NSLocalizedString("Match system contacts", comment: "")
- cell.selectionStyle = contactSyncSwitch.isOn ? .default : .none
- cell.setSettingsImage(image: UIImage(systemName: "iphone")?.applyingSymbolConfiguration(iconConfiguration))
- cell.accessoryView = contactSyncSwitch
- contactSyncSwitch.isOn = NCSettingsController.sharedInstance().isContactSyncEnabled()
- default:
- break
- }
- cell.textLabel?.numberOfLines = 0
- cell.detailTextLabel?.numberOfLines = 0
- cell.detailTextLabel?.textColor = .secondaryLabel
- return cell
- }
- func userAccountsCell(for indexPath: IndexPath) -> UITableViewCell {
- guard let account = inactiveAccounts[indexPath.row] as? TalkAccount else { return UITableViewCell() }
- let accountServer = account.server.replacingOccurrences(of: "https://", with: "")
- let cell = tableView.dequeueReusableCell(withIdentifier: kAccountCellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: kAccountCellIdentifier)
- cell.textLabel?.text = account.userDisplayName
- cell.detailTextLabel?.text = accountServer
- cell.detailTextLabel?.textColor = .secondaryLabel
- cell.imageView?.image = nil
- if let accountImage = self.getProfilePicture(for: account) {
- cell.setSettingsImage(image: NCUtils.roundedImage(fromImage: accountImage), renderingMode: .alwaysOriginal)
- }
- cell.accessoryView = nil
- if account.unreadBadgeNumber > 0 {
- let badgeView = RoundedNumberView()
- badgeView.highlightType = .important
- badgeView.number = account.unreadBadgeNumber
- cell.accessoryView = badgeView
- }
- return cell
- }
- func sectionConfigurationCell(for indexPath: IndexPath) -> UITableViewCell {
- let videoConfigurationCellIdentifier = "VideoConfigurationCellIdentifier"
- let options = getConfigurationSectionOptions()
- let option = options[indexPath.row]
- var cell = UITableViewCell()
- switch option {
- case ConfigurationSectionOption.kConfigurationSectionOptionVideo.rawValue:
- cell = UITableViewCell(style: .default, reuseIdentifier: videoConfigurationCellIdentifier)
- cell.textLabel?.text = NSLocalizedString("Video quality", comment: "")
- cell.setSettingsImage(image: UIImage(systemName: "video")?.applyingSymbolConfiguration(iconConfiguration))
- let resolution = NCSettingsController.sharedInstance().videoSettingsModel.currentVideoResolutionSettingFromStore()
- let resolutionLabel = UILabel()
- resolutionLabel.text = NCSettingsController.sharedInstance().videoSettingsModel.readableResolution(resolution)
- resolutionLabel.textColor = .secondaryLabel
- resolutionLabel.sizeToFit()
- cell.accessoryView = resolutionLabel
- case ConfigurationSectionOption.kConfigurationSectionOptionRecents.rawValue:
- cell = UITableViewCell(style: .default, reuseIdentifier: videoConfigurationCellIdentifier)
- cell.textLabel?.text = NSLocalizedString("Include calls in call history", comment: "")
- cell.setSettingsImage(image: UIImage(systemName: "clock.arrow.circlepath")?.applyingSymbolConfiguration(iconConfiguration))
- cell.selectionStyle = .none
- cell.accessoryView = includeInRecentsSwitch
- includeInRecentsSwitch.isOn = NCUserDefaults.includeCallsInRecents()
- default:
- break
- }
- cell.textLabel?.numberOfLines = 0
- return cell
- }
- func advancedCell(for indexPath: IndexPath) -> UITableViewCell {
- let advancedCellDisclosureIdentifier = "AdvancedCellDisclosureIdentifier"
- let advancedCellValue1Identifier = "AdvancedCellType1Identifier"
- let options = getAdvancedSectionOptions()
- let option = options[indexPath.row]
- var cell = UITableViewCell()
- switch option {
- case AdvancedSectionOption.kAdvancedSectionOptionDiagnostics.rawValue:
- cell = UITableViewCell(style: .default, reuseIdentifier: advancedCellDisclosureIdentifier)
- cell.textLabel?.text = NSLocalizedString("Diagnostics", comment: "")
- cell.textLabel?.numberOfLines = 0
- cell.setSettingsImage(image: UIImage(systemName: "gear")?.applyingSymbolConfiguration(iconConfiguration))
- cell.accessoryType = .disclosureIndicator
- case AdvancedSectionOption.kAdvancedSectionOptionCallFromOldAccount.rawValue:
- cell = UITableViewCell(style: .default, reuseIdentifier: advancedCellDisclosureIdentifier)
- cell.textLabel?.text = NSLocalizedString("Calls from old accounts", comment: "")
- cell.textLabel?.numberOfLines = 0
- cell.setSettingsImage(image: UIImage(systemName: "exclamationmark.triangle.fill")?.applyingSymbolConfiguration(iconConfiguration))
- cell.accessoryType = .disclosureIndicator
- case AdvancedSectionOption.kAdvancedSectionOptionCachedImages.rawValue:
- let byteFormatter = ByteCountFormatter()
- byteFormatter.allowedUnits = [.useMB]
- byteFormatter.countStyle = .file
- cell = UITableViewCell(style: .default, reuseIdentifier: advancedCellValue1Identifier)
- cell.textLabel?.text = NSLocalizedString("Cached images", comment: "")
- cell.textLabel?.numberOfLines = 0
- cell.setSettingsImage(image: UIImage(systemName: "photo")?.applyingSymbolConfiguration(iconConfiguration))
- let byteCounterLabel = UILabel()
- byteCounterLabel.text = byteFormatter.string(fromByteCount: Int64(self.totalImageCacheSize))
- byteCounterLabel.textColor = .secondaryLabel
- byteCounterLabel.sizeToFit()
- cell.accessoryView = byteCounterLabel
- case AdvancedSectionOption.kAdvancedSectionOptionCachedFiles.rawValue:
- let byteFormatter = ByteCountFormatter()
- byteFormatter.allowedUnits = [.useMB]
- byteFormatter.countStyle = .file
- cell = UITableViewCell(style: .default, reuseIdentifier: advancedCellValue1Identifier)
- cell.textLabel?.text = NSLocalizedString("Cached files", comment: "")
- cell.textLabel?.numberOfLines = 0
- cell.setSettingsImage(image: UIImage(systemName: "doc")?.applyingSymbolConfiguration(iconConfiguration))
- let byteCounterLabel = UILabel()
- byteCounterLabel.text = byteFormatter.string(fromByteCount: Int64(self.totalFileCacheSize))
- byteCounterLabel.textColor = .secondaryLabel
- byteCounterLabel.sizeToFit()
- cell.accessoryView = byteCounterLabel
- default:
- break
- }
- return cell
- }
- func sectionAboutCell(for indexPath: IndexPath) -> UITableViewCell {
- let privacyCellIdentifier = "PrivacyCellIdentifier"
- let sourceCodeCellIdentifier = "SourceCodeCellIdentifier"
- let options = getAboutSectionOptions()
- let option = options[indexPath.row]
- var cell = UITableViewCell()
- switch option {
- case AboutSection.kAboutSectionPrivacy.rawValue:
- cell =
- UITableViewCell(style: .default, reuseIdentifier: privacyCellIdentifier)
- cell.textLabel?.text = NSLocalizedString("Privacy", comment: "")
- cell.setSettingsImage(image: UIImage(systemName: "lock.shield")?.applyingSymbolConfiguration(iconConfiguration))
- case AboutSection.kAboutSectionSourceCode.rawValue:
- cell =
- UITableViewCell(style: .default, reuseIdentifier: sourceCodeCellIdentifier)
- cell.textLabel?.text = NSLocalizedString("Get source code", comment: "")
- cell.setSettingsImage(image: UIImage(named: "github"))
- default:
- break
- }
- cell.textLabel?.numberOfLines = 0
- return cell
- }
- // UIImage should be optional because userProfileImage (objC) can return a nil value
- func getProfilePicture(for account: TalkAccount) -> UIImage? {
- if let avatar = self.profilePictures[account.accountId] {
- return avatar
- }
- return NCAPIController.sharedInstance().userProfileImage(for: account, with: self.traitCollection.userInterfaceStyle)
- }
- func updateTotalImageCacheSize() {
- let imageCacheSize = NCImageSessionManager.shared.cache.currentDiskUsage
- let sdImageCacheSize = SDImageCache.shared.totalDiskSize()
- self.totalImageCacheSize = imageCacheSize + Int(sdImageCacheSize)
- }
- func updateTotalFileCacheSize() {
- self.totalFileCacheSize = 0
- let fileController = NCChatFileController()
- let talkAccounts = NCDatabaseManager.sharedInstance().allAccounts()
- if let talkAccounts = talkAccounts as? [TalkAccount] {
- for account in talkAccounts {
- self.totalFileCacheSize += Int(fileController.getDiskUsage(for: account))
- }
- }
- }
- }
|