Add sheet to see who has viewed and replied to your outgoing group stories

This commit is contained in:
Nora Trapp 2022-08-09 19:38:50 -07:00 committed by Nora Trapp
parent 6dceaa615f
commit 3a1741cedf
5 changed files with 223 additions and 7 deletions

View File

@ -956,6 +956,7 @@
88B00D4B28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B00D4A28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift */; };
88B00D4D28A3346100BC9CA0 /* StoryViewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B00D4C28A3346000BC9CA0 /* StoryViewsViewController.swift */; };
88B00D4F28A33B5800BC9CA0 /* StoryPrivateViewsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B00D4E28A33B5800BC9CA0 /* StoryPrivateViewsSheet.swift */; };
88B00D5128A341D000BC9CA0 /* StoryGroupRepliesAndViewsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B00D5028A341CF00BC9CA0 /* StoryGroupRepliesAndViewsSheet.swift */; };
88B2234A283F290400A25048 /* StoryPrivacySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B22349283F290400A25048 /* StoryPrivacySettingsViewController.swift */; };
88B2234C284FABE600A25048 /* StoryThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B2234B284FABE600A25048 /* StoryThumbnailView.swift */; };
88B688B0238F0D1000286F82 /* ReactionsDetailSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B688AF238F0D1000286F82 /* ReactionsDetailSheet.swift */; };
@ -3281,6 +3282,7 @@
88B00D4A28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryGroupReplyViewController.swift; sourceTree = "<group>"; };
88B00D4C28A3346000BC9CA0 /* StoryViewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryViewsViewController.swift; sourceTree = "<group>"; };
88B00D4E28A33B5800BC9CA0 /* StoryPrivateViewsSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryPrivateViewsSheet.swift; sourceTree = "<group>"; };
88B00D5028A341CF00BC9CA0 /* StoryGroupRepliesAndViewsSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryGroupRepliesAndViewsSheet.swift; sourceTree = "<group>"; };
88B22349283F290400A25048 /* StoryPrivacySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryPrivacySettingsViewController.swift; sourceTree = "<group>"; };
88B2234B284FABE600A25048 /* StoryThumbnailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryThumbnailView.swift; sourceTree = "<group>"; };
88B688AF238F0D1000286F82 /* ReactionsDetailSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsDetailSheet.swift; sourceTree = "<group>"; };
@ -6104,6 +6106,7 @@
isa = PBXGroup;
children = (
8864072727EEA658009916B6 /* StoryGroupReplySheet.swift */,
88B00D5028A341CF00BC9CA0 /* StoryGroupRepliesAndViewsSheet.swift */,
88B00D4A28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift */,
8864072927F0D426009916B6 /* StoryGroupReplyLoader.swift */,
8864072B27F0DA37009916B6 /* StoryGroupReplyViewItem.swift */,
@ -10284,6 +10287,7 @@
3428577926BD8777005A2A96 /* Emoji+SkinTones.swift in Sources */,
329D42A225AD65C400DCB449 /* GetStartedBannerCell.swift in Sources */,
348815C9255346A500D4F4C4 /* CVComponent.swift in Sources */,
88B00D5128A341D000BC9CA0 /* StoryGroupRepliesAndViewsSheet.swift in Sources */,
32AC5CE7255B51E900829BD8 /* JoinGroupCallPill.swift in Sources */,
32ECD08A24BFBF9500EDDED0 /* ContactSupportViewController.swift in Sources */,
88A4CC10246CE2760082211F /* TransferProgressView.swift in Sources */,

View File

