Render group story sending state on stories list
This commit is contained in:
parent
bf1575f819
commit
5d21020614
@ -781,7 +781,7 @@
|
||||
669E8FED28B4177900043D28 /* OWSSignalServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E8FEC28B4177800043D28 /* OWSSignalServiceMock.swift */; };
|
||||
669E8FEF28B417D500043D28 /* OWSSignalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E8FEE28B417D500043D28 /* OWSSignalService.swift */; };
|
||||
669E8FF028B4185300043D28 /* OWSURLSessionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E8FE528B4149200043D28 /* OWSURLSessionMock.swift */; };
|
||||
669E8FF128B41A8500043D28 /* StoryAuthorUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668AB0CB28AD610600B31984 /* StoryAuthorUtil.swift */; };
|
||||
669E8FF128B41A8500043D28 /* StoryUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 668AB0CB28AD610600B31984 /* StoryUtil.swift */; };
|
||||
669E900028B42B7A00043D28 /* SystemStoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E8FFF28B42B7A00043D28 /* SystemStoryManager.swift */; };
|
||||
669E900528B42CCF00043D28 /* SystemStoryManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E900428B42CCF00043D28 /* SystemStoryManagerTest.swift */; };
|
||||
669E900728B43F5B00043D28 /* SystemStoryManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669E900628B43F5B00043D28 /* SystemStoryManagerProtocol.swift */; };
|
||||
@ -3030,7 +3030,7 @@
|
||||
661396AE28BE881E00E0C4DF /* ChainedPromiseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChainedPromiseTest.swift; sourceTree = "<group>"; };
|
||||
661602A328BEB94400C1932D /* StoryListDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryListDataSource.swift; sourceTree = "<group>"; };
|
||||
6657FDE7B91C2845BB3BEAB5 /* Pods_SignalShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
668AB0CB28AD610600B31984 /* StoryAuthorUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryAuthorUtil.swift; sourceTree = "<group>"; };
|
||||
668AB0CB28AD610600B31984 /* StoryUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryUtil.swift; sourceTree = "<group>"; };
|
||||
668CAB3D289983520085A2C3 /* AudioMessagePlaybackRateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMessagePlaybackRateView.swift; sourceTree = "<group>"; };
|
||||
668FE09A28B923A4008B9071 /* Bool+SSK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bool+SSK.swift"; sourceTree = "<group>"; };
|
||||
668FE09E28B947ED008B9071 /* StoryHidingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryHidingManager.swift; sourceTree = "<group>"; };
|
||||
@ -6105,10 +6105,10 @@
|
||||
886292112835606D00AA0C3B /* MyStoryCell.swift */,
|
||||
8862920F28355B8000AA0C3B /* MyStoryViewModel.swift */,
|
||||
8852572827DD366D0032073C /* StoriesViewController.swift */,
|
||||
668AB0CB28AD610600B31984 /* StoryAuthorUtil.swift */,
|
||||
884DB94627DD754700C6A309 /* StoryCell.swift */,
|
||||
661602A328BEB94400C1932D /* StoryListDataSource.swift */,
|
||||
88B2234B284FABE600A25048 /* StoryThumbnailView.swift */,
|
||||
668AB0CB28AD610600B31984 /* StoryUtil.swift */,
|
||||
884DB94427DD70F700C6A309 /* StoryViewModel.swift */,
|
||||
);
|
||||
path = Stories;
|
||||
@ -10374,7 +10374,6 @@
|
||||
34B3F87B1E8DF1700035BE1A /* SplashViewController.swift in Sources */,
|
||||
349767E425B8744700ECE1B0 /* StickerPackViewController.swift in Sources */,
|
||||
8852572927DD366D0032073C /* StoriesViewController.swift in Sources */,
|
||||
669E8FF128B41A8500043D28 /* StoryAuthorUtil.swift in Sources */,
|
||||
884DB94727DD754700C6A309 /* StoryCell.swift in Sources */,
|
||||
884DB95027DE67BB00C6A309 /* StoryContextViewController.swift in Sources */,
|
||||
88423A52280A171E007D2918 /* StoryDirectReplySheet.swift in Sources */,
|
||||
@ -10398,6 +10397,7 @@
|
||||
88863A54280CAEAA00977F69 /* StoryReplySheetAnimator.swift in Sources */,
|
||||
88863A4E280CAE0800977F69 /* StorySlideAnimator.swift in Sources */,
|
||||
88B2234C284FABE600A25048 /* StoryThumbnailView.swift in Sources */,
|
||||
669E8FF128B41A8500043D28 /* StoryUtil.swift in Sources */,
|
||||
884DB94527DD70F700C6A309 /* StoryViewModel.swift in Sources */,
|
||||
88B00D4D28A3346100BC9CA0 /* StoryViewsViewController.swift in Sources */,
|
||||
88863A50280CAE4400977F69 /* StoryZoomAnimator.swift in Sources */,
|
||||
|
||||
@ -274,7 +274,7 @@ class StoryItemMediaView: UIView {
|
||||
)
|
||||
|
||||
authorAvatarView.update(transaction) { config in
|
||||
config.dataSource = try? StoryAuthorUtil.authorAvatarDataSource(
|
||||
config.dataSource = try? StoryUtil.authorAvatarDataSource(
|
||||
for: item.message,
|
||||
transaction: transaction
|
||||
)
|
||||
@ -283,7 +283,7 @@ class StoryItemMediaView: UIView {
|
||||
switch item.message.context {
|
||||
case .groupId:
|
||||
guard
|
||||
let groupAvatarDataSource = try? StoryAuthorUtil.contextAvatarDataSource(
|
||||
let groupAvatarDataSource = try? StoryUtil.contextAvatarDataSource(
|
||||
for: item.message,
|
||||
transaction: transaction
|
||||
)
|
||||
@ -323,7 +323,7 @@ class StoryItemMediaView: UIView {
|
||||
let label = UILabel()
|
||||
label.textColor = Theme.darkThemePrimaryColor
|
||||
label.font = UIFont.ows_dynamicTypeSubheadline.ows_semibold
|
||||
label.text = StoryAuthorUtil.authorDisplayName(
|
||||
label.text = StoryUtil.authorDisplayName(
|
||||
for: item.message,
|
||||
contactsManager: contactsManager,
|
||||
useFullNameForLocalAddress: false,
|
||||
|
||||
@ -129,7 +129,7 @@ extension MyStoriesViewController: UITableViewDelegate {
|
||||
guard let thread = thread(for: indexPath.section), let item = item(for: indexPath) else { return }
|
||||
|
||||
if item.message.sendingState == .failed {
|
||||
return askToResend(for: item)
|
||||
return StoryUtil.askToResend(item.message, in: item.thread, from: self)
|
||||
}
|
||||
|
||||
let vc = StoryPageViewController(
|
||||
@ -141,40 +141,6 @@ extension MyStoriesViewController: UITableViewDelegate {
|
||||
vc.contextDataSource = self
|
||||
present(vc, animated: true)
|
||||
}
|
||||
|
||||
private func askToResend(for item: OutgoingStoryItem) {
|
||||
let actionSheet = ActionSheetController(message: NSLocalizedString(
|
||||
"STORY_RESEND_MESSAGE_ACTION_SHEET",
|
||||
comment: "Title for the dialog asking user if they wish to resend a failed story message."
|
||||
))
|
||||
actionSheet.addAction(OWSActionSheets.cancelAction)
|
||||
actionSheet.addAction(.init(title: CommonStrings.deleteButton, style: .destructive, handler: { _ in
|
||||
Self.databaseStorage.write { transaction in
|
||||
item.message.remotelyDelete(for: item.thread, transaction: transaction)
|
||||
}
|
||||
}))
|
||||
actionSheet.addAction(.init(title: CommonStrings.sendMessage, handler: { [weak self] _ in
|
||||
self?.resend(item: item)
|
||||
}))
|
||||
presentActionSheet(actionSheet, animated: true)
|
||||
}
|
||||
|
||||
private func resend(item: OutgoingStoryItem) {
|
||||
guard case .outgoing(let recipientStates) = item.message.manifest else { return }
|
||||
let stoppedBySafetyNumberChange = SafetyNumberConfirmationSheet.presentIfNecessary(
|
||||
addresses: recipientStates.keys.map { .init(uuid: $0) },
|
||||
confirmationText: SafetyNumberStrings.confirmSendButton
|
||||
) { [weak self] confirmedSafetyNumberChange in
|
||||
guard confirmedSafetyNumberChange else { return }
|
||||
self?.resend(item: item)
|
||||
}
|
||||
|
||||
guard !stoppedBySafetyNumberChange else { return }
|
||||
|
||||
Self.databaseStorage.write { transaction in
|
||||
item.message.resendMessageToFailedRecipients(transaction: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MyStoriesViewController: UITableViewDataSource {
|
||||
|
||||
@ -80,7 +80,7 @@ class StoriesViewController: OWSViewController, StoryListDataSourceDelegate {
|
||||
case .visibleStories, .hiddenStories:
|
||||
guard let cell = self.tableView.cellForRow(at: indexPath) as? StoryCell else { continue }
|
||||
guard let model = self.model(for: indexPath) else { continue }
|
||||
cell.configureTimestamp(with: model)
|
||||
cell.configureSubtitle(with: model)
|
||||
case .none:
|
||||
owsFailDebug("Unexpected story type")
|
||||
}
|
||||
@ -243,6 +243,20 @@ extension StoriesViewController: UITableViewDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
// Navigate to "My Stories" rather than the viewer if the message is failed
|
||||
if model.latestMessageSendingState == .failed {
|
||||
guard let latestMessage = model.messages.last else {
|
||||
owsFailDebug("Missing message for failed send")
|
||||
return
|
||||
}
|
||||
guard let latestMessageThread = databaseStorage.read(block: { latestMessage.context.thread(transaction: $0) }) else {
|
||||
owsFailDebug("Missing thread for failed send")
|
||||
return
|
||||
}
|
||||
StoryUtil.askToResend(latestMessage, in: latestMessageThread, from: self)
|
||||
return
|
||||
}
|
||||
|
||||
// If we tap on a story with unviewed stories, we only want the viewer
|
||||
// to page through unviewed contexts.
|
||||
let filterViewed = model.hasUnviewedMessages
|
||||
|
||||
@ -12,12 +12,15 @@ class StoryCell: UITableViewCell {
|
||||
|
||||
let nameLabel = UILabel()
|
||||
let nameIconView = UIImageView()
|
||||
let timestampLabel = UILabel()
|
||||
let subtitleLabel = UILabel()
|
||||
let avatarView = ConversationAvatarView(sizeClass: .fiftySix, localUserDisplayMode: .asUser, useAutolayout: true)
|
||||
let attachmentThumbnail = UIView()
|
||||
let replyImageView = UIImageView()
|
||||
|
||||
let failedIconView = UIImageView()
|
||||
|
||||
let contentHStackView = UIStackView()
|
||||
let subtitleStack = UIStackView()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
@ -32,7 +35,16 @@ class StoryCell: UITableViewCell {
|
||||
nameStack.alignment = .center
|
||||
nameStack.spacing = 3
|
||||
|
||||
let vStack = UIStackView(arrangedSubviews: [nameStack, timestampLabel, replyImageView])
|
||||
failedIconView.autoSetDimension(.width, toSize: 16)
|
||||
failedIconView.contentMode = .scaleAspectFit
|
||||
failedIconView.tintColor = .ows_accentRed
|
||||
|
||||
subtitleStack.addArrangedSubviews([failedIconView, subtitleLabel])
|
||||
subtitleStack.axis = .horizontal
|
||||
subtitleStack.alignment = .center
|
||||
subtitleStack.spacing = 6
|
||||
|
||||
let vStack = UIStackView(arrangedSubviews: [nameStack, subtitleStack, replyImageView])
|
||||
vStack.axis = .vertical
|
||||
vStack.alignment = .leading
|
||||
|
||||
@ -54,7 +66,7 @@ class StoryCell: UITableViewCell {
|
||||
}
|
||||
|
||||
func configure(with model: StoryViewModel) {
|
||||
configureTimestamp(with: model)
|
||||
configureSubtitle(with: model)
|
||||
|
||||
switch model.context {
|
||||
case .authorUuid:
|
||||
@ -94,10 +106,24 @@ class StoryCell: UITableViewCell {
|
||||
contentView.alpha = model.isHidden ? 0.27 : 1
|
||||
}
|
||||
|
||||
func configureTimestamp(with model: StoryViewModel) {
|
||||
timestampLabel.isHidden = model.isSystemStory
|
||||
timestampLabel.font = .ows_dynamicTypeSubheadline
|
||||
timestampLabel.textColor = Theme.secondaryTextAndIconColor
|
||||
timestampLabel.text = DateUtil.formatTimestampRelatively(model.latestMessageTimestamp)
|
||||
func configureSubtitle(with model: StoryViewModel) {
|
||||
subtitleStack.isHidden = model.isSystemStory
|
||||
subtitleLabel.font = .ows_dynamicTypeSubheadline
|
||||
subtitleLabel.textColor = Theme.secondaryTextAndIconColor
|
||||
|
||||
switch model.latestMessageSendingState {
|
||||
case .sent:
|
||||
subtitleLabel.text = DateUtil.formatTimestampRelatively(model.latestMessageTimestamp)
|
||||
failedIconView.isHiddenInStackView = true
|
||||
case .sending, .pending:
|
||||
subtitleLabel.text = NSLocalizedString("STORY_SENDING", comment: "Text indicating that the story is currently sending")
|
||||
failedIconView.isHiddenInStackView = true
|
||||
case .failed:
|
||||
subtitleLabel.text = NSLocalizedString("STORY_SEND_FAILED", comment: "Text indicating that the story send has failed")
|
||||
failedIconView.image = Theme.iconImage(.error16)
|
||||
failedIconView.isHiddenInStackView = false
|
||||
case .sent_OBSOLETE, .delivered_OBSOLETE:
|
||||
owsFailDebug("Unexpected legacy sending state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,9 +3,10 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SignalServiceKit
|
||||
|
||||
/// Container for util methods related to story authors.
|
||||
public enum StoryAuthorUtil {
|
||||
public enum StoryUtil: Dependencies {
|
||||
|
||||
static func authorDisplayName(
|
||||
for storyMessage: StoryMessage,
|
||||
@ -88,4 +89,38 @@ public enum StoryAuthorUtil {
|
||||
badge: nil
|
||||
)
|
||||
}
|
||||
|
||||
static func askToResend(_ message: StoryMessage, in thread: TSThread, from vc: UIViewController) {
|
||||
let actionSheet = ActionSheetController(message: NSLocalizedString(
|
||||
"STORY_RESEND_MESSAGE_ACTION_SHEET",
|
||||
comment: "Title for the dialog asking user if they wish to resend a failed story message."
|
||||
))
|
||||
actionSheet.addAction(OWSActionSheets.cancelAction)
|
||||
actionSheet.addAction(.init(title: CommonStrings.deleteButton, style: .destructive, handler: { _ in
|
||||
Self.databaseStorage.write { transaction in
|
||||
message.remotelyDelete(for: thread, transaction: transaction)
|
||||
}
|
||||
}))
|
||||
actionSheet.addAction(.init(title: CommonStrings.sendMessage, handler: { _ in
|
||||
resend(message)
|
||||
}))
|
||||
vc.presentActionSheet(actionSheet, animated: true)
|
||||
}
|
||||
|
||||
private static func resend(_ message: StoryMessage) {
|
||||
guard case .outgoing(let recipientStates) = message.manifest else { return }
|
||||
let stoppedBySafetyNumberChange = SafetyNumberConfirmationSheet.presentIfNecessary(
|
||||
addresses: recipientStates.keys.map { .init(uuid: $0) },
|
||||
confirmationText: SafetyNumberStrings.confirmSendButton
|
||||
) { confirmedSafetyNumberChange in
|
||||
guard confirmedSafetyNumberChange else { return }
|
||||
resend(message)
|
||||
}
|
||||
|
||||
guard !stoppedBySafetyNumberChange else { return }
|
||||
|
||||
Self.databaseStorage.write { transaction in
|
||||
message.resendMessageToFailedRecipients(transaction: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ struct StoryViewModel: Dependencies {
|
||||
let latestMessageName: String
|
||||
let latestMessageTimestamp: UInt64
|
||||
let latestMessageViewedTimestamp: UInt64?
|
||||
let latestMessageSendingState: TSOutgoingMessageState
|
||||
|
||||
let threadUniqueId: String?
|
||||
|
||||
@ -45,15 +46,16 @@ struct StoryViewModel: Dependencies {
|
||||
self.context = latestMessage.context
|
||||
self.hasReplies = InteractionFinder.hasReplies(for: sortedFilteredMessages, transaction: transaction)
|
||||
|
||||
latestMessageName = StoryAuthorUtil.authorDisplayName(
|
||||
latestMessageName = StoryUtil.authorDisplayName(
|
||||
for: latestMessage,
|
||||
contactsManager: Self.contactsManager,
|
||||
transaction: transaction
|
||||
)
|
||||
latestMessageAvatarDataSource = try StoryAuthorUtil.contextAvatarDataSource(for: latestMessage, transaction: transaction)
|
||||
latestMessageAvatarDataSource = try StoryUtil.contextAvatarDataSource(for: latestMessage, transaction: transaction)
|
||||
latestMessageAttachment = .from(latestMessage.attachment, transaction: transaction)
|
||||
latestMessageTimestamp = latestMessage.timestamp
|
||||
latestMessageViewedTimestamp = latestMessage.localUserViewedTimestamp
|
||||
latestMessageSendingState = latestMessage.sendingState
|
||||
|
||||
let threadUniqueId = context.threadUniqueId(transaction: transaction)
|
||||
self.threadUniqueId = threadUniqueId
|
||||
|
||||
Loading…
Reference in New Issue
Block a user