mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
6eeb0ec7ac
Copy tweak Added a toast when copying the sessionId or group URL (fixes to the toast UI as well) Fixed the new conversation screen styling Fixed the styling of the various attachment screens Updated the buttons on the attachment screen to behave like the input view buttons Removed the old OWSNavigationBar and OWSNavigationController (logic was buggy and not actually needed in most cases)
304 lines
11 KiB
Swift
304 lines
11 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import UIKit
|
|
import Combine
|
|
import SessionUIKit
|
|
import SessionMessagingKit
|
|
import SessionUtilitiesKit
|
|
import SignalUtilitiesKit
|
|
|
|
class SessionAvatarCell: UITableViewCell {
|
|
var disposables: Set<AnyCancellable> = Set()
|
|
private var originalInputValue: String?
|
|
|
|
// MARK: - Initialization
|
|
|
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
|
|
setupViewHierarchy()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
|
|
setupViewHierarchy()
|
|
}
|
|
|
|
// MARK: - UI
|
|
|
|
private let stackView: UIStackView = {
|
|
let stackView: UIStackView = UIStackView()
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
stackView.axis = .vertical
|
|
stackView.spacing = Values.mediumSpacing
|
|
stackView.alignment = .center
|
|
stackView.distribution = .equalSpacing
|
|
|
|
let horizontalSpacing: CGFloat = (UIScreen.main.bounds.size.height < 568 ?
|
|
Values.largeSpacing :
|
|
Values.veryLargeSpacing
|
|
)
|
|
stackView.layoutMargins = UIEdgeInsets(
|
|
top: Values.mediumSpacing,
|
|
leading: horizontalSpacing,
|
|
bottom: Values.largeSpacing,
|
|
trailing: horizontalSpacing
|
|
)
|
|
stackView.isLayoutMarginsRelativeArrangement = true
|
|
|
|
return stackView
|
|
}()
|
|
|
|
fileprivate let profilePictureView: ProfilePictureView = {
|
|
let view: ProfilePictureView = ProfilePictureView()
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.size = Values.largeProfilePictureSize
|
|
|
|
return view
|
|
}()
|
|
|
|
fileprivate let displayNameContainer: UIView = {
|
|
let view: UIView = UIView()
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.accessibilityLabel = "Edit name text field"
|
|
view.isAccessibilityElement = true
|
|
|
|
return view
|
|
}()
|
|
|
|
private lazy var displayNameLabel: UILabel = {
|
|
let label: UILabel = UILabel()
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
label.font = .ows_mediumFont(withSize: Values.veryLargeFontSize)
|
|
label.themeTextColor = .textPrimary
|
|
label.textAlignment = .center
|
|
label.lineBreakMode = .byTruncatingTail
|
|
label.numberOfLines = 0
|
|
|
|
return label
|
|
}()
|
|
|
|
fileprivate let displayNameTextField: UITextField = {
|
|
let textField: TextField = TextField(placeholder: "Enter a name", usesDefaultHeight: false)
|
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
|
textField.textAlignment = .center
|
|
textField.accessibilityLabel = "Edit name text field"
|
|
textField.alpha = 0
|
|
|
|
return textField
|
|
}()
|
|
|
|
private let descriptionSeparator: Separator = {
|
|
let result: Separator = Separator()
|
|
result.isHidden = true
|
|
|
|
return result
|
|
}()
|
|
|
|
private let descriptionLabel: SRCopyableLabel = {
|
|
let label: SRCopyableLabel = SRCopyableLabel()
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
label.themeTextColor = .textPrimary
|
|
label.textAlignment = .center
|
|
label.lineBreakMode = .byCharWrapping
|
|
label.numberOfLines = 0
|
|
|
|
return label
|
|
}()
|
|
|
|
private let descriptionActionStackView: UIStackView = {
|
|
let stackView: UIStackView = UIStackView()
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
stackView.axis = .horizontal
|
|
stackView.alignment = .center
|
|
stackView.distribution = .fillEqually
|
|
stackView.spacing = (UIDevice.current.isIPad ? Values.iPadButtonSpacing : Values.mediumSpacing)
|
|
|
|
if (UIDevice.current.isIPad) {
|
|
stackView.layoutMargins = UIEdgeInsets(
|
|
top: 0,
|
|
left: Values.iPadButtonContainerMargin,
|
|
bottom: 0,
|
|
right: Values.iPadButtonContainerMargin
|
|
)
|
|
stackView.isLayoutMarginsRelativeArrangement = true
|
|
}
|
|
|
|
return stackView
|
|
}()
|
|
|
|
private func setupViewHierarchy() {
|
|
self.themeBackgroundColor = nil
|
|
self.selectedBackgroundView = UIView()
|
|
|
|
contentView.addSubview(stackView)
|
|
|
|
stackView.addArrangedSubview(profilePictureView)
|
|
stackView.addArrangedSubview(displayNameContainer)
|
|
stackView.addArrangedSubview(descriptionSeparator)
|
|
stackView.addArrangedSubview(descriptionLabel)
|
|
stackView.addArrangedSubview(descriptionActionStackView)
|
|
|
|
displayNameContainer.addSubview(displayNameLabel)
|
|
displayNameContainer.addSubview(displayNameTextField)
|
|
|
|
setupLayout()
|
|
}
|
|
|
|
// MARK: - Layout
|
|
|
|
private func setupLayout() {
|
|
stackView.pin(to: contentView)
|
|
|
|
profilePictureView.set(.width, to: profilePictureView.size)
|
|
profilePictureView.set(.height, to: profilePictureView.size)
|
|
|
|
displayNameLabel.pin(to: displayNameContainer)
|
|
displayNameTextField.center(in: displayNameContainer)
|
|
displayNameTextField.widthAnchor
|
|
.constraint(
|
|
lessThanOrEqualTo: stackView.widthAnchor,
|
|
constant: -(stackView.layoutMargins.left + stackView.layoutMargins.right)
|
|
)
|
|
.isActive = true
|
|
|
|
descriptionSeparator.set(
|
|
.width,
|
|
to: .width,
|
|
of: stackView,
|
|
withOffset: -(stackView.layoutMargins.left + stackView.layoutMargins.right)
|
|
)
|
|
descriptionActionStackView.set(
|
|
.width,
|
|
to: .width,
|
|
of: stackView,
|
|
withOffset: -(stackView.layoutMargins.left + stackView.layoutMargins.right)
|
|
)
|
|
}
|
|
|
|
// MARK: - Content
|
|
|
|
override func prepareForReuse() {
|
|
super.prepareForReuse()
|
|
|
|
self.disposables = Set()
|
|
self.originalInputValue = nil
|
|
self.displayNameLabel.text = nil
|
|
self.displayNameTextField.text = nil
|
|
self.descriptionLabel.font = .ows_lightFont(withSize: Values.smallFontSize)
|
|
self.descriptionLabel.text = nil
|
|
|
|
self.descriptionSeparator.isHidden = true
|
|
self.descriptionActionStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
}
|
|
|
|
func update(
|
|
threadViewModel: SessionThreadViewModel,
|
|
style: SessionCell.Accessory.ThreadInfoStyle,
|
|
viewController: UIViewController
|
|
) {
|
|
profilePictureView.update(
|
|
publicKey: threadViewModel.threadId,
|
|
profile: threadViewModel.profile,
|
|
additionalProfile: threadViewModel.additionalProfile,
|
|
threadVariant: threadViewModel.threadVariant,
|
|
openGroupProfilePictureData: threadViewModel.openGroupProfilePictureData,
|
|
useFallbackPicture: (
|
|
threadViewModel.threadVariant == .openGroup &&
|
|
threadViewModel.openGroupProfilePictureData == nil
|
|
),
|
|
showMultiAvatarForClosedGroup: true
|
|
)
|
|
|
|
originalInputValue = threadViewModel.profile?.nickname
|
|
displayNameLabel.text = {
|
|
guard !threadViewModel.threadIsNoteToSelf else {
|
|
guard let profile: Profile = threadViewModel.profile else {
|
|
return Profile.truncated(id: threadViewModel.threadId, truncating: .middle)
|
|
}
|
|
|
|
return profile.displayName()
|
|
}
|
|
|
|
return threadViewModel.displayName
|
|
}()
|
|
descriptionLabel.font = {
|
|
switch style.descriptionStyle {
|
|
case .small: return .ows_lightFont(withSize: Values.smallFontSize)
|
|
case .monoSmall: return Fonts.spaceMono(ofSize: Values.smallFontSize)
|
|
case .monoLarge: return Fonts.spaceMono(
|
|
ofSize: (isIPhone5OrSmaller ? Values.mediumFontSize : Values.largeFontSize)
|
|
)
|
|
}
|
|
}()
|
|
descriptionLabel.text = threadViewModel.threadId
|
|
descriptionLabel.isHidden = (threadViewModel.threadVariant != .contact)
|
|
descriptionLabel.isUserInteractionEnabled = (
|
|
threadViewModel.threadVariant == .contact ||
|
|
threadViewModel.threadVariant == .openGroup
|
|
)
|
|
displayNameTextField.text = threadViewModel.profile?.nickname
|
|
descriptionSeparator.update(title: style.separatorTitle)
|
|
descriptionSeparator.isHidden = (style.separatorTitle == nil)
|
|
|
|
style.descriptionActions.forEach { action in
|
|
let result: SessionButton = SessionButton(style: .bordered, size: .medium)
|
|
result.setTitle(action.title, for: UIControl.State.normal)
|
|
result.tapPublisher
|
|
.receive(on: DispatchQueue.main)
|
|
.sink(receiveValue: { [weak result] _ in action.run(result) })
|
|
.store(in: &self.disposables)
|
|
|
|
descriptionActionStackView.addArrangedSubview(result)
|
|
}
|
|
descriptionActionStackView.isHidden = style.descriptionActions.isEmpty
|
|
}
|
|
|
|
func update(isEditing: Bool, animated: Bool) {
|
|
let changes = { [weak self] in
|
|
self?.displayNameLabel.alpha = (isEditing ? 0 : 1)
|
|
self?.displayNameTextField.alpha = (isEditing ? 1 : 0)
|
|
}
|
|
let completion: (Bool) -> Void = { [weak self] complete in
|
|
self?.displayNameTextField.text = self?.originalInputValue
|
|
}
|
|
|
|
if animated {
|
|
UIView.animate(withDuration: 0.25, animations: changes, completion: completion)
|
|
}
|
|
else {
|
|
changes()
|
|
completion(true)
|
|
}
|
|
|
|
if isEditing {
|
|
displayNameTextField.becomeFirstResponder()
|
|
}
|
|
else {
|
|
displayNameTextField.resignFirstResponder()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Compose
|
|
|
|
extension CombineCompatible where Self: SessionAvatarCell {
|
|
var textPublisher: AnyPublisher<String, Never> {
|
|
return self.displayNameTextField.publisher(for: .editingChanged)
|
|
.map { textField -> String in (textField.text ?? "") }
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
var displayNameTapPublisher: AnyPublisher<Void, Never> {
|
|
return self.displayNameContainer.tapPublisher
|
|
.map { _ in () }
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
var profilePictureTapPublisher: AnyPublisher<Void, Never> {
|
|
return self.profilePictureView.tapPublisher
|
|
.map { _ in () }
|
|
.eraseToAnyPublisher()
|
|
}
|
|
}
|