Skip to content

Commit

Permalink
Improve mark as read SC messages
Browse files Browse the repository at this point in the history
  • Loading branch information
ykyivskyi-gl authored and Egor Egorov committed Dec 5, 2024
1 parent 37c9a9d commit 09d8132
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ import Foundation
extension SecureConversations.TranscriptModel {
func showLeaveConversationDialogIfNeeded() {
if environment.shouldShowLeaveSecureConversationDialog {
engagementAction?(.showAlert(.leaveCurrentConversation { [weak self] in
self?.environment.leaveCurrentSecureConversation()
}))
let action = EngagementViewModel.Action.showAlert(
.leaveCurrentConversation(
confirmed: { [weak self] in
self?.environment.leaveCurrentSecureConversation()
}, declined: { [weak self] in
guard let self else {
return
}
self.markMessagesAsRead(with: self.hasUnreadMessages)
}
)
)
engagementAction?(action)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ extension SecureConversations {

private let deliveredStatusText: String
private let failedToDeliverStatusText: String
private(set) var hasUnreadMessages = false

var numberOfSections: Int {
sections.count
Expand Down Expand Up @@ -554,13 +555,14 @@ extension SecureConversations.TranscriptModel {
divider: ChatItem(kind: .unreadMessageDivider)
)

self.hasUnreadMessages = messagesWithUnreadCount.unreadCount > 0
self.historySection.set(itemsWithDivider)
self.action?(.refreshSection(self.historySection.index))
self.action?(.scrollToBottom(animated: false))
completion(messagesWithUnreadCount.messages)
if messagesWithUnreadCount.unreadCount > 0 {
self.markMessagesAsRead()
}
markMessagesAsRead(
with: self.hasUnreadMessages && !environment.shouldShowLeaveSecureConversationDialog
)

if let item = items.last, case .gvaQuickReply(_, let button, _, _) = item.kind {
let props = button.options.compactMap { [weak self] in self?.quickReplyOption($0) }
Expand Down Expand Up @@ -589,18 +591,22 @@ extension SecureConversations.TranscriptModel {
// We no longer need to listen to Interactor events,
// so unsubscribe.
self.environment.interactor.removeObserver(self)
markMessagesAsRead()
delegate?(.upgradeToChatEngagement(self))
case let .receivedMessage(message):
receiveMessage(from: .socket(message))
markMessagesAsRead(delayed: false)
default:
break
}
}

func markMessagesAsRead() {
func markMessagesAsRead(delayed: Bool = true, with predicate: Bool = true) {
guard predicate else {
return
}
let mainQueue = environment.gcd.mainQueue
let dispatchTime: DispatchTime = .now() + .seconds(Self.markUnreadMessagesDelaySeconds)
let delay = DispatchTimeInterval.seconds(delayed ? Self.markUnreadMessagesDelaySeconds : 0)
let dispatchTime: DispatchTime = .now() + delay

mainQueue.asyncAfterDeadline(dispatchTime) { [environment, weak historySection, action, weak self] in
_ = environment.secureMarkMessagesAsRead { result in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ extension AlertViewController {
func makeLeaveConversationAlertView(
with conf: ConfirmationAlertConfiguration,
accessibilityIdentifier: String,
confirmed: @escaping () -> Void
confirmed: @escaping () -> Void,
declined: (() -> Void)?
) -> AlertView {
let alertView = makeAlertView(
with: conf,
Expand Down Expand Up @@ -33,7 +34,7 @@ extension AlertViewController {
let declineButton = ActionButton(
props: ActionButton.Props(
style: positiveButtonStyle,
tap: .init { [weak self] in self?.dismiss(animated: true) },
tap: .init { [weak self] in self?.dismiss(animated: true) { declined?() } },
accessibilityIdentifier: "alert_positive_button"
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,12 @@ class AlertViewController: UIViewController, Replaceable {
accessibilityIdentifier: accessibilityIdentifier,
confirmed: confirmed
)
case let .leaveConversation(conf, accessibilityIdentifier, confirmed):
case let .leaveConversation(conf, accessibilityIdentifier, confirmed, declined):
return makeLeaveConversationAlertView(
with: conf,
accessibilityIdentifier: accessibilityIdentifier,
confirmed: confirmed
confirmed: confirmed,
declined: declined
)
case let .singleAction(conf, accessibilityIdentifier, actionTapped):
return makeSingleActionAlertView(
Expand Down
2 changes: 1 addition & 1 deletion GliaWidgets/Sources/AlertManager/AlertInputType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ enum AlertInputType: Equatable {
declined: (() -> Void)? = nil,
answer: CoreSdkClient.AnswerBlock
)
case leaveCurrentConversation(confirmed: () -> Void)
case leaveCurrentConversation(confirmed: () -> Void, declined: (() -> Void)? = nil)

static func == (lhs: AlertInputType, rhs: AlertInputType) -> Bool {
switch (lhs, rhs) {
Expand Down
3 changes: 2 additions & 1 deletion GliaWidgets/Sources/AlertManager/AlertType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ enum AlertType {
case leaveConversation(
conf: ConfirmationAlertConfiguration,
accessibilityIdentifier: String,
confirmed: () -> Void
confirmed: () -> Void,
declined: (() -> Void)?
)
case singleAction(
conf: SingleActionAlertConfiguration,
Expand Down
10 changes: 5 additions & 5 deletions GliaWidgets/Sources/AlertManager/AlertTypeComposer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,8 @@ extension AlertManager.AlertTypeComposer {
error: error,
dismissed: dismissed
)

case let .leaveCurrentConversation(confirmed):
return leaveCurrentConversationAlertType(confirmed: confirmed)
case let .leaveCurrentConversation(confirmed, declined):
return leaveCurrentConversationAlertType(confirmed: confirmed, declined: declined)
}
}

Expand Down Expand Up @@ -335,12 +334,13 @@ private extension AlertManager.AlertTypeComposer {
)
}

func leaveCurrentConversationAlertType(confirmed: @escaping () -> Void) -> AlertType {
func leaveCurrentConversationAlertType(confirmed: @escaping () -> Void, declined: (() -> Void)?) -> AlertType {
environment.log.prefixed(Self.self).info("Show Leave Current Conversations Dialog")
return .leaveConversation(
conf: theme.alertConfiguration.leaveCurrentConversation,
accessibilityIdentifier: "alert_confirmation_leaveCurrentConversation",
confirmed: confirmed
confirmed: confirmed,
declined: declined
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -759,4 +759,223 @@ final class SecureConversationsTranscriptModelTests: XCTestCase {
XCTFail("Unexpected action: \(receivedAction)")
}
}

func testLoadHistoryAlsoInvokesSecureMarkMessagesAsReadIfShouldNotShowLeaveSecureConversationDialog() {
var modelEnv = TranscriptModel.Environment.failing
let fileUploadListModel = FileUploadListViewModel.mock()
fileUploadListModel.environment.uploader.limitReached.value = false
modelEnv.gcd.mainQueue.asyncAfterDeadline = { _, function in function() }
modelEnv.fileManager = .mock
modelEnv.createFileUploadListModel = { _ in fileUploadListModel }
modelEnv.listQueues = { _ in }
modelEnv.loadChatMessagesFromHistory = { true }
modelEnv.fetchChatHistory = { $0(.success([.mock(), .mock(), .mock()])) }
modelEnv.getSecureUnreadMessageCount = { $0(.success(5)) }
modelEnv.fetchSiteConfigurations = { _ in }
modelEnv.startSocketObservation = {}
modelEnv.maximumUploads = { 2 }
modelEnv.createEntryWidget = { _ in .mock() }
let scheduler = CoreSdkClient.ReactiveSwift.TestScheduler()
modelEnv.messagesWithUnreadCountLoaderScheduler = scheduler
enum Call: Equatable { case secureMarkMessagesAsRead }
var calls: [Call] = []
modelEnv.secureMarkMessagesAsRead = { completion in
calls.append(.secureMarkMessagesAsRead)
completion(.success(()))
return .mock
}
modelEnv.shouldShowLeaveSecureConversationDialog = false

let availabilityEnv = SecureConversations.Availability.Environment(
listQueues: modelEnv.listQueues,
isAuthenticated: { true },
log: .failing,
queuesMonitor: .mock(listQueues: modelEnv.listQueues)
)

let viewModel = TranscriptModel(
isCustomCardSupported: false,
environment: modelEnv,
availability: .init(
environment: availabilityEnv
),
deliveredStatusText: "",
failedToDeliverStatusText: "",
interactor: .failing
)

viewModel.start()
scheduler.run()

XCTAssertEqual(calls, [.secureMarkMessagesAsRead])
}

func testLoadHistoryNotInvokesSecureMarkMessagesAsReadIfShouldShowLeaveSecureConversationDialog() {
var modelEnv = TranscriptModel.Environment.failing
let fileUploadListModel = FileUploadListViewModel.mock()
fileUploadListModel.environment.uploader.limitReached.value = false
modelEnv.gcd.mainQueue.asyncAfterDeadline = { _, function in function() }
modelEnv.fileManager = .mock
modelEnv.createFileUploadListModel = { _ in fileUploadListModel }
modelEnv.listQueues = { _ in }
modelEnv.loadChatMessagesFromHistory = { true }
modelEnv.fetchChatHistory = { $0(.success([.mock(), .mock(), .mock()])) }
modelEnv.getSecureUnreadMessageCount = { $0(.success(5)) }
modelEnv.fetchSiteConfigurations = { _ in }
modelEnv.startSocketObservation = {}
modelEnv.maximumUploads = { 2 }
modelEnv.createEntryWidget = { _ in .mock() }
let scheduler = CoreSdkClient.ReactiveSwift.TestScheduler()
modelEnv.messagesWithUnreadCountLoaderScheduler = scheduler
enum Call: Equatable { case secureMarkMessagesAsRead }
var calls: [Call] = []
modelEnv.secureMarkMessagesAsRead = { completion in
calls.append(.secureMarkMessagesAsRead)
completion(.success(()))
return .mock
}
modelEnv.shouldShowLeaveSecureConversationDialog = true

let availabilityEnv = SecureConversations.Availability.Environment(
listQueues: modelEnv.listQueues,
isAuthenticated: { true },
log: .failing,
queuesMonitor: .mock(listQueues: modelEnv.listQueues)
)

let viewModel = TranscriptModel(
isCustomCardSupported: false,
environment: modelEnv,
availability: .init(
environment: availabilityEnv
),
deliveredStatusText: "",
failedToDeliverStatusText: "",
interactor: .failing
)

viewModel.start()
scheduler.run()

XCTAssertTrue(calls.isEmpty)
}

func testLeaveCurrentConversationAlertDeclineMarkMessagesAsRead() {
var modelEnv = TranscriptModel.Environment.failing
let fileUploadListModel = FileUploadListViewModel.mock()
fileUploadListModel.environment.uploader.limitReached.value = false
modelEnv.gcd.mainQueue.asyncAfterDeadline = { _, function in function() }
modelEnv.fileManager = .mock
modelEnv.createFileUploadListModel = { _ in fileUploadListModel }
modelEnv.listQueues = { _ in }
modelEnv.loadChatMessagesFromHistory = { true }
modelEnv.fetchChatHistory = { $0(.success([.mock(), .mock(), .mock()])) }
modelEnv.getSecureUnreadMessageCount = { $0(.success(5)) }
modelEnv.fetchSiteConfigurations = { _ in }
modelEnv.startSocketObservation = {}
modelEnv.maximumUploads = { 2 }
modelEnv.createEntryWidget = { _ in .mock() }
let scheduler = CoreSdkClient.ReactiveSwift.TestScheduler()
modelEnv.messagesWithUnreadCountLoaderScheduler = scheduler
enum Call: Equatable { case secureMarkMessagesAsRead }
var calls: [Call] = []
modelEnv.secureMarkMessagesAsRead = { completion in
calls.append(.secureMarkMessagesAsRead)
completion(.success(()))
return .mock
}
modelEnv.shouldShowLeaveSecureConversationDialog = true

let availabilityEnv = SecureConversations.Availability.Environment(
listQueues: modelEnv.listQueues,
isAuthenticated: { true },
log: .failing,
queuesMonitor: .mock(listQueues: modelEnv.listQueues)
)

let viewModel = TranscriptModel(
isCustomCardSupported: false,
environment: modelEnv,
availability: .init(
environment: availabilityEnv
),
deliveredStatusText: "",
failedToDeliverStatusText: "",
interactor: .failing
)

viewModel.engagementAction = { action in
if case .showAlert(let type) = action, case let .leaveCurrentConversation(_, declined) = type {
declined?()
}
}

viewModel.start()
scheduler.run()

XCTAssertEqual(calls, [.secureMarkMessagesAsRead])
}

func testReceiveMessageMarksMessagesAsRead() {
enum Call: Equatable { case secureMarkMessagesAsRead }
var calls: [Call] = []
var modelEnv = TranscriptModel.Environment.failing
let interactor: Interactor = .mock()
let fileUploadListModel = FileUploadListViewModel.mock()
fileUploadListModel.environment.uploader.limitReached.value = false
modelEnv.fileManager = .mock
modelEnv.createFileUploadListModel = { _ in fileUploadListModel }
modelEnv.listQueues = { _ in }
modelEnv.fetchSiteConfigurations = { _ in }
modelEnv.getSecureUnreadMessageCount = { $0(.success(5)) }
modelEnv.maximumUploads = { 2 }
modelEnv.startSocketObservation = {}
modelEnv.gcd.mainQueue.asyncAfterDeadline = { _, callback in callback() }
modelEnv.loadChatMessagesFromHistory = { true }
modelEnv.createEntryWidget = { _ in .mock() }
modelEnv.secureMarkMessagesAsRead = { completion in
calls.append(.secureMarkMessagesAsRead)
completion(.success(()))
return .mock
}
modelEnv.fetchChatHistory = { completion in
completion(.success([.mock()]))
}
modelEnv.shouldShowLeaveSecureConversationDialog = true
let scheduler = CoreSdkClient.ReactiveSwift.TestScheduler()
modelEnv.messagesWithUnreadCountLoaderScheduler = scheduler
modelEnv.interactor = interactor

let availabilityEnv = SecureConversations.Availability.Environment(
listQueues: modelEnv.listQueues,
isAuthenticated: { true },
log: .failing,
queuesMonitor: .mock(listQueues: modelEnv.listQueues)
)

let viewModel = TranscriptModel(
isCustomCardSupported: false,
environment: modelEnv,
availability: .init(
environment: availabilityEnv
),
deliveredStatusText: "",
failedToDeliverStatusText: "",
interactor: interactor
)

viewModel.start()
scheduler.run()

let uuid = UUID.mock.uuidString
let message = CoreSdkClient.Message(
id: uuid,
content: "Test",
sender: .init(type: .system),
metadata: nil
)
interactor.receive(message: message)

XCTAssertEqual(calls, [.secureMarkMessagesAsRead])
}
}
Loading

0 comments on commit 09d8132

Please sign in to comment.