Add sheet to see who has viewed and replied to your outgoing group stories
This commit is contained in:
parent
6dceaa615f
commit
3a1741cedf
@ -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 */,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user