Tweak layout in Story Viewer a little bit.

• larger avatar.
• tweaked UI element spacing per design specs.
This commit is contained in:
Igor Solomennikov 2026-05-19 23:12:09 -07:00 committed by GitHub
parent 6560f22eac
commit ba6e02810d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 66 additions and 49 deletions

View File

@ -261,27 +261,34 @@ class StoryContextViewController: OWSViewController, DatabaseChangeDelegate,
view.addSubview(sendingIndicatorStackView)
sendingIndicatorStackView.autoPinEdges(toEdgesOf: repliesAndViewsButton)
view.addSubview(playbackProgressView)
playbackProgressView.autoPinEdge(.leading, to: .leading, of: mediaViewContainer, withOffset: OWSTableViewController2.defaultHOuterMargin)
playbackProgressView.autoPinEdge(.trailing, to: .trailing, of: mediaViewContainer, withOffset: -OWSTableViewController2.defaultHOuterMargin)
playbackProgressView.autoSetDimension(.height, toSize: 2)
playbackProgressView.isUserInteractionEnabled = false
view.addSubview(playbackProgressView)
let progressViewSideMargin: CGFloat
if UIDevice.current.hasIPhoneXNotch || UIDevice.current.isIPad {
let cornerRadius: CGFloat = if #available(iOS 26, *) { 40 } else { 18 }
// iPhone with notch or iPad (views/replies rendered below media, media is in a card)
mediaViewContainer.layer.cornerRadius = cornerRadius
mediaViewContainer.clipsToBounds = true
onboardingOverlay.layer.cornerRadius = cornerRadius
onboardingOverlay.clipsToBounds = true
progressViewSideMargin = OWSTableViewController2.defaultHOuterMargin + 4
// Equal margins on the sides and the bottom.
playbackProgressView.autoPinEdge(.bottom, to: .bottom, of: mediaViewContainer, withOffset: -progressViewSideMargin)
repliesAndViewsButton.autoPinEdge(.top, to: .bottom, of: mediaViewContainer)
playbackProgressView.autoPinEdge(.bottom, to: .top, of: repliesAndViewsButton, withOffset: -OWSTableViewController2.defaultHOuterMargin)
} else {
progressViewSideMargin = OWSTableViewController2.defaultHOuterMargin
// iPhone with home button (views/replies rendered on top of media, media is fullscreen)
repliesAndViewsButton.autoPinEdge(.bottom, to: .bottom, of: mediaViewContainer)
playbackProgressView.autoPinEdge(.bottom, to: .top, of: repliesAndViewsButton)
mediaViewContainer.autoPinEdge(toSuperviewSafeArea: .bottom)
}
playbackProgressView.autoSetDimension(.height, toSize: 2)
playbackProgressView.autoPinEdge(.leading, to: .leading, of: mediaViewContainer, withOffset: progressViewSideMargin)
playbackProgressView.autoPinEdge(.trailing, to: .trailing, of: mediaViewContainer, withOffset: -progressViewSideMargin)
applyConstraints()

View File