@ -491,11 +491,20 @@ extension StoryContextViewController: UIGestureRecognizerDelegate {
switch self.context {
case .groupId:
let groupReplyVC = StoryGroupReplySheet(storyMessage: currentItem.message)
groupReplyVC.interactiveTransitionCoordinator = interactiveTransitionCoordinator
groupReplyVC.dismissHandler = { [weak self] in self?.play() }
self.pause()
self.present(groupReplyVC, animated: true)
switch currentItem.message.direction {
case .outgoing:
let groupRepliesAndViewsVC = StoryGroupRepliesAndViewsSheet(storyMessage: currentItem.message)
groupRepliesAndViewsVC.interactiveTransitionCoordinator = interactiveTransitionCoordinator
groupRepliesAndViewsVC.dismissHandler = { [weak self] in self?.play() }
self.pause()
self.present(groupRepliesAndViewsVC, animated: true)
case .incoming:
let groupReplyVC = StoryGroupReplySheet(storyMessage: currentItem.message)
groupReplyVC.interactiveTransitionCoordinator = interactiveTransitionCoordinator
groupReplyVC.dismissHandler = { [weak self] in self?.play() }
self.pause()
self.present(groupReplyVC, animated: true)
}
case .authorUuid:
let directReplyVC = StoryDirectReplySheet(storyMessage: currentItem.message)
directReplyVC.interactiveTransitionCoordinator = interactiveTransitionCoordinator

View File

@ -0,0 +1,192 @@
//
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
import UIKit
import SignalServiceKit
import SignalUI
class StoryGroupRepliesAndViewsSheet: InteractiveSheetViewController {
override var interactiveScrollViews: [UIScrollView] { [groupReplyViewController.tableView, viewsViewController.tableView] }
override var minHeight: CGFloat { CurrentAppContext().frame.height * 0.6 }
override var sheetBackgroundColor: UIColor { .ows_gray90 }
weak var interactiveTransitionCoordinator: StoryInteractiveTransitionCoordinator?
private let groupReplyViewController: StoryGroupReplyViewController
private let viewsViewController: StoryViewsViewController
private let pagingScrollView = UIScrollView()
private lazy var viewsButton = createToggleButton(
title: NSLocalizedString("STORIES_VIEWS_TAB", comment: "Title text for the 'views' tab on the stories views & replies sheet")
) { [weak self] in
self?.switchToViewsTab(animated: true)
}
private lazy var repliesButton = createToggleButton(
title: NSLocalizedString("STORIES_REPLIES_TAB", comment: "Title text for the 'replies' tab on the stories views & replies sheet")
) { [weak self] in
self?.switchToRepliesTab(animated: true)
}
var dismissHandler: (() -> Void)?
init(storyMessage: StoryMessage) {
self.groupReplyViewController = StoryGroupReplyViewController(storyMessage: storyMessage)
self.viewsViewController = StoryViewsViewController(storyMessage: storyMessage)
super.init()
}
public required init() {
fatalError("init() has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let vStack = UIStackView()
vStack.axis = .vertical
vStack.alignment = .center
vStack.spacing = 20
contentView.addSubview(vStack)
vStack.autoPinEdgesToSuperviewEdges()
let hStack = UIStackView(arrangedSubviews: [viewsButton, repliesButton])
hStack.axis = .horizontal
hStack.spacing = 12
vStack.addArrangedSubview(hStack)
pagingScrollView.isPagingEnabled = true
pagingScrollView.showsHorizontalScrollIndicator = false
pagingScrollView.isDirectionalLockEnabled = true
pagingScrollView.delegate = self
vStack.addArrangedSubview(pagingScrollView)
pagingScrollView.autoPinEdge(toSuperviewSafeArea: .left)
pagingScrollView.autoPinEdge(toSuperviewSafeArea: .right)
let pagesContainer = UIView()
pagingScrollView.addSubview(pagesContainer)
pagesContainer.autoPinEdgesToSuperviewEdges()
pagesContainer.autoMatch(.height, to: .height, of: pagingScrollView)
pagesContainer.autoMatch(.width, to: .width, of: pagingScrollView, withMultiplier: 2)
addChild(viewsViewController)
pagesContainer.addSubview(viewsViewController.view)
viewsViewController.view.autoMatch(.width, to: .width, of: pagesContainer, withMultiplier: 0.5)
viewsViewController.view.autoPinHeightToSuperview()
viewsViewController.view.autoPinEdge(toSuperviewEdge: .leading)
groupReplyViewController.delegate = self
addChild(groupReplyViewController)
pagesContainer.addSubview(groupReplyViewController.view)
groupReplyViewController.view.autoMatch(.width, to: .width, of: pagesContainer, withMultiplier: 0.5)
groupReplyViewController.view.autoPinHeightToSuperview()
groupReplyViewController.view.autoPinEdge(.leading, to: .trailing, of: viewsViewController.view)
groupReplyViewController.view.autoPinEdge(toSuperviewEdge: .trailing)
switchToViewsTab(animated: false)
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
super.dismiss(animated: flag) { [dismissHandler] in
completion?()
dismissHandler?()
}
}
private var isManuallySwitchingTabs = false
func switchToRepliesTab(animated: Bool) {
isManuallySwitchingTabs = true
repliesButton.isSelected = true
viewsButton.isSelected = false
pagingScrollView.setContentOffset(CGPoint(x: pagingScrollView.width, y: 0), animated: animated)
isManuallySwitchingTabs = false
}
func switchToViewsTab(animated: Bool) {
isManuallySwitchingTabs = true
repliesButton.isSelected = false
viewsButton.isSelected = true
pagingScrollView.setContentOffset(.zero, animated: animated)
isManuallySwitchingTabs = false
}
func createToggleButton(title: String, block: @escaping () -> Void) -> UIButton {
let button = OWSButton()
button.block = {
guard !button.isSelected else { return }
block()
}
button.autoSetDimension(.height, toSize: 28)
button.layer.cornerRadius = 14
button.clipsToBounds = true
button.titleLabel?.font = UIFont.ows_semiboldFont(withSize: 15)
button.contentEdgeInsets = UIEdgeInsets(hMargin: 12, vMargin: 4)
button.setTitle(title, for: .normal)
button.setTitleColor(Theme.darkThemePrimaryColor, for: .normal)
button.setBackgroundImage(UIImage(color: .ows_gray65), for: .selected)
return button
}
}
extension StoryGroupRepliesAndViewsSheet: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
groupReplyViewController.inputToolbar.resignFirstResponder()
guard !isManuallySwitchingTabs else { return }
if scrollView.contentOffset.x < scrollView.width / 2 {
repliesButton.isSelected = false
viewsButton.isSelected = true
} else {
repliesButton.isSelected = true
viewsButton.isSelected = false
}
}
}
extension StoryGroupRepliesAndViewsSheet: StoryGroupReplyDelegate {
func storyGroupReplyViewControllerDidBeginEditing(_ storyGroupReplyViewController: StoryGroupReplyViewController) {
maximizeHeight()
}
}
extension StoryGroupRepliesAndViewsSheet {
override func presentationController(
forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController
) -> UIPresentationController? {
return nil
}
public func animationController(
forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return StoryReplySheetAnimator(
isPresenting: true,
isInteractive: interactiveTransitionCoordinator != nil,
backdropView: backdropView
)
}
public func animationController(
forDismissed dismissed: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return StoryReplySheetAnimator(
isPresenting: false,
isInteractive: false,
backdropView: backdropView
)
}
public func interactionControllerForPresentation(
using animator: UIViewControllerAnimatedTransitioning
) -> UIViewControllerInteractiveTransitioning? {
interactiveTransitionCoordinator?.mode = .reply
return interactiveTransitionCoordinator
}
}

View File

@ -6,7 +6,13 @@ import Foundation
import UIKit
import SignalServiceKit
protocol StoryGroupReplyDelegate: AnyObject {
func storyGroupReplyViewControllerDidBeginEditing(_ storyGroupReplyViewController: StoryGroupReplyViewController)
}
class StoryGroupReplyViewController: OWSViewController, StoryReplySheet {
weak var delegate: StoryGroupReplyDelegate?
private(set) lazy var tableView = UITableView()
private(set) lazy var inputToolbar = StoryReplyInputToolbar()
private lazy var inputToolbarBottomConstraint = inputToolbar.autoPinEdge(toSuperviewEdge: .bottom)
@ -193,8 +199,7 @@ extension StoryGroupReplyViewController: InputAccessoryViewPlaceholderDelegate {
extension StoryGroupReplyViewController: StoryReplyInputToolbarDelegate {
func storyReplyInputToolbarDidBeginEditing(_ storyReplyInputToolbar: StoryReplyInputToolbar) {
// maximizeHeight()
// TODO:
delegate?.storyGroupReplyViewControllerDidBeginEditing(self)
}
func storyReplyInputToolbarHeightDidChange(_ storyReplyInputToolbar: StoryReplyInputToolbar) {

View File

@ -6154,6 +6154,9 @@
/* Toggle text for the 'replies & reactions' switch in stories settings */
"STORIES_REPLIES_AND_REACTIONS_TOGGLE" = "Allow Replies & Reactions";
/* Title text for the 'replies' tab on the stories views & replies sheet */
"STORIES_REPLIES_TAB" = "Replies";
/* alert notifying that the 'save' operation failed */
"STORIES_SAVE_FAILED" = "Failed to Save Story";
@ -6178,6 +6181,9 @@
/* Title for the stories view. */
"STORIES_TITLE" = "Stories";
/* Title text for the 'views' tab on the stories views & replies sheet */
"STORIES_VIEWS_TAB" = "Views";
/* One of two possible sources when composing a new story. Displayed at the bottom in in-app camera. */
"STORY_COMPOSER_CAMERA" = "Camera";