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

[trello.com/c/TOuHqbBz] Fix: Read/unread messages #588

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
25 changes: 0 additions & 25 deletions Adamant/Models/CoreData/Chatroom+CoreDataClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,6 @@ import CoreData
public class Chatroom: NSManagedObject, @unchecked Sendable {
static let entityName = "Chatroom"

var hasUnread: Bool {
return hasUnreadMessages || (lastTransaction?.isUnread ?? false)
}

func markAsReaded() {
hasUnreadMessages = false

if let trs = transactions as? Set<ChatTransaction> {
trs.filter { $0.isUnread }.forEach { $0.isUnread = false }
}
lastTransaction?.isUnread = false
}

func markAsUnread() {
hasUnreadMessages = true
lastTransaction?.isUnread = true
}

func getFirstUnread() -> ChatTransaction? {
if let trs = transactions as? Set<ChatTransaction> {
return trs.filter { $0.isUnread }.map { $0 }.first
}
return nil
}

@MainActor func getName(addressBookService: AddressBookService) -> String? {
guard let partner = partner else { return nil }
let result: String?
Expand Down
20 changes: 20 additions & 0 deletions Adamant/Modules/Chat/View/ChatViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ final class ChatViewController: MessagesViewController {
super.scrollViewDidScroll(scrollView)
updateIsScrollPositionNearlyTheBottom()
updateScrollDownButtonVisibility()
identifyBottomVisibleMessage()

if scrollView.isTracking || scrollView.isDragging || scrollView.isDecelerating {
updateDateHeaderIfNeeded()
Expand Down Expand Up @@ -712,6 +713,24 @@ private extension ChatViewController {
scrollDownButton.isHidden = isScrollPositionNearlyTheBottom
}

func identifyBottomVisibleMessage() {
let targetY: CGFloat = view.frame.height - view.safeAreaInsets.bottom - targetYOffsetBottom
let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems

for indexPath in visibleIndexPaths {
guard let cell = messagesCollectionView.cellForItem(at: indexPath)
else { continue }

let cellRect = messagesCollectionView.convert(cell.frame, to: self.view)

guard cellRect.maxY >= targetY && cellRect.minY <= targetY
else { continue }

viewModel.checkBottomMessage(indexPath: indexPath)
break
}
}

func updateDateHeaderIfNeeded() {
guard viewAppeared else { return }

Expand Down Expand Up @@ -1093,3 +1112,4 @@ private let scrollDownButtonInset: CGFloat = 20
private let messagePadding: CGFloat = 12
private let filesToolbarViewHeight: CGFloat = 140
private let targetYOffset: CGFloat = 20
private let targetYOffsetBottom: CGFloat = 100
43 changes: 41 additions & 2 deletions Adamant/Modules/Chat/ViewModel/ChatViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,19 @@ final class ChatViewModel: NSObject {
Task {
guard
let chatroom = chatroom,
chatroom.hasUnreadMessages == true || chatroom.lastTransaction?.isUnread == true
let address = chatroom.partner?.address,
let lastTransaction = chatroom.lastTransaction,
await chatsProvider.isUnreadChat(chatroom: chatroom)
else { return }

await chatsProvider.markChatAsRead(chatroom: chatroom)
guard let transactions = chatroom.transactions as? Set<ChatTransaction>
else { return }

await chatsProvider.setLastReadMessage(
height: lastTransaction.height,
transactions: transactions,
chatroom: address
)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if user entered the chat from push? And it scrolls to a certain message

}

Expand Down Expand Up @@ -1040,6 +1049,36 @@ extension ChatViewModel {
hideHeaderTimer = nil
}

func checkBottomMessage(indexPath: IndexPath) {
guard let message = messages[safe: indexPath.section],
let transaction = chatTransactions.first(
where: { $0.chatMessageId == message.id }
)
else {
return
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting (like below)


Task {
guard
let address = chatroom?.partner?.address,
let lastReadMessage = await chatsProvider.getLastReadMessage(chatroom: address),
lastReadMessage.height <= transaction.height || transaction.height == .zero
else {
return
}

await chatsProvider.appendLastReadMessage(
readMessage: .init(
height: transaction.height > .zero
? transaction.height
: lastReadMessage.height,
transactionsId: [transaction.transactionId]
),
chatroom: address
)
}
}

func startHideDateTimer() {
hideHeaderTimer?.cancel()
hideHeaderTimer = Timer
Expand Down
51 changes: 43 additions & 8 deletions Adamant/Modules/ChatsList/ChatListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ final class ChatListViewController: KeyboardObservingViewController {
}
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

tableView.reloadData()
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

Expand Down Expand Up @@ -691,7 +697,8 @@ extension ChatListViewController {
cell.hasUnreadMessages = chatroom.hasUnreadMessages

if let lastTransaction = chatroom.lastTransaction {
cell.hasUnreadMessages = lastTransaction.isUnread
let isUnread = chatsProvider.isUnreadChat(chatroom: chatroom)
cell.hasUnreadMessages = isUnread
cell.lastMessageLabel.attributedText = shortDescription(for: lastTransaction)
} else {
cell.lastMessageLabel.text = nil
Expand Down Expand Up @@ -1164,14 +1171,42 @@ extension ChatListViewController {
let markAsRead = UIContextualAction(
style: .normal,
title: "👀"
) { (_, _, completionHandler) in
if chatroom.hasUnread {
chatroom.markAsReaded()
} else {
chatroom.markAsUnread()
) { [weak self] (_, _, completionHandler) in
guard let self = self else { return }

Task { @MainActor in
defer {
completionHandler(true)
self.tableView.reloadData()
}

guard
let address = chatroom.partner?.address,
let lastTransaction = chatroom.lastTransaction
else {
return
}

let isUnread = await self.chatsProvider.isUnreadChat(chatroom: chatroom)

guard let transactions = chatroom.transactions as? Set<ChatTransaction>
else { return }

if isUnread {
await self.chatsProvider.setLastReadMessage(
height: lastTransaction.height,
transactions: transactions,
chatroom: address
)
return
}

await self.chatsProvider.setLastReadMessage(
height: lastTransaction.height - 1,
transactions: [],
chatroom: address
)
}
try? chatroom.managedObjectContext?.save()
completionHandler(true)
}

markAsRead.backgroundColor = UIColor.adamant.contextMenuDefaultBackgroundColor
Expand Down
21 changes: 20 additions & 1 deletion Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,26 @@ protocol ChatsProvider: DataProvider, Actor {
/// Unread messages controller. Sections by chatroom.
func getUnreadMessagesController() -> NSFetchedResultsController<ChatTransaction>

func setLastReadMessage(
readMessage: ReadMessage,
chatroom: String
)

func getLastReadMessage(chatroom: String) -> ReadMessage?

func isUnreadChat(chatroom: Chatroom) -> Bool

func appendLastReadMessage(
readMessage: ReadMessage,
chatroom: String
)

func setLastReadMessage(
height: Int64,
transactions: Set<ChatTransaction>,
chatroom: String
)

// ForceUpdate chats
func update(notifyState: Bool) async -> ChatsProviderResult?

Expand Down Expand Up @@ -248,7 +268,6 @@ protocol ChatsProvider: DataProvider, Actor {
func validateMessage(_ message: AdamantMessage) -> ValidateMessageResult
func blockChat(with address: String)
func removeMessage(with id: String)
func markChatAsRead(chatroom: Chatroom)

@MainActor func removeChatPositon(for address: String)
@MainActor func setChatPositon(for address: String, position: Double?)
Expand Down
109 changes: 84 additions & 25 deletions Adamant/Services/DataProviders/AdamantChatsProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import MarkdownKit
import Combine
import CommonKit

struct ReadMessage: Codable {
let height: Int64
var transactionsId: Set<String>
}

actor AdamantChatsProvider: ChatsProvider {

// MARK: Dependencies
Expand Down Expand Up @@ -371,6 +376,19 @@ extension AdamantChatsProvider {
privateKey: privateKey
)

if !accountService.hasStayInAccount {
chatrooms.chats?.forEach({ chatroom in
guard let lastTransaction = chatroom.lastTransaction else { return }
setLastReadMessage(
readMessage: .init(
height: lastTransaction.height,
transactionsId: [String(lastTransaction.id)]
),
chatroom: lastTransaction.recipientId
)
})
}

if !isInitiallySynced {
isInitiallySynced = true
preLoadChats(array, address: address)
Expand Down Expand Up @@ -473,7 +491,7 @@ extension AdamantChatsProvider {
}

let offset = (offset ?? 0) + messageCount

let loadedCount = chatLoadedMessages[addressRecipient] ?? 0
chatLoadedMessages[addressRecipient] = loadedCount + messageCount

Expand Down Expand Up @@ -532,7 +550,7 @@ extension AdamantChatsProvider {
let privateKey = accountService.keypair?.privateKey else {
return
}

// MARK: 3. Get transactions

socketService.connect(address: address) { [weak self] result in
Expand All @@ -555,6 +573,70 @@ extension AdamantChatsProvider {
self.socketService.disconnect()
}

func appendLastReadMessage(
readMessage: ReadMessage,
chatroom: String
) {
var lastReadMessage = getLastReadMessage(chatroom: chatroom) ?? readMessage

if lastReadMessage.height == readMessage.height {
lastReadMessage.transactionsId.formUnion(readMessage.transactionsId)
} else {
lastReadMessage = readMessage
}

setLastReadMessage(readMessage: lastReadMessage, chatroom: chatroom)
}

func setLastReadMessage(
height: Int64,
transactions: Set<ChatTransaction>,
chatroom: String
) {
let unreadTransactions = transactions.filter {
$0.height == height || $0.height == .zero
}.compactMap { $0.transactionId }

setLastReadMessage(
readMessage: .init(
height: height,
transactionsId: Set(unreadTransactions)
),
chatroom: chatroom
)
}

func setLastReadMessage(
readMessage: ReadMessage,
chatroom: String
) {
securedStore.set(readMessage, for: StoreKey.chat.lastReadHeight(for: chatroom))
}

func getLastReadMessage(chatroom: String) -> ReadMessage? {
guard let result: ReadMessage = securedStore.get(StoreKey.chat.lastReadHeight(for: chatroom))
else {
return nil
}
return result
}

func isUnreadChat(chatroom: Chatroom) -> Bool {
guard
let address = chatroom.partner?.address,
let lastTransaction = chatroom.lastTransaction,
let lastReadMessage = getLastReadMessage(chatroom: address) else {
return true
}

if lastReadMessage.height == lastTransaction.height
|| lastTransaction.height == .zero {
return !lastReadMessage.transactionsId.contains(lastTransaction.transactionId)
}

return lastReadMessage.height < lastTransaction.height
}

func update(notifyState: Bool) async -> ChatsProviderResult? {
// MARK: 1. Check state
guard isInitiallySynced,
Expand Down Expand Up @@ -1805,22 +1887,6 @@ extension AdamantChatsProvider {
}
}

// MARK: 4. Unread messagess
if let readedLastHeight = readedLastHeight {
var unreadTransactions = newMessageTransactions.filter { $0.height > readedLastHeight }
if unreadTransactions.count == 0 {
unreadTransactions = newMessageTransactions.filter { $0.height == 0 }
}
let chatrooms = Dictionary(grouping: unreadTransactions, by: ({ (t: ChatTransaction) -> Chatroom in t.chatroom! }))
for (chatroom, trs) in chatrooms {
if let address = chatroom.partner?.address {
chatroom.isHidden = self.blockList.contains(address)
}
chatroom.hasUnreadMessages = true
trs.forEach { $0.isUnread = true }
}
}

// MARK: 5. Dump new transactions
if privateContext.hasChanges {
do {
Expand Down Expand Up @@ -1954,13 +2020,6 @@ extension AdamantChatsProvider {
}
}

func markChatAsRead(chatroom: Chatroom) {
chatroom.managedObjectContext?.perform {
chatroom.markAsReaded()
try? chatroom.managedObjectContext?.save()
}
}

private func onConnectionToTheInternetRestored() {
onConnectionToTheInternetRestoredTasks.forEach { $0() }
onConnectionToTheInternetRestoredTasks = []
Expand Down
Loading