@ -31,8 +31,7 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
private var gradientProtectionViewHeightConstraint: NSLayoutConstraint?
private var contextButton: ContextMenuButton!
private let bottomContentVStack = UIStackView()
private var bottomContentVStack: UIStackView!
init(
item: StoryItem,
contextButton: ContextMenuButton,
@ -45,6 +44,17 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
super.init(frame: .zero)
// Will use these margins to position avatar, contact name and timestamp at the bottom.
directionalLayoutMargins.leading = OWSTableViewController2.defaultHOuterMargin
directionalLayoutMargins.trailing = OWSTableViewController2.defaultHOuterMargin
if UIDevice.current.hasIPhoneXNotch || UIDevice.current.isIPad {
// iPhone with notch or iPad (views/replies rendered below media, media is in a card)
directionalLayoutMargins.bottom = OWSTableViewController2.defaultHOuterMargin + 22
} else {
// iPhone with home button (views/replies rendered on top of media, media is fullscreen)
directionalLayoutMargins.bottom = 80
}
autoPin(toAspectRatio: 9 / 16)
updateMediaView()
@ -55,28 +65,26 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
}
addSubview(gradientProtectionView)
gradientProtectionView.autoPinWidthToSuperview()
gradientProtectionView.autoPinEdge(toSuperviewEdge: .bottom)
gradientProtectionView.translatesAutoresizingMaskIntoConstraints = false
bottomContentVStack = UIStackView(arrangedSubviews: [
captionLabel,
authorRow,
])
bottomContentVStack.axis = .vertical
bottomContentVStack.spacing = 24
addSubview(bottomContentVStack)
bottomContentVStack.translatesAutoresizingMaskIntoConstraints = false
bottomContentVStack.autoPinWidthToSuperview(withMargin: OWSTableViewController2.defaultHOuterMargin)
NSLayoutConstraint.activate([
gradientProtectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
gradientProtectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
gradientProtectionView.bottomAnchor.constraint(equalTo: bottomAnchor),
if UIDevice.current.hasIPhoneXNotch || UIDevice.current.isIPad {
// iPhone with notch or iPad (views/replies rendered below media, media is in a card)
bottomContentVStack.autoPinEdge(toSuperviewEdge: .bottom, withInset: OWSTableViewController2.defaultHOuterMargin + 16)
} else {
// iPhone with home button (views/replies rendered on top of media, media is fullscreen)
bottomContentVStack.autoPinEdge(toSuperviewEdge: .bottom, withInset: 80)
}
bottomContentVStack.autoPinEdge(toSuperviewEdge: .top, withInset: OWSTableViewController2.defaultHOuterMargin)
bottomContentVStack.addArrangedSubview(.vStretchingSpacer())
bottomContentVStack.addArrangedSubview(captionLabel)
bottomContentVStack.addArrangedSubview(authorRow)
bottomContentVStack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
bottomContentVStack.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
bottomContentVStack.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
])
updateCaption()
updateAuthorRow(newContextButton: contextButton)
@ -314,7 +322,20 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
// MARK: - Author Row
private lazy var timestampLabel = UILabel()
private lazy var timestampLabel: UILabel = {
let label = UILabel()
label.font = .dynamicTypeFootnote
label.adjustsFontForContentSizeCategory = true
label.textColor = .Signal.label
label.layer.shadowColor = UIColor.black.cgColor
label.layer.shadowOpacity = 0.4
label.layer.shadowOffset = CGSize(width: 0, height: 1)
label.layer.shadowRadius = 2
label.setCompressionResistanceHorizontalHigh()
label.setContentHuggingHorizontalHigh()
return label
}()
private lazy var authorRow = UIStackView()
private func updateAuthorRow(newContextButton contextButton: ContextMenuButton) {
let (avatarView, nameLabel) = SSKEnvironment.shared.databaseStorageRef.read { (
@ -322,28 +343,18 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
buildNameLabel(transaction: $0),
) }
let nameTrailingView: UIView
let nameTrailingSpacing: CGFloat
let nameHStack = UIStackView(arrangedSubviews: [nameLabel])
if item.message.authorAddress.isSystemStoryAddress {
let icon = UIImageView(image: Theme.iconImage(.official))
icon.contentMode = .center
nameTrailingView = icon
nameTrailingSpacing = 3
nameHStack.addArrangedSubview(UIImageView(image: Theme.iconImage(.official)))
nameHStack.spacing = 3
nameHStack.alignment = .center
} else {
nameTrailingView = timestampLabel
nameTrailingSpacing = 8
nameHStack.addArrangedSubview(timestampLabel)
nameHStack.spacing = 8
nameHStack.alignment = .firstBaseline
}
let metadataStackView: UIStackView
let nameHStack = UIStackView(arrangedSubviews: [
nameLabel,
nameTrailingView,
])
nameHStack.spacing = nameTrailingSpacing
nameHStack.axis = .horizontal
nameHStack.alignment = .center
if
case .privateStory(let uniqueId) = delegate?.context,
let privateStoryThread = SSKEnvironment.shared.databaseStorageRef.read(
@ -404,17 +415,12 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
contextButton.centerYAnchor.constraint(equalTo: authorRow.centerYAnchor),
])
timestampLabel.setCompressionResistanceHorizontalHigh()
timestampLabel.setContentHuggingHorizontalHigh()
timestampLabel.font = .dynamicTypeFootnote
timestampLabel.textColor = .Signal.label
timestampLabel.alpha = 0.8
updateTimestampText()
}
private func buildAvatarView(transaction: DBReadTransaction) -> UIView {
let authorAvatarView = ConversationAvatarView(
sizeClass: .twentyEight,
sizeClass: .thirtySix,
localUserDisplayMode: .asLocalUser,
badged: false,
shape: .circular,
@ -441,7 +447,7 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
}
let groupAvatarView = ConversationAvatarView(
sizeClass: .twentyEight,
sizeClass: .thirtySix,
localUserDisplayMode: .asLocalUser,
badged: false,
shape: .circular,
@ -470,7 +476,7 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
private func buildNameLabel(transaction: DBReadTransaction) -> UIView {
let label = UILabel()
label.textColor = .Signal.label
label.font = UIFont.dynamicTypeSubheadline.semibold()
label.font = UIFont.dynamicTypeSubheadline.bold()
label.text = StoryUtil.authorDisplayName(
for: item.message,
contactsManager: SSKEnvironment.shared.contactManagerRef,
@ -478,6 +484,10 @@ class StoryItemMediaView: UIView, VideoPlayerDelegate {
useShortGroupName: false,
transaction: transaction,
)
label.layer.shadowColor = UIColor.black.cgColor
label.layer.shadowOpacity = 0.4
label.layer.shadowOffset = CGSize(width: 0, height: 1)
label.layer.shadowRadius = 2
return label
}