diff --git a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift index 35abf14f4..f8fbc947f 100644 --- a/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift +++ b/Cryptomator/AddVault/OpenExistingVault/OpenExistingVaultChooseFolderViewController.swift @@ -45,7 +45,7 @@ class OpenExistingVaultChooseFolderViewController: ChooseFolderViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if viewModel.foundMasterkey { - let cell = addVaultButtonViewModel.type.init() + let cell = addVaultButtonViewModel.type.init(style: addVaultButtonViewModel.cellStyle, reuseIdentifier: nil) cell.configure(with: addVaultButtonViewModel) return cell } else { diff --git a/Cryptomator/Common/Cells/BindableTableViewCellViewModel.swift b/Cryptomator/Common/Cells/BindableTableViewCellViewModel.swift index 154850374..36e2c6f96 100644 --- a/Cryptomator/Common/Cells/BindableTableViewCellViewModel.swift +++ b/Cryptomator/Common/Cells/BindableTableViewCellViewModel.swift @@ -39,6 +39,10 @@ class TableViewCellViewModel: Hashable { fatalError("not implemented") } + var cellStyle: UITableViewCell.CellStyle { + return .default + } + private let identifier = UUID() static func == (lhs: TableViewCellViewModel, rhs: TableViewCellViewModel) -> Bool { diff --git a/Cryptomator/Common/Cells/ButtonCellViewModel.swift b/Cryptomator/Common/Cells/ButtonCellViewModel.swift index 5c0cfb32b..8e8a3b0ae 100644 --- a/Cryptomator/Common/Cells/ButtonCellViewModel.swift +++ b/Cryptomator/Common/Cells/ButtonCellViewModel.swift @@ -9,20 +9,16 @@ import UIKit class ButtonCellViewModel: BindableTableViewCellViewModel { - override var type: ConfigurableTableViewCell.Type { ButtonTableViewCell.self } + private let preferredCellStyle: UITableViewCell.CellStyle + override var cellStyle: UITableViewCell.CellStyle { preferredCellStyle } let action: T - init(action: T, title: String, titleTextColor: UIColor? = .cryptomatorPrimary, detailTitle: String? = nil, isEnabled: Bool = true, selectionStyle: UITableViewCell.SelectionStyle = .default, accessoryType: UITableViewCell.AccessoryType = .none) { + init(action: T, title: String, titleTextColor: UIColor? = .cryptomatorPrimary, detailTitle: String? = nil, image: UIImage? = nil, isEnabled: Bool = true, selectionStyle: UITableViewCell.SelectionStyle = .default, accessoryType: UITableViewCell.AccessoryType = .none, cellStyle: UITableViewCell.CellStyle = .value1) { self.action = action - super.init(title: title, titleTextColor: titleTextColor, detailTitle: detailTitle, isEnabled: isEnabled, selectionStyle: selectionStyle, accessoryType: accessoryType) + self.preferredCellStyle = cellStyle + super.init(title: title, titleTextColor: titleTextColor, detailTitle: detailTitle, image: image, isEnabled: isEnabled, selectionStyle: selectionStyle, accessoryType: accessoryType) } - static func createDisclosureButton(action: T, title: String, detailTitle: String? = nil, accessoryType: UITableViewCell.AccessoryType = .disclosureIndicator, isEnabled: Bool = true) -> ButtonCellViewModel { - return ButtonCellViewModel(action: action, title: title, titleTextColor: nil, detailTitle: detailTitle, isEnabled: isEnabled, accessoryType: accessoryType) - } -} - -class ButtonTableViewCell: TableViewCell { - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: .value1, reuseIdentifier: reuseIdentifier) + static func createDisclosureButton(action: T, title: String, detailTitle: String? = nil, image: UIImage? = nil, accessoryType: UITableViewCell.AccessoryType = .disclosureIndicator, isEnabled: Bool = true, cellStyle: UITableViewCell.CellStyle = .value1) -> ButtonCellViewModel { + return ButtonCellViewModel(action: action, title: title, titleTextColor: nil, detailTitle: detailTitle, image: image, isEnabled: isEnabled, accessoryType: accessoryType, cellStyle: cellStyle) } } diff --git a/Cryptomator/Common/Cells/LoadingButtonCell.swift b/Cryptomator/Common/Cells/LoadingButtonCell.swift index 8f216609a..742c84c4f 100644 --- a/Cryptomator/Common/Cells/LoadingButtonCell.swift +++ b/Cryptomator/Common/Cells/LoadingButtonCell.swift @@ -8,7 +8,7 @@ import UIKit -class LoadingButtonCell: ButtonTableViewCell { +class LoadingButtonCell: TableViewCell { private lazy var loadingIndicator: UIActivityIndicatorView = { let loadingIndicator = UIActivityIndicatorView(style: .medium) loadingIndicator.hidesWhenStopped = true diff --git a/Cryptomator/Common/Cells/SystemSymbolButtonCell.swift b/Cryptomator/Common/Cells/SystemSymbolButtonCell.swift index f27663ee2..d6e95352c 100644 --- a/Cryptomator/Common/Cells/SystemSymbolButtonCell.swift +++ b/Cryptomator/Common/Cells/SystemSymbolButtonCell.swift @@ -9,7 +9,7 @@ import Foundation import UIKit -class SystemSymbolButtonCell: ButtonTableViewCell { +class SystemSymbolButtonCell: TableViewCell { override func configure(with viewModel: TableViewCellViewModel) { super.configure(with: viewModel) guard let viewModel = viewModel as? SystemSymbolNameProviding else { diff --git a/Cryptomator/Common/ChooseFolder/CreateNewFolderViewController.swift b/Cryptomator/Common/ChooseFolder/CreateNewFolderViewController.swift index 545004134..0e7d67d88 100644 --- a/Cryptomator/Common/ChooseFolder/CreateNewFolderViewController.swift +++ b/Cryptomator/Common/ChooseFolder/CreateNewFolderViewController.swift @@ -35,7 +35,7 @@ class CreateNewFolderViewController: SingleSectionStaticUITableViewController { override func configureDataSource() { dataSource = BaseDiffableDataSource(viewModel: viewModel, tableView: tableView) { _, _, cellViewModel -> UITableViewCell? in - let cell = cellViewModel.type.init() + let cell = cellViewModel.type.init(style: cellViewModel.cellStyle, reuseIdentifier: nil) cell.configure(with: cellViewModel) if let textFieldCell = cell as? TextFieldCell { let imageConfiguration = UIImage.SymbolConfiguration(font: UIFont.preferredFont(forTextStyle: .title2)) diff --git a/Cryptomator/Common/StaticUITableViewController.swift b/Cryptomator/Common/StaticUITableViewController.swift index 49ca1c030..118b514b0 100644 --- a/Cryptomator/Common/StaticUITableViewController.swift +++ b/Cryptomator/Common/StaticUITableViewController.swift @@ -27,7 +27,7 @@ class StaticUITableViewController: BaseUITableViewControl func configureDataSource() { dataSource = BaseDiffableDataSource(viewModel: viewModel, tableView: tableView) { _, _, cellViewModel -> UITableViewCell? in - let cell = cellViewModel.type.init() + let cell = cellViewModel.type.init(style: cellViewModel.cellStyle, reuseIdentifier: nil) cell.configure(with: cellViewModel) return cell } diff --git a/Cryptomator/Purchase/Cells/TrialCell.swift b/Cryptomator/Purchase/Cells/TrialCell.swift index 413400497..cba4e6c1f 100644 --- a/Cryptomator/Purchase/Cells/TrialCell.swift +++ b/Cryptomator/Purchase/Cells/TrialCell.swift @@ -76,8 +76,7 @@ struct TrialCellViewModel: Hashable { var expirationText: String { let formatter = DateFormatter() formatter.dateStyle = .short - let formattedExpirationDate = formatter.string(for: expirationDate) ?? "Invalid Date" - return String(format: LocalizedString.getValue("purchase.product.trial.expirationDate"), formattedExpirationDate) + return String(format: LocalizedString.getValue("purchase.product.trial.expirationDate"), formatter.string(from: expirationDate)) } let expirationDate: Date diff --git a/Cryptomator/Purchase/PurchaseAlert.swift b/Cryptomator/Purchase/PurchaseAlert.swift index 66c7658b6..74318779b 100644 --- a/Cryptomator/Purchase/PurchaseAlert.swift +++ b/Cryptomator/Purchase/PurchaseAlert.swift @@ -18,8 +18,7 @@ enum PurchaseAlert { static func showForTrial(title: String, expirationDate: Date, on presentingViewController: UIViewController) -> Promise { let formatter = DateFormatter() formatter.dateStyle = .short - let formattedExpireDate = formatter.string(for: expirationDate) ?? "Invalid Date" - let message = String(format: LocalizedString.getValue("purchase.restorePurchase.validTrialFound.alert.message"), formattedExpireDate) + let message = String(format: LocalizedString.getValue("purchase.restorePurchase.validTrialFound.alert.message"), formatter.string(from: expirationDate)) return showAlert(title: title, message: message, on: presentingViewController) } diff --git a/Cryptomator/Settings/SettingsViewModel.swift b/Cryptomator/Settings/SettingsViewModel.swift index d761538c1..4b72f9cc8 100644 --- a/Cryptomator/Settings/SettingsViewModel.swift +++ b/Cryptomator/Settings/SettingsViewModel.swift @@ -28,7 +28,8 @@ enum SettingsButtonAction: String { } enum SettingsSection: Int { - case cloudServiceSection = 0 + case unlockFullVersionSection = 0 + case cloudServiceSection case cacheSection case aboutSection case debugSection @@ -44,12 +45,25 @@ class SettingsViewModel: TableViewModel { return _sections } + override func getFooterTitle(for section: Int) -> String? { + guard sections[section].id == .aboutSection, hasFullVersion else { return nil } + return LocalizedString.getValue("settings.aboutCryptomator.hasFullVersion.footer") + } + var showDebugModeWarning: AnyPublisher { return showDebugModeWarningPublisher.eraseToAnyPublisher() } + private var hasFullVersion: Bool { + cryptomatorSettings.hasRunningSubscription || cryptomatorSettings.fullVersionUnlocked + } + private var _sections: [Section] { - return [ + var sections: [Section] = [] + if !hasFullVersion { + sections.append(Section(id: .unlockFullVersionSection, elements: [unlockFullVersionCellViewModel])) + } + sections.append(contentsOf: [ Section(id: .cloudServiceSection, elements: [ ButtonCellViewModel.createDisclosureButton(action: SettingsButtonAction.showCloudServices, title: LocalizedString.getValue("settings.cloudServices")) ]), @@ -67,24 +81,37 @@ class SettingsViewModel: TableViewModel { ButtonCellViewModel(action: SettingsButtonAction.showContact, title: LocalizedString.getValue("settings.contact")), ButtonCellViewModel(action: SettingsButtonAction.showRateApp, title: LocalizedString.getValue("settings.rateApp")) ]) - ] + ]) + return sections } private var aboutSectionElements: [TableViewCellViewModel] { - var elements = [ButtonCellViewModel.createDisclosureButton(action: SettingsButtonAction.showAbout, title: LocalizedString.getValue("settings.aboutCryptomator"))] + var elements: [TableViewCellViewModel] = [ + ButtonCellViewModel.createDisclosureButton(action: SettingsButtonAction.showAbout, title: LocalizedString.getValue("settings.aboutCryptomator")) + ] if cryptomatorSettings.hasRunningSubscription { - elements.append(.init(action: .showManageSubscriptions, title: LocalizedString.getValue("settings.manageSubscriptions"))) - elements.append(.init(action: .restorePurchase, title: LocalizedString.getValue("purchase.restorePurchase.button"))) - } else if !cryptomatorSettings.fullVersionUnlocked { - elements.append(ButtonCellViewModel.createDisclosureButton(action: SettingsButtonAction.showUnlockFullVersion, title: LocalizedString.getValue("settings.unlockFullVersion"))) + elements.append(ButtonCellViewModel.createDisclosureButton(action: SettingsButtonAction.showManageSubscriptions, title: LocalizedString.getValue("settings.manageSubscriptions"))) } return elements } + private var unlockFullVersionCellViewModel: ButtonCellViewModel { + let detailTitle: String + if let trialExpirationDate = cryptomatorSettings.trialExpirationDate, trialExpirationDate > Date() { + let formatter = DateFormatter() + formatter.dateStyle = .short + detailTitle = String(format: LocalizedString.getValue("settings.unlockFullVersion.trialExpirationDate"), formatter.string(from: trialExpirationDate)) + } else { + detailTitle = LocalizedString.getValue("settings.unlockFullVersion.detail") + } + let image = UIImage(systemName: "checkmark.seal.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 22)) + return ButtonCellViewModel.createDisclosureButton(action: .showUnlockFullVersion, title: LocalizedString.getValue("settings.unlockFullVersion"), detailTitle: detailTitle, image: image, cellStyle: .subtitle) + } + private let cacheSizeCellViewModel = LoadingWithLabelCellViewModel(title: LocalizedString.getValue("settings.cacheSize")) private let clearCacheButtonCellViewModel = ButtonCellViewModel(action: .clearCache, title: LocalizedString.getValue("settings.clearCache"), isEnabled: false) - private var cryptomatorSettings: CryptomatorSettings + private lazy var debugModeViewModel: SwitchCellViewModel = { let viewModel = SwitchCellViewModel(title: LocalizedString.getValue("settings.debugMode"), isOn: cryptomatorSettings.debugModeEnabled) bindDebugModeViewModel(viewModel) diff --git a/Cryptomator/VaultDetail/VaultDetailViewController.swift b/Cryptomator/VaultDetail/VaultDetailViewController.swift index bda83927a..1912c5e6b 100644 --- a/Cryptomator/VaultDetail/VaultDetailViewController.swift +++ b/Cryptomator/VaultDetail/VaultDetailViewController.swift @@ -106,7 +106,7 @@ class VaultDetailViewController: BaseUITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cellViewModel = viewModel.cellViewModel(for: indexPath) - let cell = cellViewModel.type.init() + let cell = cellViewModel.type.init(style: cellViewModel.cellStyle, reuseIdentifier: nil) cell.configure(with: cellViewModel) return cell } diff --git a/SharedResources/en.lproj/Localizable.strings b/SharedResources/en.lproj/Localizable.strings index cc9788caf..06a4b3884 100644 --- a/SharedResources/en.lproj/Localizable.strings +++ b/SharedResources/en.lproj/Localizable.strings @@ -202,6 +202,7 @@ "settings.title" = "Settings"; "settings.aboutCryptomator" = "About Cryptomator"; "settings.aboutCryptomator.title" = "Version %@ (%@)"; +"settings.aboutCryptomator.hasFullVersion.footer" = "You have unlocked the full version and gained write access to your vaults."; "settings.cacheSize" = "Cache Size"; "settings.clearCache" = "Clear Cache"; "settings.cloudServices" = "Cloud Services"; @@ -213,6 +214,8 @@ "settings.sendLogFile" = "Send Log File"; "settings.shortcutsGuide" = "Shortcuts Guide"; "settings.unlockFullVersion" = "Unlock Full Version"; +"settings.unlockFullVersion.detail" = "Gain write access to your vaults."; +"settings.unlockFullVersion.trialExpirationDate" = "Trial Expiration Date: %@"; "sharePoint.enterURL.title" = "Enter SharePoint URL"; "sharePoint.enterURL.placeholder" = "SharePoint Site URL";