Split group reply sheet into two components

This commit is contained in:
Nora Trapp 2022-08-09 17:32:08 -07:00 committed by Nora Trapp
parent 43943ca621
commit f52accd3c5
10 changed files with 118 additions and 77 deletions

View File

@ -953,6 +953,7 @@
88ABB8B925349F6C00229EAA /* GroupCallVideoGridLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABB8B825349F6C00229EAA /* GroupCallVideoGridLayout.swift */; };
88ABB8BB2535066100229EAA /* GroupCallVideoOverflow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABB8BA2535066000229EAA /* GroupCallVideoOverflow.swift */; };
88ABB8BD25352B8300229EAA /* GroupCallMemberSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ABB8BC25352B8200229EAA /* GroupCallMemberSheet.swift */; };
88B00D4B28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B00D4A28A32DB600BC9CA0 /* StoryGroupReplyViewController.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 */; };
@ -3275,6 +3276,7 @@
88ABB8B825349F6C00229EAA /* GroupCallVideoGridLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupCallVideoGridLayout.swift; sourceTree = "<group>"; };
88ABB8BA2535066000229EAA /* GroupCallVideoOverflow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupCallVideoOverflow.swift; sourceTree = "<group>"; };
88ABB8BC25352B8200229EAA /* GroupCallMemberSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupCallMemberSheet.swift; sourceTree = "<group>"; };
88B00D4A28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryGroupReplyViewController.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>"; };
@ -5979,7 +5981,7 @@
path = Avatars;
sourceTree = "<group>";
};
88423A50280A1703007D2918 /* Reply Sheets */ = {
88423A50280A1703007D2918 /* Replies & Views Sheets */ = {
isa = PBXGroup;
children = (
8864072F27F21AA7009916B6 /* Group Reply Sheet */,
@ -5988,7 +5990,7 @@
8864073027F21AD7009916B6 /* StoryReplyInputToolbar.swift */,
88423A53280A2675007D2918 /* StoryReplyPreviewView.swift */,
);
path = "Reply Sheets";
path = "Replies & Views Sheets";
sourceTree = "<group>";
};
884DB94A27DE66E000C6A309 /* Context View */ = {
@ -6014,7 +6016,7 @@
886292112835606D00AA0C3B /* MyStoryCell.swift */,
88B2234B284FABE600A25048 /* StoryThumbnailView.swift */,
884DB94A27DE66E000C6A309 /* Context View */,
88423A50280A1703007D2918 /* Reply Sheets */,
88423A50280A1703007D2918 /* Replies & Views Sheets */,
88863A4C280CADDC00977F69 /* Transitions */,
);
path = Stories;
@ -6096,6 +6098,7 @@
isa = PBXGroup;
children = (
8864072727EEA658009916B6 /* StoryGroupReplySheet.swift */,
88B00D4A28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift */,
8864072927F0D426009916B6 /* StoryGroupReplyLoader.swift */,
8864072B27F0DA37009916B6 /* StoryGroupReplyViewItem.swift */,
8864072D27F0E8DF009916B6 /* StoryGroupReplyCell.swift */,
@ -10083,6 +10086,7 @@
88423A52280A171E007D2918 /* StoryDirectReplySheet.swift in Sources */,
34ACA7E42733159600E47AD4 /* OnboardingPermissionsViewController.swift in Sources */,
8871B16F25F007DE00D4E070 /* ProfileSettingsViewController.swift in Sources */,
88B00D4B28A32DB600BC9CA0 /* StoryGroupReplyViewController.swift in Sources */,
4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */,
4C30E226234FB033009558B7 /* SecondaryLinkingQRCodeViewController.swift in Sources */,
88ABB8BB2535066100229EAA /* GroupCallVideoOverflow.swift in Sources */,

View File

@ -0,0 +1,87 @@
//
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
import UIKit
import SignalServiceKit
class StoryGroupReplySheet: InteractiveSheetViewController {
override var interactiveScrollViews: [UIScrollView] { [groupReplyViewController.tableView] }
override var minHeight: CGFloat { maximizedHeight }
override var sheetBackgroundColor: UIColor { .ows_gray90 }
weak var interactiveTransitionCoordinator: StoryInteractiveTransitionCoordinator?
private let groupReplyViewController: StoryGroupReplyViewController
var dismissHandler: (() -> Void)?
init(storyMessage: StoryMessage) {
self.groupReplyViewController = StoryGroupReplyViewController(storyMessage: storyMessage)
super.init()
}
public required init() {
fatalError("init() has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
addChild(groupReplyViewController)
contentView.addSubview(groupReplyViewController.view)
groupReplyViewController.view.autoPinEdgesToSuperviewEdges()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
groupReplyViewController.inputToolbar.becomeFirstResponder()
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
super.dismiss(animated: flag) { [dismissHandler] in
completion?()
dismissHandler?()
}
}
}
extension StoryGroupReplySheet {
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,13 +6,9 @@ import Foundation
import UIKit
import SignalServiceKit
class StoryGroupReplySheet: InteractiveSheetViewController, StoryReplySheet {
override var interactiveScrollViews: [UIScrollView] { [tableView] }
override var minHeight: CGFloat { CurrentAppContext().frame.height * 0.6 }
override var sheetBackgroundColor: UIColor { .ows_gray90 }
private lazy var tableView = UITableView()
lazy var inputToolbar = StoryReplyInputToolbar()
class StoryGroupReplyViewController: OWSViewController, StoryReplySheet {
private(set) lazy var tableView = UITableView()
private(set) lazy var inputToolbar = StoryReplyInputToolbar()
private lazy var inputToolbarBottomConstraint = inputToolbar.autoPinEdge(toSuperviewEdge: .bottom)
private lazy var contextMenu = ContextMenuInteraction(delegate: self)
@ -36,13 +32,10 @@ class StoryGroupReplySheet: InteractiveSheetViewController, StoryReplySheet {
let storyMessage: StoryMessage
lazy var thread: TSThread? = databaseStorage.read { storyMessage.context.thread(transaction: $0) }
weak var interactiveTransitionCoordinator: StoryInteractiveTransitionCoordinator?
var reactionPickerBackdrop: UIView?
var reactionPicker: MessageReactionPicker?
var dismissHandler: (() -> Void)?
init(storyMessage: StoryMessage) {
self.storyMessage = storyMessage
@ -54,10 +47,6 @@ class StoryGroupReplySheet: InteractiveSheetViewController, StoryReplySheet {
}
}
public required init() {
fatalError("init() has not been implemented")
}
fileprivate var replyLoader: StoryGroupReplyLoader?
override func viewDidLoad() {
super.viewDidLoad()
@ -70,11 +59,11 @@ class StoryGroupReplySheet: InteractiveSheetViewController, StoryReplySheet {
tableView.backgroundColor = .ows_gray90
tableView.addInteraction(contextMenu)
contentView.addSubview(tableView)
view.addSubview(tableView)
tableView.autoPinEdgesToSuperviewEdges()
inputToolbar.delegate = self
contentView.addSubview(inputToolbar)
view.addSubview(inputToolbar)
inputToolbar.autoPinWidthToSuperview()
inputToolbarBottomConstraint.isActive = true
@ -84,7 +73,7 @@ class StoryGroupReplySheet: InteractiveSheetViewController, StoryReplySheet {
replyLoader = StoryGroupReplyLoader(storyMessage: storyMessage, threadUniqueId: thread?.uniqueId, tableView: tableView)
contentView.addSubview(emptyStateView)
view.addSubview(emptyStateView)
emptyStateView.autoPinWidthToSuperview()
emptyStateView.autoPinEdge(toSuperviewEdge: .top)
emptyStateView.autoPinEdge(.bottom, to: .top, of: inputToolbar)
@ -92,20 +81,13 @@ class StoryGroupReplySheet: InteractiveSheetViewController, StoryReplySheet {
public override var inputAccessoryView: UIView? { inputAccessoryPlaceholder }
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
super.dismiss(animated: flag) { [dismissHandler] in
completion?()
dismissHandler?()
}
}
func didSendMessage() {
replyLoader?.reload()
inputToolbar.messageBody = nil
}
}
extension StoryGroupReplySheet: UIScrollViewDelegate {
extension StoryGroupReplyViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let visibleRows = tableView.indexPathsForVisibleRows?.map({ $0.row }),
!visibleRows.isEmpty,
@ -125,11 +107,11 @@ extension StoryGroupReplySheet: UIScrollViewDelegate {
}
}
extension StoryGroupReplySheet: UITableViewDelegate {
extension StoryGroupReplyViewController: UITableViewDelegate {
}
extension StoryGroupReplySheet: UITableViewDataSource {
extension StoryGroupReplyViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let item = replyLoader?.replyItem(for: indexPath) else {
owsFailDebug("Missing item for cell at indexPath \(indexPath)")
@ -153,7 +135,7 @@ extension StoryGroupReplySheet: UITableViewDataSource {
}
}
extension StoryGroupReplySheet: InputAccessoryViewPlaceholderDelegate {
extension StoryGroupReplyViewController: InputAccessoryViewPlaceholderDelegate {
public func inputAccessoryPlaceholderKeyboardIsPresenting(animationDuration: TimeInterval, animationCurve: UIView.AnimationCurve) {
handleKeyboardStateChange(animationDuration: animationDuration, animationCurve: animationCurve)
}
@ -209,9 +191,10 @@ extension StoryGroupReplySheet: InputAccessoryViewPlaceholderDelegate {
}
}
extension StoryGroupReplySheet: StoryReplyInputToolbarDelegate {
extension StoryGroupReplyViewController: StoryReplyInputToolbarDelegate {
func storyReplyInputToolbarDidBeginEditing(_ storyReplyInputToolbar: StoryReplyInputToolbar) {
maximizeHeight()
// maximizeHeight()
// TODO:
}
func storyReplyInputToolbarHeightDidChange(_ storyReplyInputToolbar: StoryReplyInputToolbar) {
@ -219,7 +202,7 @@ extension StoryGroupReplySheet: StoryReplyInputToolbarDelegate {
}
}
extension StoryGroupReplySheet: ContextMenuInteractionDelegate {
extension StoryGroupReplyViewController: ContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: ContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> ContextMenuConfiguration? {
guard let indexPath = tableView.indexPathForRow(at: location),
let item = replyLoader?.replyItem(for: indexPath) else { return nil }
@ -279,42 +262,3 @@ extension StoryGroupReplySheet: ContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: ContextMenuInteraction, didEndForConfiguration configuration: ContextMenuConfiguration) {}
}
extension StoryGroupReplySheet {
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

@ -152,6 +152,16 @@ class StoryReplyInputToolbar: UIView {
return textView
}()
@discardableResult
override func becomeFirstResponder() -> Bool {
textView.becomeFirstResponder()
}
@discardableResult
override func resignFirstResponder() -> Bool {
textView.resignFirstResponder()
}
private let placeholderText = OWSLocalizedString(
"STORY_REPLY_TEXT_FIELD_PLACEHOLDER",
comment: "placeholder text for replying to a story"

View File

@ -11,10 +11,6 @@ protocol StoryReplySheet: OWSViewController, StoryReplyInputToolbarDelegate, Mes
var inputToolbar: StoryReplyInputToolbar { get }
var storyMessage: StoryMessage { get }
var thread: TSThread? { get }
var interactiveTransitionCoordinator: StoryInteractiveTransitionCoordinator? { get set }
var backdropView: UIView? { get }
var dismissHandler: (() -> Void)? { get set }
var reactionPickerBackdrop: UIView? { get set }
var reactionPicker: MessageReactionPicker? { get set }