Signal-iOS/Signal/src/ViewControllers/ThreadSettings/GroupAttributesViewController.swift
2020-04-17 17:33:27 -03:00

329 lines
13 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
import UIKit
protocol GroupAttributesViewControllerDelegate: class {
func groupAttributesDidUpdate()
}
// MARK: -
class GroupAttributesViewController: OWSViewController {
// MARK: - Dependencies
fileprivate var databaseStorage: SDSDatabaseStorage {
return SDSDatabaseStorage.shared
}
fileprivate var tsAccountManager: TSAccountManager {
return .sharedInstance()
}
// MARK: -
public enum EditAction {
case none
case name
case avatar
}
private weak var delegate: GroupAttributesViewControllerDelegate?
private let groupThread: TSGroupThread
private let helper: GroupAttributesEditorHelper
private var editAction: EditAction?
private var nameTextField: UITextField {
return helper.nameTextField
}
private var hasUnsavedChanges: Bool {
return helper.hasUnsavedChanges
}
public required init(groupThread: TSGroupThread,
editAction: EditAction,
delegate: GroupAttributesViewControllerDelegate) {
self.groupThread = groupThread
self.editAction = editAction
self.delegate = delegate
self.helper = GroupAttributesEditorHelper(groupId: groupThread.groupModel.groupId,
conversationColorName: groupThread.conversationColorName.rawValue,
groupNameOriginal: groupThread.groupModel.groupName,
avatarOriginalData: groupThread.groupModel.groupAvatarData,
iconViewSize: kLargeAvatarSize)
super.init()
}
// MARK: - View Lifecycle
@objc
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Theme.backgroundColor
title = NSLocalizedString("EDIT_GROUP_DEFAULT_TITLE", comment: "The navbar title for the 'update group' view.")
helper.delegate = self
helper.buildContents(avatarViewHelperDelegate: self)
let avatarStack = UIStackView(arrangedSubviews: [ helper.avatarWrapper ])
avatarStack.axis = .vertical
avatarStack.spacing = 10
avatarStack.alignment = .center
avatarStack.isUserInteractionEnabled = true
avatarStack.addGestureRecognizer(UITapGestureRecognizer(target: self,
action: #selector(didTapAvatarView)))
avatarStack.layoutMargins = UIEdgeInsets(top: 12, leading: 18, bottom: 18, trailing: 28)
avatarStack.isLayoutMarginsRelativeArrangement = true
let nameLabel = UILabel()
nameLabel.text = NSLocalizedString("EDIT_GROUP_GROUP_NAME",
comment: "Label for the group name in the 'edit group' view.")
nameLabel.font = .ows_dynamicTypeBody
nameLabel.textColor = Theme.primaryTextColor
nameLabel.setCompressionResistanceHorizontalHigh()
nameLabel.setContentHuggingHorizontalHigh()
nameTextField.setCompressionResistanceHorizontalLow()
nameTextField.textAlignment = CurrentAppContext().isRTL ? .left : .right
let nameStack = UIStackView(arrangedSubviews: [ nameLabel, nameTextField ])
nameStack.axis = .horizontal
nameStack.spacing = 10
nameStack.alignment = .center
nameStack.distribution = .fill
nameStack.layoutMargins = UIEdgeInsets(top: 12, leading: 18, bottom: 18, trailing: 12)
nameStack.isLayoutMarginsRelativeArrangement = true
let makeHairlineSeparator = { () -> UIView in
let seperator = UIView.container()
seperator.backgroundColor = Theme.secondaryBackgroundColor
seperator.autoSetDimension(.height, toSize: 1)
seperator.setContentHuggingHorizontalLow()
return seperator
}
let stackView = UIStackView(arrangedSubviews: [ avatarStack,
makeHairlineSeparator(),
nameStack,
makeHairlineSeparator(),
UIView.vStretchingSpacer() ])
stackView.axis = .vertical
nameStack.alignment = .fill
view.addSubview(stackView)
stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
stackView.autoPinEdge(toSuperviewEdge: .leading)
stackView.autoPinEdge(toSuperviewEdge: .trailing)
stackView.autoPinEdge(toSuperviewEdge: .bottom)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let editAction = self.editAction {
switch editAction {
case .none, .name:
nameTextField.becomeFirstResponder()
case .avatar:
helper.showAvatarUI()
}
self.editAction = nil
}
}
// MARK: -
fileprivate func updateNavbar() {
if helper.hasUnsavedChanges {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("EDIT_GROUP_UPDATE_BUTTON",
comment: "The title for the 'update group' button."),
style: .plain,
target: self,
action: #selector(updateGroupPressed),
accessibilityIdentifier: UIView.accessibilityIdentifier(in: self, name: "update"))
} else {
navigationItem.rightBarButtonItem = nil
}
}
@objc func updateGroupPressed() {
updateGroupThreadAndDismiss()
}
// MARK: - Events
@objc
func didTapAvatarView(sender: UIGestureRecognizer) {
helper.didTapAvatarView(sender: sender)
}
}
// MARK: -
extension GroupAttributesViewController: AvatarViewHelperDelegate {
func avatarActionSheetTitle() -> String? {
return NSLocalizedString("NEW_GROUP_ADD_PHOTO_ACTION", comment: "Action Sheet title prompting the user for a group avatar")
}
func avatarDidChange(_ image: UIImage) {
helper.setAvatarImage(image)
}
func fromViewController() -> UIViewController {
return self
}
func hasClearAvatarAction() -> Bool {
return helper.avatarCurrent != nil
}
func clearAvatar() {
helper.setAvatarImage(nil)
}
func clearAvatarActionLabel() -> String {
return NSLocalizedString("EDIT_GROUP_CLEAR_AVATAR", comment: "The 'clear avatar' button in the 'edit group' view.")
}
}
// MARK: -
extension GroupAttributesViewController: OWSNavigationView {
public func shouldCancelNavigationBack() -> Bool {
nameTextField.acceptAutocorrectSuggestion()
let result = hasUnsavedChanges
if result {
GroupAttributesViewController.showUnsavedGroupChangesActionSheet(from: self,
saveBlock: {
self.updateGroupThreadAndDismiss()
}, discardBlock: {
self.navigationController?.popViewController(animated: true)
})
}
return result
}
@objc
public static func showUnsavedGroupChangesActionSheet(from fromViewController: UIViewController,
saveBlock: @escaping () -> Void,
discardBlock: @escaping () -> Void) {
let actionSheet = ActionSheetController(title: NSLocalizedString("EDIT_GROUP_VIEW_UNSAVED_CHANGES_TITLE",
comment: "The alert title if user tries to exit update group view without saving changes."),
message: NSLocalizedString("EDIT_GROUP_VIEW_UNSAVED_CHANGES_MESSAGE",
comment: "The alert message if user tries to exit update group view without saving changes."))
actionSheet.addAction(ActionSheetAction(title: NSLocalizedString("ALERT_SAVE",
comment: "The label for the 'save' button in action sheets."),
accessibilityIdentifier: UIView.accessibilityIdentifier(in: fromViewController, name: "save"),
style: .default) { _ in
saveBlock()
})
actionSheet.addAction(ActionSheetAction(title: NSLocalizedString("ALERT_DONT_SAVE",
comment: "The label for the 'don't save' button in action sheets."),
accessibilityIdentifier: UIView.accessibilityIdentifier(in: fromViewController, name: "dont_save"),
style: .destructive) { _ in
discardBlock()
})
fromViewController.presentActionSheet(actionSheet)
}
}
// MARK: -
private extension GroupAttributesViewController {
func buildNewGroupModel(groupName: String?, groupAvatar: GroupAvatar?) -> TSGroupModel? {
do {
return try databaseStorage.read { transaction in
var builder = self.groupThread.groupModel.asBuilder
builder.name = groupName
builder.avatarData = groupAvatar?.imageData
return try builder.build(transaction: transaction)
}
} catch {
owsFailDebug("Error: \(error)")
return nil
}
}
func updateGroupThreadAndDismiss() {
guard let newGroupName = helper.groupNameCurrent,
!newGroupName.isEmpty else {
NewGroupConfirmViewController.showMissingGroupNameAlert()
return
}
let avatarCurrent: GroupAvatar? = helper.avatarCurrent
let dismissAndUpdateDelegate = { [weak self] in
guard let self = self else {
return
}
if let delegate = self.delegate {
delegate.groupAttributesDidUpdate()
}
self.navigationController?.popViewController(animated: true)
}
guard hasUnsavedChanges else {
owsFailDebug("!hasUnsavedChanges.")
return dismissAndUpdateDelegate()
}
let groupThread = self.groupThread
let oldGroupModel = groupThread.groupModel
guard let newGroupModel = buildNewGroupModel(groupName: newGroupName,
groupAvatar: avatarCurrent) else {
let error = OWSAssertionError("Couldn't build group model.")
GroupViewUtils.showUpdateErrorUI(error: error)
return
}
GroupViewUtils.updateGroupWithActivityIndicator(fromViewController: self,
updatePromiseBlock: {
self.updateGroupThreadPromise(oldGroupModel: oldGroupModel,
newGroupModel: newGroupModel)
},
completion: {
dismissAndUpdateDelegate()
})
}
func updateGroupThreadPromise(oldGroupModel: TSGroupModel,
newGroupModel: TSGroupModel) -> Promise<Void> {
guard let localAddress = tsAccountManager.localAddress else {
return Promise(error: OWSAssertionError("Missing localAddress."))
}
return firstly { () -> Promise<Void> in
return GroupManager.messageProcessingPromise(for: oldGroupModel,
description: self.logTag)
}.then(on: .global()) { _ in
// dmConfiguration: nil means don't change disappearing messages configuration.
GroupManager.localUpdateExistingGroup(groupModel: newGroupModel,
dmConfiguration: nil,
groupUpdateSourceAddress: localAddress)
}.asVoid()
}
}
// MARK: -
extension GroupAttributesViewController: GroupAttributesEditorHelperDelegate {
func groupAttributesEditorContentsDidChange() {
updateNavbar()
}
}