diff --git a/Signal/src/ViewControllers/HomeView/Stories/Context View/StoryContextViewController.swift b/Signal/src/ViewControllers/HomeView/Stories/Context View/StoryContextViewController.swift index bd2ac1425a..f535564d03 100644 --- a/Signal/src/ViewControllers/HomeView/Stories/Context View/StoryContextViewController.swift +++ b/Signal/src/ViewControllers/HomeView/Stories/Context View/StoryContextViewController.swift @@ -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() diff --git a/Signal/src/ViewControllers/HomeView/Stories/Context View/StoryItemMediaView.swift b/Signal/src/ViewControllers/HomeView/Stories/Context View/StoryItemMediaView.swift index f09740498a..1081237bf7 100644 --- a/Signal/src/ViewControllers/HomeView/Stories/Context View/StoryItemMediaView.swift +++ b/Signal/src/ViewControllers/HomeView/Stories/Context View/StoryItemMediaView.swift @@ -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 }