253 lines
8.7 KiB
Swift
253 lines
8.7 KiB
Swift
//
|
|
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import PromiseKit
|
|
import UIKit
|
|
|
|
protocol GroupAttributesEditorHelperDelegate: class {
|
|
func groupAttributesEditorContentsDidChange()
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
// A helper class used to DRY up the common views/logic
|
|
// used when editing group names & avatars in the
|
|
// "create new group" and "edit group" views.
|
|
class GroupAttributesEditorHelper: NSObject {
|
|
|
|
// 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
|
|
}
|
|
|
|
weak var delegate: GroupAttributesEditorHelperDelegate?
|
|
|
|
private let groupId: Data
|
|
|
|
private let conversationColorName: String
|
|
|
|
private let avatarViewHelper = AvatarViewHelper()
|
|
|
|
private var avatarView: UIImageView?
|
|
|
|
let avatarWrapper = UIView.container()
|
|
|
|
private let avatarImageView = UIImageView()
|
|
|
|
private let iconViewSize: UInt
|
|
|
|
private let cameraButton = GroupAttributesEditorHelper.buildCameraButtonForCenter()
|
|
|
|
let nameTextField = UITextField()
|
|
|
|
private var groupNameOriginal: String?
|
|
|
|
var groupNameCurrent: String? {
|
|
return nameTextField.text?.filterStringForDisplay()
|
|
}
|
|
|
|
private var avatarOriginal: GroupAvatar?
|
|
|
|
var avatarCurrent: GroupAvatar?
|
|
|
|
var hasUnsavedChanges: Bool {
|
|
return (groupNameOriginal != groupNameCurrent ||
|
|
avatarOriginal?.imageData != avatarCurrent?.imageData)
|
|
}
|
|
|
|
public required init(groupId: Data,
|
|
conversationColorName: String,
|
|
groupNameOriginal: String?,
|
|
avatarOriginalData: Data?,
|
|
iconViewSize: UInt) {
|
|
|
|
self.groupId = groupId
|
|
self.conversationColorName = conversationColorName
|
|
self.groupNameOriginal = groupNameOriginal?.filterStringForDisplay()
|
|
self.avatarOriginal = GroupAvatar.build(imageData: avatarOriginalData)
|
|
self.avatarCurrent = avatarOriginal
|
|
self.iconViewSize = iconViewSize
|
|
|
|
super.init()
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
func buildContents(avatarViewHelperDelegate: AvatarViewHelperDelegate) {
|
|
|
|
avatarViewHelper.delegate = avatarViewHelperDelegate
|
|
|
|
// We need to specify a contentMode since the size of the image
|
|
// might not match the aspect ratio of the view.
|
|
avatarImageView.contentMode = .scaleAspectFill
|
|
// Use trilinear filters for better scaling quality at
|
|
// some performance cost.
|
|
avatarImageView.layer.minificationFilter = .trilinear
|
|
avatarImageView.layer.magnificationFilter = .trilinear
|
|
updateAvatarView(groupAvatar: avatarCurrent)
|
|
avatarImageView.layer.cornerRadius = CGFloat(iconViewSize) * 0.5
|
|
avatarImageView.clipsToBounds = true
|
|
avatarImageView.autoSetDimensions(to: CGSize(square: CGFloat(iconViewSize)))
|
|
avatarImageView.isUserInteractionEnabled = true
|
|
avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self,
|
|
action: #selector(didTapAvatarView)))
|
|
|
|
avatarWrapper.addSubview(avatarImageView)
|
|
avatarImageView.autoPinEdgesToSuperviewEdges()
|
|
|
|
avatarWrapper.addSubview(cameraButton)
|
|
cameraButton.autoCenterInSuperview()
|
|
|
|
nameTextField.text = groupNameOriginal
|
|
nameTextField.font = .ows_dynamicTypeBody
|
|
nameTextField.backgroundColor = .clear
|
|
nameTextField.textColor = Theme.primaryTextColor
|
|
nameTextField.delegate = self
|
|
nameTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
|
|
nameTextField.placeholder = NSLocalizedString("GROUP_NAME_PLACEHOLDER",
|
|
comment: "Placeholder text for 'group name' field.")
|
|
}
|
|
|
|
public static func buildCameraButtonForCorner() -> UIView {
|
|
let cameraImageView = UIImageView()
|
|
cameraImageView.setTemplateImageName("camera-outline-24", tintColor: .ows_gray45)
|
|
let cameraWrapper = UIView.container()
|
|
cameraWrapper.backgroundColor = .ows_white
|
|
cameraWrapper.addSubview(cameraImageView)
|
|
cameraImageView.autoCenterInSuperview()
|
|
let wrapperSize: CGFloat = 32
|
|
cameraWrapper.layer.shadowColor = UIColor.ows_black.cgColor
|
|
cameraWrapper.layer.shadowOpacity = 0.5
|
|
cameraWrapper.layer.shadowRadius = 4
|
|
cameraWrapper.layer.shadowOffset = CGSize(width: 0, height: 4)
|
|
cameraWrapper.layer.cornerRadius = wrapperSize * 0.5
|
|
cameraWrapper.autoSetDimensions(to: CGSize(square: wrapperSize))
|
|
return cameraWrapper
|
|
}
|
|
|
|
public static func buildCameraButtonForCenter() -> UIView {
|
|
let cameraImageView = UIImageView()
|
|
cameraImageView.setTemplateImageName("camera-outline-24", tintColor: .ows_accentBlue)
|
|
let iconSize: CGFloat = 32
|
|
cameraImageView.autoSetDimensions(to: CGSize(square: iconSize))
|
|
return cameraImageView
|
|
}
|
|
|
|
private func updateAvatarView(groupAvatar: GroupAvatar?) {
|
|
if let groupAvatar = groupAvatar {
|
|
avatarImageView.image = groupAvatar.image
|
|
avatarImageView.backgroundColor = nil
|
|
cameraButton.isHidden = true
|
|
} else {
|
|
avatarImageView.image = nil
|
|
avatarImageView.backgroundColor = Theme.washColor
|
|
cameraButton.isHidden = false
|
|
}
|
|
}
|
|
|
|
func setAvatarImage(_ image: UIImage?) {
|
|
let groupAvatar: GroupAvatar? = {
|
|
guard let image = image else {
|
|
return nil
|
|
}
|
|
guard let groupAvatar = GroupAvatar.build(image: image) else {
|
|
OWSActionSheets.showErrorAlert(message: NSLocalizedString("EDIT_GROUP_ERROR_INVALID_AVATAR",
|
|
comment: "Error message indicating that an avatar image is invalid and cannot be used."))
|
|
owsFailDebug("Invalid image.")
|
|
return nil
|
|
}
|
|
return groupAvatar
|
|
}()
|
|
avatarCurrent = groupAvatar
|
|
updateAvatarView(groupAvatar: avatarCurrent)
|
|
|
|
delegate?.groupAttributesEditorContentsDidChange()
|
|
}
|
|
|
|
// MARK: - Events
|
|
|
|
@objc
|
|
func textFieldDidChange(_ textField: UITextField) {
|
|
delegate?.groupAttributesEditorContentsDidChange()
|
|
}
|
|
|
|
@objc
|
|
func didTapAvatarView(sender: UIGestureRecognizer) {
|
|
showAvatarUI()
|
|
}
|
|
|
|
func showAvatarUI() {
|
|
nameTextField.resignFirstResponder()
|
|
avatarViewHelper.showChangeAvatarUI()
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
struct GroupAvatar {
|
|
let imageData: Data
|
|
let image: UIImage
|
|
|
|
static func build(imageData: Data?) -> GroupAvatar? {
|
|
guard let imageData = imageData else {
|
|
return nil
|
|
}
|
|
guard (imageData as NSData).ows_isValidImage() else {
|
|
owsFailDebug("Invalid image data.")
|
|
return nil
|
|
}
|
|
guard let image = UIImage(data: imageData) else {
|
|
owsFailDebug("Could not load image.")
|
|
return nil
|
|
}
|
|
return GroupAvatar(imageData: imageData, image: image)
|
|
}
|
|
|
|
static func build(image: UIImage?) -> GroupAvatar? {
|
|
guard let image = image else {
|
|
return nil
|
|
}
|
|
guard let imageData = TSGroupModel.data(forGroupAvatar: image) else {
|
|
owsFailDebug("Invalid image.")
|
|
return nil
|
|
}
|
|
return GroupAvatar(imageData: imageData, image: image)
|
|
}
|
|
}
|
|
|
|
// MARK:
|
|
|
|
extension GroupAttributesEditorHelper: UITextFieldDelegate {
|
|
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString: String) -> Bool {
|
|
// Truncate the replacement to fit.
|
|
let left: String = (textField.text ?? "").substring(to: range.location)
|
|
let right: String = (textField.text ?? "").substring(from: range.location + range.length)
|
|
let maxReplacementLength = GroupManager.maxGroupNameLength - Int(left.count + right.count)
|
|
let center = replacementString.substring(to: maxReplacementLength)
|
|
textField.text = (left + center + right)
|
|
// Place the cursor after the truncated replacement.
|
|
let positionAfterChange = left.count + center.count
|
|
guard let position = textField.position(from: textField.beginningOfDocument, offset: positionAfterChange) else {
|
|
owsFailDebug("Invalid position")
|
|
return false
|
|
}
|
|
textField.selectedTextRange = textField.textRange(from: position, to: position)
|
|
return false
|
|
}
|
|
}
|