mirror of
https://github.com/signalapp/Signal-iOS.git
synced 2025-12-05 01:10:41 +00:00
Revert "Fixes/improvements for ConversationHeaderView."
This reverts commit 925e3d775b.
This commit is contained in:
@@ -21,7 +21,7 @@ public class CVViewState: NSObject {
|
||||
public let threadUniqueId: String
|
||||
public var conversationStyle: ConversationStyle
|
||||
public var inputToolbar: ConversationInputToolbar?
|
||||
let headerView = ConversationHeaderView()
|
||||
public let headerView = ConversationHeaderView()
|
||||
|
||||
public var bottomBarContainer = UIView.container()
|
||||
public var requestView: UIView?
|
||||
|
||||
@@ -3,117 +3,136 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import SignalUI
|
||||
import Foundation
|
||||
public import SignalUI
|
||||
import UIKit
|
||||
|
||||
protocol ConversationHeaderViewDelegate: AnyObject {
|
||||
public protocol ConversationHeaderViewDelegate: AnyObject {
|
||||
func didTapConversationHeaderView(_ conversationHeaderView: ConversationHeaderView)
|
||||
func didTapConversationHeaderViewAvatar(_ conversationHeaderView: ConversationHeaderView)
|
||||
}
|
||||
|
||||
class ConversationHeaderView: UIView {
|
||||
public class ConversationHeaderView: UIView {
|
||||
|
||||
weak var delegate: ConversationHeaderViewDelegate?
|
||||
public weak var delegate: ConversationHeaderViewDelegate?
|
||||
|
||||
var titleIcon: UIImage? {
|
||||
public var attributedTitle: NSAttributedString? {
|
||||
get {
|
||||
return titleIconView.image
|
||||
return self.titleLabel.attributedText
|
||||
}
|
||||
set {
|
||||
titleIconView.image = newValue
|
||||
titleIconView.isHidden = newValue == nil
|
||||
self.titleLabel.attributedText = newValue
|
||||
}
|
||||
}
|
||||
|
||||
let titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = UIColor.Signal.label
|
||||
label.lineBreakMode = .byTruncatingTail
|
||||
label.font = .systemFont(ofSize: 17, weight: .semibold)
|
||||
label.setContentHuggingHigh()
|
||||
return label
|
||||
}()
|
||||
public var titleIcon: UIImage? {
|
||||
get {
|
||||
return self.titleIconView.image
|
||||
}
|
||||
set {
|
||||
self.titleIconView.image = newValue
|
||||
self.titleIconView.isHidden = newValue == nil
|
||||
}
|
||||
}
|
||||
|
||||
let subtitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = .Signal.label
|
||||
label.lineBreakMode = .byTruncatingTail
|
||||
label.font = .systemFont(ofSize: 13, weight: .medium)
|
||||
label.setContentHuggingHigh()
|
||||
return label
|
||||
}()
|
||||
public var titleIconSize: CGFloat {
|
||||
get {
|
||||
return self.titleIconConstraints.first?.constant ?? 0
|
||||
}
|
||||
set {
|
||||
self.titleIconConstraints.forEach { $0.constant = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
private let titleIconView: UIImageView = {
|
||||
let titleIconView = UIImageView()
|
||||
titleIconView.isHidden = true
|
||||
titleIconView.contentMode = .scaleAspectFit
|
||||
titleIconView.setCompressionResistanceHigh()
|
||||
return titleIconView
|
||||
}()
|
||||
private var titleIconSizeConstraint: NSLayoutConstraint!
|
||||
public var attributedSubtitle: NSAttributedString? {
|
||||
get {
|
||||
return self.subtitleLabel.attributedText
|
||||
}
|
||||
set {
|
||||
self.subtitleLabel.attributedText = newValue
|
||||
self.subtitleLabel.isHidden = newValue == nil
|
||||
}
|
||||
}
|
||||
|
||||
public let titlePrimaryFont = UIFont.semiboldFont(ofSize: 17)
|
||||
public let subtitleFont = UIFont.regularFont(ofSize: 13).medium()
|
||||
|
||||
private let titleLabel: UILabel
|
||||
private let titleIconView: UIImageView
|
||||
private let titleIconConstraints: [NSLayoutConstraint]
|
||||
private let subtitleLabel: UILabel
|
||||
|
||||
private var avatarSizeClass: ConversationAvatarView.Configuration.SizeClass {
|
||||
// One size for the navigation bar on iOS 26.
|
||||
guard #unavailable(iOS 26) else { return .thirtySix }
|
||||
|
||||
return traitCollection.verticalSizeClass == .compact && !UIDevice.current.isPlusSizePhone
|
||||
? .twentyFour
|
||||
: .thirtySix
|
||||
traitCollection.verticalSizeClass == .compact ? .twentyFour : .thirtySix
|
||||
}
|
||||
private(set) lazy var avatarView = ConversationAvatarView(
|
||||
sizeClass: avatarSizeClass,
|
||||
localUserDisplayMode: .noteToSelf)
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
public init() {
|
||||
titleLabel = UILabel()
|
||||
titleLabel.textColor = UIColor.Signal.label
|
||||
titleLabel.lineBreakMode = .byTruncatingTail
|
||||
titleLabel.font = titlePrimaryFont
|
||||
titleLabel.setContentHuggingHigh()
|
||||
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
titleIconView = UIImageView()
|
||||
titleIconView.contentMode = .scaleAspectFit
|
||||
titleIconView.setCompressionResistanceHigh()
|
||||
titleIconConstraints = titleIconView.autoSetDimensions(to: .square(20))
|
||||
|
||||
let titleColumns = UIStackView(arrangedSubviews: [titleLabel, titleIconView])
|
||||
titleColumns.spacing = 5
|
||||
titleColumns.translatesAutoresizingMaskIntoConstraints = false
|
||||
// There is a strange bug where an initial height of 0
|
||||
// breaks the layout, so set an initial height.
|
||||
titleColumns.heightAnchor.constraint(greaterThanOrEqualToConstant: titleLabel.font.lineHeight.rounded(.up)).isActive = true
|
||||
titleColumns.autoSetDimension(
|
||||
.height,
|
||||
toSize: titleLabel.font.lineHeight,
|
||||
relation: .greaterThanOrEqual
|
||||
)
|
||||
|
||||
subtitleLabel = UILabel()
|
||||
subtitleLabel.textColor = UIColor.Signal.label
|
||||
subtitleLabel.lineBreakMode = .byTruncatingTail
|
||||
subtitleLabel.font = subtitleFont
|
||||
subtitleLabel.setContentHuggingHigh()
|
||||
|
||||
let textRows = UIStackView(arrangedSubviews: [titleColumns, subtitleLabel])
|
||||
textRows.axis = .vertical
|
||||
textRows.alignment = .leading
|
||||
textRows.distribution = .fillProportionally
|
||||
textRows.directionalLayoutMargins = .init(top: 0, leading: 8, bottom: 0, trailing: 0)
|
||||
textRows.spacing = 0
|
||||
|
||||
textRows.layoutMargins = UIEdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 0)
|
||||
textRows.isLayoutMarginsRelativeArrangement = true
|
||||
|
||||
let rootStack = UIStackView(arrangedSubviews: [ avatarView, textRows ])
|
||||
rootStack.directionalLayoutMargins = .init(hMargin: 0, vMargin: 4)
|
||||
// low content hugging so that the text rows push container to the right bar button item(s)
|
||||
textRows.setContentHuggingLow()
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
let rootStack = UIStackView()
|
||||
rootStack.layoutMargins = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)
|
||||
rootStack.isLayoutMarginsRelativeArrangement = true
|
||||
|
||||
rootStack.axis = .horizontal
|
||||
rootStack.alignment = .center
|
||||
rootStack.spacing = 0
|
||||
rootStack.addArrangedSubview(avatarView)
|
||||
rootStack.addArrangedSubview(textRows)
|
||||
|
||||
addSubview(rootStack)
|
||||
rootStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
titleIconView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
titleIconView.heightAnchor.constraint(equalToConstant: 16),
|
||||
titleIconView.widthAnchor.constraint(equalTo: titleIconView.heightAnchor),
|
||||
|
||||
rootStack.topAnchor.constraint(equalTo: topAnchor),
|
||||
rootStack.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
rootStack.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
rootStack.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
])
|
||||
|
||||
if #available(iOS 26, *) {
|
||||
heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
|
||||
}
|
||||
rootStack.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView))
|
||||
rootStack.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
required public init(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configure(threadViewModel: ThreadViewModel) {
|
||||
public func configure(threadViewModel: ThreadViewModel) {
|
||||
avatarView.updateWithSneakyTransactionIfNecessary { config in
|
||||
config.dataSource = .thread(threadViewModel.threadRecord)
|
||||
config.storyConfiguration = .autoUpdate()
|
||||
@@ -121,18 +140,13 @@ class ConversationHeaderView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
public override var intrinsicContentSize: CGSize {
|
||||
// Grow to fill as much of the navbar as possible.
|
||||
return .init(width: .greatestFiniteMagnitude, height: UIView.noIntrinsicMetric)
|
||||
return UIView.layoutFittingExpandedSize
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
// One size for the navigation bar on iOS 26.
|
||||
guard #unavailable(iOS 26) else { return }
|
||||
|
||||
guard traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass else { return }
|
||||
avatarView.updateWithSneakyTransactionIfNecessary { config in
|
||||
config.sizeClass = avatarSizeClass
|
||||
}
|
||||
|
||||
@@ -191,13 +191,13 @@ extension ConversationViewController: ContactShareViewControllerDelegate {
|
||||
// MARK: -
|
||||
|
||||
extension ConversationViewController: ConversationHeaderViewDelegate {
|
||||
func didTapConversationHeaderView(_ conversationHeaderView: ConversationHeaderView) {
|
||||
public func didTapConversationHeaderView(_ conversationHeaderView: ConversationHeaderView) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
showConversationSettings()
|
||||
}
|
||||
|
||||
func didTapConversationHeaderViewAvatar(_ conversationHeaderView: ConversationHeaderView) {
|
||||
public func didTapConversationHeaderViewAvatar(_ conversationHeaderView: ConversationHeaderView) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
if conversationHeaderView.avatarView.configuration.hasStoriesToDisplay {
|
||||
|
||||
@@ -8,28 +8,36 @@ import SignalServiceKit
|
||||
public import SignalUI
|
||||
|
||||
extension ConversationViewController {
|
||||
|
||||
public func updateNavigationTitle() {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let title = threadViewModel.name
|
||||
self.title = nil
|
||||
|
||||
// Important as it will be displayed in <Back button popup in view controllers
|
||||
// pushed over ConversationViewController.
|
||||
navigationItem.title = title
|
||||
if thread.isNoteToSelf {
|
||||
headerView.titleIcon = Theme.iconImage(.official)
|
||||
headerView.titleIconSize = 16
|
||||
} else {
|
||||
headerView.titleIcon = nil
|
||||
}
|
||||
|
||||
headerView.titleIcon = thread.isNoteToSelf ? Theme.iconImage(.official) : nil
|
||||
let attributedName = NSMutableAttributedString(
|
||||
string: threadViewModel.name,
|
||||
attributes: [
|
||||
.foregroundColor: UIColor.Signal.label,
|
||||
]
|
||||
)
|
||||
|
||||
if conversationViewModel.isSystemContact {
|
||||
// To ensure a single source of text color do not set `color` attributes unless you really need to.
|
||||
let contactIcon = SignalSymbol.personCircle.attributedString(
|
||||
dynamicTypeBaseSize: 14,
|
||||
weight: .bold,
|
||||
leadingCharacter: .space
|
||||
)
|
||||
headerView.titleLabel.attributedText = NSAttributedString(string: title).stringByAppendingString(contactIcon)
|
||||
} else {
|
||||
headerView.titleLabel.text = title
|
||||
attributedName.append(contactIcon)
|
||||
}
|
||||
|
||||
if headerView.attributedTitle != attributedName {
|
||||
headerView.attributedTitle = attributedName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +80,10 @@ extension ConversationViewController {
|
||||
public func updateBarButtonItems() {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
if #unavailable(iOS 26) {
|
||||
// Don't include "Back" text on view controllers pushed above us, just use the arrow.
|
||||
if #available(iOS 26, *), BuildFlags.iOS26SDKIsAvailable {
|
||||
// iOS 26 already doesn't show back button text
|
||||
} else {
|
||||
// Don't include "Back" text on view controllers pushed above us, just use the arrow.
|
||||
navigationItem.backBarButtonItem = UIBarButtonItem(
|
||||
title: "",
|
||||
style: .plain,
|
||||
@@ -85,31 +94,29 @@ extension ConversationViewController {
|
||||
|
||||
navigationItem.hidesBackButton = false
|
||||
navigationItem.leftBarButtonItem = nil
|
||||
groupCallBarButtonItem = nil
|
||||
self.groupCallBarButtonItem = nil
|
||||
|
||||
switch uiMode {
|
||||
case .search:
|
||||
if userLeftGroup {
|
||||
if self.userLeftGroup {
|
||||
navigationItem.rightBarButtonItems = []
|
||||
return
|
||||
}
|
||||
owsAssertDebug(navigationItem.searchController != nil)
|
||||
return
|
||||
|
||||
case .selection:
|
||||
navigationItem.rightBarButtonItems = [ cancelSelectionBarButtonItem ]
|
||||
navigationItem.leftBarButtonItem = deleteAllBarButtonItem
|
||||
navigationItem.rightBarButtonItems = [ self.cancelSelectionBarButtonItem ]
|
||||
navigationItem.leftBarButtonItem = self.deleteAllBarButtonItem
|
||||
navigationItem.hidesBackButton = true
|
||||
return
|
||||
|
||||
case .normal:
|
||||
if userLeftGroup {
|
||||
if self.userLeftGroup {
|
||||
navigationItem.rightBarButtonItems = []
|
||||
return
|
||||
}
|
||||
var barButtons = [UIBarButtonItem]()
|
||||
if canCall {
|
||||
if isGroupConversation {
|
||||
if self.canCall {
|
||||
if self.isGroupConversation {
|
||||
let videoCallButton = UIBarButtonItem()
|
||||
|
||||
if conversationViewModel.groupCallInProgress {
|
||||
@@ -125,11 +132,12 @@ extension ConversationViewController {
|
||||
)
|
||||
pill.buttonText = self.isCurrentCallForThread ? returnString : CallStrings.joinCallPillButtonTitle
|
||||
videoCallButton.customView = pill
|
||||
|
||||
if #available(iOS 26, *) {
|
||||
#if compiler(>=6.2)
|
||||
if #available(iOS 26.0, *) {
|
||||
videoCallButton.tintColor = UIColor.Signal.green
|
||||
videoCallButton.style = .prominent
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
videoCallButton.image = Theme.iconImage(.buttonVideoCall)
|
||||
videoCallButton.target = self
|
||||
@@ -138,13 +146,13 @@ extension ConversationViewController {
|
||||
|
||||
videoCallButton.isEnabled = (
|
||||
AppEnvironment.shared.callService.callServiceState.currentCall == nil
|
||||
|| isCurrentCallForThread
|
||||
|| self.isCurrentCallForThread
|
||||
)
|
||||
videoCallButton.accessibilityLabel = OWSLocalizedString(
|
||||
"VIDEO_CALL_LABEL",
|
||||
comment: "Accessibility label for placing a video call"
|
||||
)
|
||||
groupCallBarButtonItem = videoCallButton
|
||||
self.groupCallBarButtonItem = videoCallButton
|
||||
barButtons.append(videoCallButton)
|
||||
} else {
|
||||
let audioCallButton = UIBarButtonItem(
|
||||
@@ -183,16 +191,24 @@ extension ConversationViewController {
|
||||
public func updateNavigationBarSubtitleLabel() {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
// Shorter, more vertically compact navigation bar doesn't have second line of text.
|
||||
if #unavailable(iOS 26), !UIDevice.current.isPlusSizePhone, traitCollection.verticalSizeClass == .compact {
|
||||
headerView.subtitleLabel.text = nil
|
||||
let hasCompactHeader = self.traitCollection.verticalSizeClass == .compact
|
||||
if hasCompactHeader {
|
||||
self.headerView.attributedSubtitle = nil
|
||||
return
|
||||
}
|
||||
|
||||
let subtitleText = NSMutableAttributedString()
|
||||
let subtitleFont = headerView.subtitleLabel.font!
|
||||
// To ensure a single source of text color do not set `color` attributes unless you really need to.
|
||||
let attributes: [NSAttributedString.Key: Any] = [ .font: subtitleFont ]
|
||||
let subtitleFont = self.headerView.subtitleFont
|
||||
// Use higher-contrast color for the blurred iOS 26 nav bars
|
||||
let fontColor: UIColor = if #available(iOS 26, *), BuildFlags.iOS26SDKIsAvailable {
|
||||
UIColor.Signal.label
|
||||
} else {
|
||||
Theme.navbarTitleColor.withAlphaComponent(0.9)
|
||||
}
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
.font: subtitleFont,
|
||||
.foregroundColor: fontColor,
|
||||
]
|
||||
let hairSpace = "\u{200a}"
|
||||
let thinSpace = "\u{2009}"
|
||||
let iconSpacer = UIDevice.current.isNarrowerThanIPhone6 ? hairSpace : thinSpace
|
||||
@@ -219,13 +235,11 @@ extension ConversationViewController {
|
||||
|
||||
subtitleText.appendTemplatedImage(named: Theme.iconName(.timer16), font: subtitleFont)
|
||||
subtitleText.append(iconSpacer, attributes: attributes)
|
||||
subtitleText.append(
|
||||
DateUtil.formatDuration(
|
||||
seconds: disappearingMessagesConfiguration.durationSeconds,
|
||||
useShortFormat: true
|
||||
),
|
||||
attributes: attributes
|
||||
)
|
||||
subtitleText.append(DateUtil.formatDuration(
|
||||
seconds: disappearingMessagesConfiguration.durationSeconds,
|
||||
useShortFormat: true
|
||||
),
|
||||
attributes: attributes)
|
||||
}
|
||||
|
||||
if isVerified {
|
||||
@@ -242,7 +256,7 @@ extension ConversationViewController {
|
||||
)
|
||||
}
|
||||
|
||||
headerView.subtitleLabel.attributedText = subtitleText
|
||||
headerView.attributedSubtitle = subtitleText
|
||||
}
|
||||
|
||||
public var safeContentHeight: CGFloat {
|
||||
|
||||
Reference in New Issue
Block a user