Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve mark as read SC messages #1140

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
ykyivskyi-gl marked this conversation as resolved.
Show resolved Hide resolved
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])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ final class AlertViewControllerDynamicTypeFontTests: SnapshotTestCase {
let alert = alert(ofKind: .leaveConversation(
conf: .leaveConversationMock(),
accessibilityIdentifier: "mocked-accessibility-identifier",
confirmed: {}
confirmed: {},
declined: {}
))

alert.assertSnapshot(as: .extra3LargeFont, in: .portrait)
Expand Down
3 changes: 2 additions & 1 deletion SnapshotTests/AlertViewControllerLayoutTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ final class AlertViewControllerLayoutTests: SnapshotTestCase {
let alert = alert(ofKind: .leaveConversation(
conf: .leaveConversationMock(),
accessibilityIdentifier: "mocked-accessibility-identifier",
confirmed: {}
confirmed: {},
declined: {}
))

alert.assertSnapshot(as: .image, in: .portrait)
Expand Down
Loading
Loading