356 lines
12 KiB
Swift
356 lines
12 KiB
Swift
//
|
|
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import PromiseKit
|
|
|
|
class DisappearingMessagesTimerSettingsViewController: OWSTableViewController2 {
|
|
let thread: TSThread?
|
|
let originalConfiguration: OWSDisappearingMessagesConfiguration
|
|
var configuration: OWSDisappearingMessagesConfiguration
|
|
let completion: (OWSDisappearingMessagesConfiguration) -> Void
|
|
let isUniversal: Bool
|
|
let useCustomPicker: Bool
|
|
private lazy var pickerView = CustomTimePicker { [weak self] duration in
|
|
guard let self = self else { return }
|
|
self.configuration = self.originalConfiguration.copyAsEnabled(withDurationSeconds: duration)
|
|
self.updateNavigation()
|
|
}
|
|
|
|
init(
|
|
thread: TSThread? = nil,
|
|
configuration: OWSDisappearingMessagesConfiguration,
|
|
isUniversal: Bool = false,
|
|
useCustomPicker: Bool = false,
|
|
completion: @escaping (OWSDisappearingMessagesConfiguration) -> Void
|
|
) {
|
|
self.thread = thread
|
|
self.originalConfiguration = configuration
|
|
self.configuration = configuration
|
|
self.isUniversal = isUniversal
|
|
self.useCustomPicker = useCustomPicker
|
|
self.completion = completion
|
|
|
|
super.init()
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
title = NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES",
|
|
comment: "table cell label in conversation settings"
|
|
)
|
|
|
|
defaultSeparatorInsetLeading = Self.cellHInnerMargin + 24 + OWSTableItem.iconSpacing
|
|
|
|
if useCustomPicker {
|
|
self.configuration = self.originalConfiguration.copyAsEnabled(withDurationSeconds: pickerView.selectedDuration)
|
|
}
|
|
|
|
updateNavigation()
|
|
updateTableContents()
|
|
}
|
|
|
|
private var hasUnsavedChanges: Bool {
|
|
originalConfiguration.asToken != configuration.asToken
|
|
}
|
|
|
|
// Don't allow interactive dismiss when there are unsaved changes.
|
|
override var isModalInPresentation: Bool {
|
|
get { hasUnsavedChanges }
|
|
set {}
|
|
}
|
|
|
|
private func updateNavigation() {
|
|
if !useCustomPicker {
|
|
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
|
barButtonSystemItem: .cancel,
|
|
target: self,
|
|
action: #selector(didTapCancel),
|
|
accessibilityIdentifier: "cancel_button"
|
|
)
|
|
}
|
|
|
|
if hasUnsavedChanges {
|
|
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
|
title: CommonStrings.setButton,
|
|
style: .done,
|
|
target: self,
|
|
action: #selector(didTapDone),
|
|
accessibilityIdentifier: "set_button"
|
|
)
|
|
} else {
|
|
navigationItem.rightBarButtonItem = nil
|
|
}
|
|
}
|
|
|
|
func updateTableContents() {
|
|
let contents = OWSTableContents()
|
|
defer { self.contents = contents }
|
|
|
|
let footerHeaderSection = OWSTableSection()
|
|
footerHeaderSection.footerTitle = isUniversal
|
|
? NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES_UNIVERSAL_DESCRIPTION",
|
|
comment: "subheading in privacy settings"
|
|
)
|
|
: NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES_DESCRIPTION",
|
|
comment: "subheading in conversation settings"
|
|
)
|
|
contents.addSection(footerHeaderSection)
|
|
|
|
guard !useCustomPicker else {
|
|
let section = OWSTableSection()
|
|
section.add(.init(
|
|
customCellBlock: { [weak self] in
|
|
let cell = OWSTableItem.newCell()
|
|
guard let self = self else { return cell }
|
|
|
|
cell.selectionStyle = .none
|
|
cell.contentView.addSubview(self.pickerView)
|
|
self.pickerView.autoPinEdgesToSuperviewMargins()
|
|
|
|
return cell
|
|
},
|
|
actionBlock: {}
|
|
))
|
|
contents.addSection(section)
|
|
return
|
|
}
|
|
|
|
let section = OWSTableSection()
|
|
section.add(.actionItem(
|
|
icon: configuration.isEnabled ? .empty : .accessoryCheckmark,
|
|
name: CommonStrings.switchOff,
|
|
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "timer_off"),
|
|
actionBlock: { [weak self] in
|
|
guard let self = self else { return }
|
|
self.configuration = self.originalConfiguration.copy(withIsEnabled: false)
|
|
self.updateNavigation()
|
|
self.updateTableContents()
|
|
}
|
|
))
|
|
|
|
for duration in disappearingMessagesDurations {
|
|
section.add(.actionItem(
|
|
icon: (configuration.isEnabled && duration == configuration.durationSeconds) ? .accessoryCheckmark : .empty,
|
|
name: NSString.formatDurationSeconds(duration, useShortFormat: false),
|
|
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "timer_\(duration)"),
|
|
actionBlock: { [weak self] in
|
|
guard let self = self else { return }
|
|
self.configuration = self.originalConfiguration.copyAsEnabled(withDurationSeconds: duration)
|
|
self.updateNavigation()
|
|
self.updateTableContents()
|
|
}
|
|
))
|
|
}
|
|
|
|
let isCustomTime = configuration.isEnabled && !disappearingMessagesDurations.contains(configuration.durationSeconds)
|
|
|
|
section.add(.disclosureItem(
|
|
icon: isCustomTime ? .accessoryCheckmark : .empty,
|
|
name: NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES_CUSTOM_TIME",
|
|
comment: "Disappearing message option to define a custom time"
|
|
),
|
|
accessoryText: isCustomTime ? NSString.formatDurationSeconds(configuration.durationSeconds, useShortFormat: false) : nil,
|
|
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "timer_custom"),
|
|
actionBlock: { [weak self] in
|
|
guard let self = self else { return }
|
|
let vc = DisappearingMessagesTimerSettingsViewController(
|
|
thread: self.thread,
|
|
configuration: self.originalConfiguration,
|
|
isUniversal: self.isUniversal,
|
|
useCustomPicker: true,
|
|
completion: self.completion
|
|
)
|
|
self.navigationController?.pushViewController(vc, animated: true)
|
|
}
|
|
))
|
|
|
|
contents.addSection(section)
|
|
}
|
|
|
|
var disappearingMessagesDurations: [UInt32] {
|
|
return OWSDisappearingMessagesConfiguration.presetDurationsSeconds().map { $0.uint32Value }.reversed()
|
|
}
|
|
|
|
@objc
|
|
func didTapCancel() {
|
|
guard hasUnsavedChanges else {
|
|
dismiss(animated: true)
|
|
return
|
|
}
|
|
|
|
OWSActionSheets.showPendingChangesActionSheet(discardAction: { [weak self] in
|
|
self?.dismiss(animated: true)
|
|
})
|
|
}
|
|
|
|
@objc
|
|
func didTapDone() {
|
|
let configuration = self.configuration
|
|
|
|
// We use this view some places that don't have a thread like the
|
|
// new group view and the universal timer in privacy settings. We
|
|
// only need to do the extra "save" logic to apply the timer
|
|
// immediately if we have a thread.
|
|
guard let thread = thread, hasUnsavedChanges else {
|
|
completion(configuration)
|
|
dismiss(animated: true)
|
|
return
|
|
}
|
|
|
|
GroupViewUtils.updateGroupWithActivityIndicator(
|
|
fromViewController: self,
|
|
updatePromiseBlock: {
|
|
self.updateConfigurationPromise(configuration, thread: thread)
|
|
},
|
|
completion: { [weak self] _ in
|
|
self?.completion(configuration)
|
|
self?.dismiss(animated: true)
|
|
}
|
|
)
|
|
}
|
|
|
|
private func updateConfigurationPromise(
|
|
_ configuration: OWSDisappearingMessagesConfiguration,
|
|
thread: TSThread
|
|
) -> Promise<Void> {
|
|
return firstly { () -> Promise<Void> in
|
|
return GroupManager.messageProcessingPromise(
|
|
for: thread,
|
|
description: "Update disappearing messages configuration"
|
|
)
|
|
}.map(on: .global()) {
|
|
// We're sending a message, so we're accepting any pending message request.
|
|
ThreadUtil.addToProfileWhitelistIfEmptyOrPendingRequestWithSneakyTransaction(thread: thread)
|
|
}.then(on: .global()) {
|
|
GroupManager.localUpdateDisappearingMessages(
|
|
thread: thread,
|
|
disappearingMessageToken: configuration.asToken
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private class CustomTimePicker: UIPickerView, UIPickerViewDataSource, UIPickerViewDelegate {
|
|
enum Component: Int {
|
|
case duration = 0
|
|
case unit = 1
|
|
}
|
|
|
|
enum Unit: Int {
|
|
case second = 0
|
|
case minute = 1
|
|
case hour = 2
|
|
case day = 3
|
|
case week = 4
|
|
|
|
var maxValue: Int {
|
|
switch self {
|
|
case .second: return 59
|
|
case .minute: return 59
|
|
case .hour: return 23
|
|
case .day: return 6
|
|
case .week: return 4
|
|
}
|
|
}
|
|
|
|
var name: String {
|
|
switch self {
|
|
case .second: return NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES_SECONDS",
|
|
comment: "The unit for a number of seconds"
|
|
)
|
|
case .minute: return NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES_MINUTES",
|
|
comment: "The unit for a number of minutes"
|
|
)
|
|
case .hour: return NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES_HOURS",
|
|
comment: "The unit for a number of hours"
|
|
)
|
|
case .day: return NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES_DAYS",
|
|
comment: "The unit for a number of days"
|
|
)
|
|
case .week: return NSLocalizedString(
|
|
"DISAPPEARING_MESSAGES_WEEKS",
|
|
comment: "The unit for a number of weeks"
|
|
)
|
|
}
|
|
}
|
|
|
|
var interval: TimeInterval {
|
|
switch self {
|
|
case .second: return kSecondInterval
|
|
case .minute: return kMinuteInterval
|
|
case .hour: return kHourInterval
|
|
case .day: return kDayInterval
|
|
case .week: return kWeekInterval
|
|
}
|
|
}
|
|
}
|
|
|
|
var selectedUnit: Unit = .second {
|
|
didSet {
|
|
guard oldValue != selectedUnit else { return }
|
|
reloadComponent(Component.duration.rawValue)
|
|
durationChangeCallback(selectedDuration)
|
|
}
|
|
}
|
|
var selectedTime: Int = 1 {
|
|
didSet {
|
|
guard oldValue != selectedTime else { return }
|
|
durationChangeCallback(selectedDuration)
|
|
}
|
|
}
|
|
var selectedDuration: UInt32 { UInt32(selectedUnit.interval) * UInt32(selectedTime) }
|
|
|
|
let durationChangeCallback: (UInt32) -> Void
|
|
init(durationChangeCallback: @escaping (UInt32) -> Void) {
|
|
self.durationChangeCallback = durationChangeCallback
|
|
super.init(frame: .zero)
|
|
dataSource = self
|
|
delegate = self
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
func numberOfComponents(in pickerView: UIPickerView) -> Int { 2 }
|
|
|
|
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
|
switch Component(rawValue: component) {
|
|
case .duration: return selectedUnit.maxValue
|
|
case .unit: return 5
|
|
default:
|
|
owsFailDebug("Unexpected component")
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
|
switch Component(rawValue: component) {
|
|
case .duration: return OWSFormat.formatInt(row + 1)
|
|
case .unit: return (Unit(rawValue: row) ?? .second).name
|
|
default:
|
|
owsFailDebug("Unexpected component")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
|
switch Component(rawValue: component) {
|
|
case .duration: selectedTime = row + 1
|
|
case .unit: selectedUnit = Unit(rawValue: row) ?? .second
|
|
default: owsFailDebug("Unexpected component")
|
|
}
|
|
}
|
|
}
|