diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index 666ea440e..ca3cbcc23 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -196,6 +196,7 @@ B20462E42440A4A600367A57 /* AutodelOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20462E32440A4A600367A57 /* AutodelOverviewViewController.swift */; }; B20462E62440C99600367A57 /* AutodelOptionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20462E52440C99600367A57 /* AutodelOptionsViewController.swift */; }; B206C2AB2B7B8088003ACBE6 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B206C2AA2B7B8088003ACBE6 /* MapViewController.swift */; }; + B20A907B2D05112800F44463 /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20A907A2D05112700F44463 /* NotificationsViewController.swift */; }; B21005DB23383664004C70C5 /* EmailOptionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21005DA23383664004C70C5 /* EmailOptionsViewController.swift */; }; B2172F3C29C125F2002C289E /* AdvancedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2172F3B29C125F2002C289E /* AdvancedViewController.swift */; }; B259D64329B771D5008FB706 /* BackupTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B259D64229B771D5008FB706 /* BackupTransferViewController.swift */; }; @@ -516,6 +517,7 @@ B209B2042B10139700FBBECF /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/InfoPlist.strings; sourceTree = ""; }; B209B2052B10139700FBBECF /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; B209B2062B10139700FBBECF /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Localizable.stringsdict; sourceTree = ""; }; + B20A907A2D05112700F44463 /* NotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = ""; }; B21005DA23383664004C70C5 /* EmailOptionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailOptionsViewController.swift; sourceTree = ""; }; B2172F3B29C125F2002C289E /* AdvancedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedViewController.swift; sourceTree = ""; }; B2537DD625E2F92F0010D739 /* ckb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ckb; path = ckb.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1106,6 +1108,7 @@ D8CF2DDB2CDD110F001C2352 /* Proxy */, 78E45E3921D3CFBC00D4B15E /* SettingsViewController.swift */, B2D4B63A29C38D1900B47DA8 /* ChatsAndMediaViewController.swift */, + B20A907A2D05112700F44463 /* NotificationsViewController.swift */, B2172F3B29C125F2002C289E /* AdvancedViewController.swift */, AEE6EC472283045D00EDC689 /* SelfProfileViewController.swift */, B21005DA23383664004C70C5 /* EmailOptionsViewController.swift */, @@ -1699,6 +1702,7 @@ 70B8882E2091B8550074812E /* ContactCell.swift in Sources */, 305961CD2346125100C80F33 /* UIEdgeInsets+Extensions.swift in Sources */, 30EF7324252FF15F00E2C54A /* MessageLabel.swift in Sources */, + B20A907B2D05112800F44463 /* NotificationsViewController.swift in Sources */, 30C0D49D237C4908008E2A0E /* CertificateCheckController.swift in Sources */, 3080A034277DE30100E74565 /* NSNotification+Extensions.swift in Sources */, 3080A01B277DDB8A00E74565 /* InputPlugin.swift in Sources */, diff --git a/deltachat-ios/Controller/Settings/NotificationsViewController.swift b/deltachat-ios/Controller/Settings/NotificationsViewController.swift new file mode 100644 index 000000000..7e6b9ca00 --- /dev/null +++ b/deltachat-ios/Controller/Settings/NotificationsViewController.swift @@ -0,0 +1,248 @@ +import UIKit +import DcCore +import Intents + +internal final class NotificationsViewController: UITableViewController { + + private struct SectionConfigs { + let headerTitle: String? + let footerTitle: String? + let cells: [UITableViewCell] + } + + private enum CellTags: Int { + case blockedContacts + case receiptConfirmation + case exportBackup + case autodel + case mediaQuality + case downloadOnDemand + } + + private var dcContext: DcContext + internal let dcAccounts: DcAccounts + var progressAlertHandler: ProgressAlertHandler? + + // MARK: - cells + + private lazy var blockedContactsCell: UITableViewCell = { + let cell = UITableViewCell(style: .default, reuseIdentifier: nil) + cell.tag = CellTags.blockedContacts.rawValue + cell.textLabel?.text = String.localized("pref_blocked_contacts") + cell.accessoryType = .disclosureIndicator + return cell + }() + + func autodelSummary() -> String { + let delDeviceAfter = dcContext.getConfigInt("delete_device_after") + let delServerAfter = dcContext.getConfigInt("delete_server_after") + if delDeviceAfter==0 && delServerAfter==0 { + return String.localized("never") + } else { + return String.localized("on") + } + } + + private lazy var autodelCell: UITableViewCell = { + let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) + cell.tag = CellTags.autodel.rawValue + cell.textLabel?.text = String.localized("delete_old_messages") + cell.accessoryType = .disclosureIndicator + cell.detailTextLabel?.text = autodelSummary() + return cell + }() + + private lazy var mediaQualityCell: UITableViewCell = { + let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) + cell.tag = CellTags.mediaQuality.rawValue + cell.textLabel?.text = String.localized("pref_outgoing_media_quality") + cell.accessoryType = .disclosureIndicator + cell.detailTextLabel?.text = MediaQualityViewController.getValString(val: dcContext.getConfigInt("media_quality")) + return cell + }() + + private lazy var downloadOnDemandCell: UITableViewCell = { + let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) + cell.tag = CellTags.downloadOnDemand.rawValue + cell.textLabel?.text = String.localized("auto_download_messages") + cell.accessoryType = .disclosureIndicator + cell.detailTextLabel?.text = DownloadOnDemandViewController.getValString(val: dcContext.getConfigInt("download_limit")) + return cell + }() + + private lazy var receiptConfirmationSwitch: UISwitch = { + let switchControl = UISwitch() + switchControl.isOn = dcContext.mdnsEnabled + switchControl.addTarget(self, action: #selector(handleReceiptConfirmationToggle(_:)), for: .valueChanged) + return switchControl + }() + + private lazy var receiptConfirmationCell: UITableViewCell = { + let cell = UITableViewCell(style: .default, reuseIdentifier: nil) + cell.tag = CellTags.receiptConfirmation.rawValue + cell.textLabel?.text = String.localized("pref_read_receipts") + cell.accessoryView = receiptConfirmationSwitch + cell.selectionStyle = .none + return cell + }() + + private lazy var exportBackupCell: ActionCell = { + let cell = ActionCell() + cell.tag = CellTags.exportBackup.rawValue + cell.actionTitle = String.localized("export_backup_desktop") + return cell + }() + + private lazy var sections: [SectionConfigs] = { + let preferencesSection = SectionConfigs( + headerTitle: nil, + footerTitle: String.localized("pref_read_receipts_explain"), + cells: [blockedContactsCell, mediaQualityCell, downloadOnDemandCell, + autodelCell, receiptConfirmationCell] + ) + let exportBackupSection = SectionConfigs( + headerTitle: nil, + footerTitle: String.localized("pref_backup_explain"), + cells: [exportBackupCell] + ) + return [preferencesSection, exportBackupSection] + }() + + init(dcAccounts: DcAccounts) { + self.dcContext = dcAccounts.getSelected() + self.dcAccounts = dcAccounts + super.init(style: .grouped) + hidesBottomBarWhenPushed = true + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - lifecycle + override func viewDidLoad() { + super.viewDidLoad() + title = String.localized("pref_chats_and_media") + tableView.rowHeight = UITableView.automaticDimension + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + updateCells() + } + + // MARK: - UITableViewDelegate + UITableViewDatasource + override func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return sections[section].cells.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return sections[indexPath.section].cells[indexPath.row] + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let cell = tableView.cellForRow(at: indexPath), let cellTag = CellTags(rawValue: cell.tag) else { + safe_fatalError() + return + } + tableView.deselectRow(at: indexPath, animated: false) + + switch cellTag { + case .blockedContacts: showBlockedContacts() + case .autodel: showAutodelOptions() + case .mediaQuality: showMediaQuality() + case .downloadOnDemand: showDownloadOnDemand() + case .receiptConfirmation: break + case .exportBackup: + Utils.authenticateDeviceOwner(reason: String.localized("pref_backup_explain")) { [weak self] in + self?.createBackup() + } + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return sections[section].headerTitle + } + + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + return sections[section].footerTitle + } + + // MARK: - actions + + private func createBackup() { + let alert = UIAlertController(title: String.localized("pref_backup_export_explain"), message: nil, preferredStyle: .safeActionSheet) + alert.addAction(UIAlertAction(title: String.localized("pref_backup_export_start_button"), style: .default, handler: { [weak self] _ in + guard let self else { return } + + let progressHandler = ProgressAlertHandler(dcAccounts: self.dcAccounts, notification: Event.importExportProgress) { [weak self] in + guard let self else { return } + + let alert = UIAlertController( + title: String.localized("backup_successful"), + message: String.localizedStringWithFormat(String.localized("backup_successful_explain_ios"), "\(String.localized("Files")) ➔ Delta Chat"), + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil)) + self.present(alert, animated: true, completion: nil) + } + progressHandler.dataSource = self + + self.progressAlertHandler = progressHandler + + self.dismiss(animated: true, completion: nil) + self.startImex(what: DC_IMEX_EXPORT_BACKUP) + })) + alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) + } + + @objc private func handleReceiptConfirmationToggle(_ sender: UISwitch) { + dcContext.mdnsEnabled = sender.isOn + } + + // MARK: - updates + private func startImex(what: Int32, passphrase: String? = nil) { + let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) + if !documents.isEmpty { + progressAlertHandler?.showProgressAlert(title: String.localized("export_backup_desktop"), dcContext: dcContext) + DispatchQueue.main.async { + self.dcAccounts.stopIo() + self.dcContext.imex(what: what, directory: documents[0], passphrase: passphrase) + } + } else { + logger.error("document directory not found") + } + } + + private func updateCells() { + mediaQualityCell.detailTextLabel?.text = MediaQualityViewController.getValString(val: dcContext.getConfigInt("media_quality")) + downloadOnDemandCell.detailTextLabel?.text = DownloadOnDemandViewController.getValString( + val: dcContext.getConfigInt("download_limit")) + autodelCell.detailTextLabel?.text = autodelSummary() + } + + // MARK: - coordinator + private func showMediaQuality() { + let mediaQualityController = MediaQualityViewController(dcContext: dcContext) + navigationController?.pushViewController(mediaQualityController, animated: true) + } + + private func showDownloadOnDemand() { + let downloadOnDemandViewController = DownloadOnDemandViewController(dcContext: dcContext) + navigationController?.pushViewController(downloadOnDemandViewController, animated: true) + } + + private func showBlockedContacts() { + let blockedContactsController = BlockedContactsViewController(dcContext: dcContext) + navigationController?.pushViewController(blockedContactsController, animated: true) + } + + private func showAutodelOptions() { + let settingsAutodelOverviewController = AutodelOverviewViewController(dcContext: dcContext) + navigationController?.pushViewController(settingsAutodelOverviewController, animated: true) + } +} diff --git a/deltachat-ios/Controller/Settings/SettingsViewController.swift b/deltachat-ios/Controller/Settings/SettingsViewController.swift index 490468781..31bc9db84 100644 --- a/deltachat-ios/Controller/Settings/SettingsViewController.swift +++ b/deltachat-ios/Controller/Settings/SettingsViewController.swift @@ -52,22 +52,14 @@ internal final class SettingsViewController: UITableViewController { return cell }() - private lazy var notificationSwitch: UISwitch = { - let switchControl = UISwitch() - switchControl.isOn = !dcContext.isMuted() - switchControl.addTarget(self, action: #selector(handleNotificationToggle(_:)), for: .valueChanged) - return switchControl - }() - private lazy var notificationCell: UITableViewCell = { - let cell = UITableViewCell(style: .default, reuseIdentifier: nil) + let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) cell.tag = CellTags.notifications.rawValue cell.textLabel?.text = String.localized("pref_notifications") if #available(iOS 16.0, *) { cell.imageView?.image = UIImage(systemName: "bell") } - cell.accessoryView = notificationSwitch - cell.selectionStyle = .none + cell.accessoryType = .disclosureIndicator return cell }() @@ -220,7 +212,7 @@ internal final class SettingsViewController: UITableViewController { case .profile: showEditSettingsController() case .chatsAndMedia: showChatsAndMedia() case .addAnotherDevice: showBackupProviderViewController() - case .notifications: break + case .notifications: showNotificationsViewController() case .advanced: showAdvanced() case .help: showHelp() case .connectivity: showConnectivity() @@ -280,6 +272,10 @@ internal final class SettingsViewController: UITableViewController { navigationController?.pushViewController(ChatsAndMediaViewController(dcAccounts: dcAccounts), animated: true) } + private func showNotificationsViewController() { + navigationController?.pushViewController(NotificationsViewController(dcAccounts: dcAccounts), animated: true) + } + private func showBackupProviderViewController() { let alert = UIAlertController(title: String.localized("multidevice_title"), message: String.localized("multidevice_this_creates_a_qr_code"), preferredStyle: .alert) alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))