Compare commits

...

21 Commits

Author SHA1 Message Date
Diogo 009968b7d4 open on unread 2024-11-05 15:50:55 +00:00
Diogo e1f24e7588 initial api integration and types 2024-11-05 10:28:18 +00:00
Diogo d8d990a437 Merge branch 'dc/core-pagination' into dc/core-initial-landing-for-chat 2024-11-04 22:44:35 +00:00
Diogo 83875a35f8 final 2024-11-04 22:25:38 +00:00
Diogo d3d3b9aba9 gap size wip (needs testing) 2024-11-04 15:13:04 +00:00
Diogo 40db6686c1 throw when search is sent for initial 2024-11-01 22:11:10 +00:00
Diogo 21f9c7fd50 Revert "propagate search"
This reverts commit 01611fd719.
2024-11-01 21:57:26 +00:00
Diogo 01611fd719 propagate search 2024-11-01 16:09:12 +00:00
Diogo 0ce7d16a59 refactor: use foldr everywhere 2024-11-01 16:00:38 +00:00
Diogo bf20b73893 refactor to make landingSection reusable 2024-11-01 15:56:15 +00:00
spaced4ndy fd5e5d295f foldr 2024-11-01 17:30:58 +04:00
Diogo 065cc170da Merge branch 'dc/core-pagination' into dc/core-initial-landing-for-chat 2024-10-31 22:08:30 +00:00
Diogo 37a5c353ea descriptive view message 2024-10-31 16:09:56 +00:00
Diogo 31eca1687c total accuracy on landing section 2024-10-31 16:03:45 +00:00
Diogo 4bbee43602 fix ChatLandingSection serialized type 2024-10-31 10:50:13 +00:00
Diogo 22be251862 refactor names 2024-10-30 16:08:40 +00:00
Diogo 2b7ad80dfd fixed sqls 2024-10-27 00:25:05 +01:00
Diogo d8f5b6e813 controller parse 2024-10-27 00:01:52 +01:00
Diogo b6359559c1 support for initial 2024-10-26 23:44:43 +01:00
Diogo 5826d14866 Merge branch 'dc/core-pagination' into dc/core-initial-landing-for-chat 2024-10-26 22:22:04 +01:00
Diogo 185e307e70 initial work on initial param for loading chat 2024-10-25 17:12:23 +01:00
12 changed files with 430 additions and 100 deletions
+22 -6
View File
@@ -320,15 +320,24 @@ private func apiChatsResponse(_ r: ChatResponse) throws -> [ChatData] {
let loadItemsPerPage = 50
func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> Chat {
let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: .last(count: loadItemsPerPage), search: search))
if case let .apiChat(_, chat) = r { return Chat.init(chat) }
func apiGetChat(type: ChatType, id: Int64, search: String = "") async throws -> (Chat, Int?) {
let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: .initial(count: loadItemsPerPage), search: search))
if case let .apiChat(_, chat, gap) = r { return (Chat.init(chat), gap) }
throw r
}
func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> [ChatItem] {
func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> ([ChatItem], Int?) {
let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: pagination, search: search))
if case let .apiChat(_, chat) = r { return chat.chatItems }
if case let .apiChat(_, chat, gap) = r { return (chat.chatItems, gap) }
if case .chatCmdError(_, _) = r {
if case .chatError(_, let chatError) = r {
if case .errorStore(let storeError) = chatError {
if case .chatItemNotFound(_) = storeError {
itemNotFoundAlert()
}
}
}
}
throw r
}
@@ -341,7 +350,7 @@ func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async {
if clearItems {
await MainActor.run { im.reversedChatItems = [] }
}
let chat = try await apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search)
let (chat, _) = try await apiGetChat(type: cInfo.chatType, id: cInfo.apiId, search: search)
await MainActor.run {
im.reversedChatItems = chat.chatItems.reversed()
m.updateChatInfo(chat.chatInfo)
@@ -418,6 +427,13 @@ func apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage]
return nil
}
func itemNotFoundAlert() {
AlertManager.shared.showAlertMsg(
title: "Message no longer available",
message: "The quoted message you are trying to view has been deleted."
)
}
private func sendMessageErrorAlert(_ r: ChatResponse) {
logger.error("send message error: \(String(describing: r))")
AlertManager.shared.showAlertMsg(
@@ -48,10 +48,18 @@ struct FramedItemView: View {
if let qi = chatItem.quotedItem {
ciQuoteView(qi)
.onTapGesture {
if let ci = ItemsModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) {
withAnimation {
scrollModel.scrollToItem(id: ci.id)
if let itemId = qi.itemId {
if !scrollToItem(itemId) {
Task {
if await loadItemsAround(chat.chatInfo, itemId) != nil {
await MainActor.run {
let _ = scrollToItem(itemId)
}
}
}
}
} else {
itemNotFoundAlert()
}
}
} else if let itemForwarded = chatItem.meta.itemForwarded {
@@ -323,6 +331,42 @@ struct FramedItemView: View {
return videoWidth
}
}
// Scroll to an item, if success returns true otherwise false
private func scrollToItem(_ itemId: Int64) -> Bool {
if let ci = ItemsModel.shared.reversedChatItems.first(where: { $0.id == itemId }) {
withAnimation {
scrollModel.scrollToItem(id: ci.id)
}
return true
} else {
return false
}
}
private func loadItemsAround(_ cInfo: ChatInfo, _ chatItemId: Int64) async -> [ChatItem]? {
do {
var reversedPage = Array<ChatItem>()
let pagination: ChatPagination = .around(chatItemId: chatItemId, count: loadItemsPerPage * 2)
let (chatItems, _) = try await apiGetChatItems(
type: cInfo.chatType,
id: cInfo.apiId,
pagination: pagination,
search: ""
)
reversedPage.append(contentsOf: chatItems.reversed())
await MainActor.run {
ItemsModel.shared.reversedChatItems.append(contentsOf: reversedPage)
}
return reversedPage
} catch let error {
logger.error("apiGetChat error: \(responseError(error))")
return nil
}
}
}
@ViewBuilder func toggleSecrets<V: View>(_ ft: [FormattedText]?, _ showSecrets: Binding<Bool>, _ v: V) -> some View {
+19 -1
View File
@@ -363,6 +363,11 @@ struct ChatView: View {
}
}
ChatView.FloatingButtonModel.shared.totalUnread = chat.chatStats.unreadCount
Task {
if let firstunreadItem = self.getFirstUnreadItem() {
scrollModel.scrollToUnread(id: firstunreadItem.id)
}
}
}
private func searchToolbar() -> some View {
@@ -471,6 +476,19 @@ struct ChatView: View {
EmptyView()
}
}
private func getFirstUnreadItem() -> ChatItem? {
logger.error("[scrolling] \(im.reversedChatItems.count)")
for i in stride(from: im.reversedChatItems.count - 1, through: 0, by: -1) {
let item = im.reversedChatItems[i]
if item.isRcvNew {
logger.error("[scrolling] First unread item: \(item.text)")
return item
}
}
return nil
}
class FloatingButtonModel: ObservableObject {
static let shared = FloatingButtonModel()
@@ -854,7 +872,7 @@ struct ChatView: View {
} else {
.last(count: loadItemsPerPage)
}
let chatItems = try await apiGetChatItems(
let (chatItems, _) = try await apiGetChatItems(
type: cInfo.chatType,
id: cInfo.apiId,
pagination: pagination,
@@ -340,7 +340,7 @@ struct GroupMemberInfoView: View {
InfoViewButton(image: "message.fill", title: "message", width: width) {
Task {
do {
let chat = try await apiGetChat(type: .direct, id: contactId)
let (chat, _) = try await apiGetChat(type: .direct, id: contactId)
chatModel.addChat(chat)
ItemsModel.shared.loadOpenChat(chat.id) {
dismissAllSheets(animated: true)
+28 -2
View File
@@ -36,8 +36,11 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
controller.scroll(to: items.firstIndex(where: { $0.id == id }), position: .bottom)
case .bottom:
controller.scroll(to: 0, position: .top)
case let .unread(id):
controller.scrollToUnread(to: items.firstIndex(where: { $0.id == id }))
}
} else {
logger.error("[scrolling] not scrolling")
controller.update(items: items)
}
}
@@ -78,7 +81,8 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
tableView: tableView
) { (tableView, indexPath, item) -> UITableViewCell? in
if indexPath.item > self.itemCount - 8 {
if indexPath.item > self.itemCount - 8, self.representer.scrollState == .atDestination {
logger.error("[scrolling] requesting page")
self.representer.loadPage()
}
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
@@ -161,6 +165,18 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
)
Task { representer.scrollState = .atDestination }
}
func scrollToUnread(to index: Int?) {
if let index = index {
if isVisible(indexPath: IndexPath(row: index, section: 0)) {
self.scroll(to: 0, position: .top)
} else {
self.scroll(to: index, position: .bottom)
}
} else {
self.scroll(to: index, position: .bottom)
}
}
/// Scrolls to Item at index path
/// - Parameter indexPath: Item to scroll to - will scroll to beginning of the list, if `nil`
@@ -169,7 +185,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
if #available(iOS 16.0, *) {
animated = true
}
if let index, tableView.numberOfRows(inSection: 0) != 0 {
if let index, tableView.numberOfRows(inSection: 0) != 0, !isVisible(indexPath: IndexPath(row: index, section: 0)) {
tableView.scrollToRow(
at: IndexPath(row: index, section: 0),
at: position,
@@ -294,6 +310,7 @@ class ReverseListScrollModel: ObservableObject {
case nextPage
case item(ChatItem.ID)
case bottom
case unread(ChatItem.ID)
}
case scrollingTo(Destination)
@@ -303,16 +320,25 @@ class ReverseListScrollModel: ObservableObject {
@Published var state: State = .atDestination
func scrollToNextPage() {
logger.error("[scrolling] to next page")
state = .scrollingTo(.nextPage)
}
func scrollToBottom() {
logger.error("[scrolling] to bottom")
state = .scrollingTo(.bottom)
}
func scrollToItem(id: ChatItem.ID) {
logger.error("[scrolling] to item \(id)")
state = .scrollingTo(.item(id))
}
func scrollToUnread(id: ChatItem.ID) {
logger.error("[scrolling] to unread")
state = .scrollingTo(.unread(id))
}
}
fileprivate let cellReuseId = "hostingCell"
+2 -1
View File
@@ -214,7 +214,8 @@ public func chatResponse(_ s: String) -> ChatResponse {
let user: UserRef = try? decodeObject(jApiChat["user"] as Any),
let jChat = jApiChat["chat"] as? NSDictionary,
let chat = try? parseChatData(jChat) {
return .apiChat(user: user, chat: chat)
let gap = jApiChat["gap"] as? Int
return .apiChat(user: user, chat: chat, gap: gap)
}
} else if type == "chatCmdError" {
if let jError = jResp["chatCmdError"] as? NSDictionary {
+7 -3
View File
@@ -546,7 +546,7 @@ public enum ChatResponse: Decodable, Error {
case chatStopped
case chatSuspended
case apiChats(user: UserRef, chats: [ChatData])
case apiChat(user: UserRef, chat: ChatData)
case apiChat(user: UserRef, chat: ChatData, gap: Int?)
case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo)
case userProtoServers(user: UserRef, servers: UserProtoServers)
case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?)
@@ -888,7 +888,7 @@ public enum ChatResponse: Decodable, Error {
case .chatStopped: return noDetails
case .chatSuspended: return noDetails
case let .apiChats(u, chats): return withUser(u, String(describing: chats))
case let .apiChat(u, chat): return withUser(u, String(describing: chat))
case let .apiChat(u, chat, gap): return withUser(u, "gap: \(String(describing: gap)) \(String(describing: chat))")
case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))")
case let .userProtoServers(u, servers): return withUser(u, "servers: \(String(describing: servers))")
case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))")
@@ -1133,12 +1133,16 @@ public enum ChatPagination {
case last(count: Int)
case after(chatItemId: Int64, count: Int)
case before(chatItemId: Int64, count: Int)
case around(chatItemId: Int64, count: Int)
case initial(count: Int)
var cmdString: String {
switch self {
case let .last(count): return "count=\(count)"
case let .after(chatItemId, count): return "after=\(chatItemId) count=\(count)"
case let .before(chatItemId, count): return "before=\(chatItemId) count=\(count)"
case let .around(chatItemId, count): return "around=\(chatItemId) count=\(count)"
case let .initial(count): return "initial=\(count)"
}
}
}
@@ -605,7 +605,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi
listGroups count pending =
readTVarIO (groupRegs st) >>= \groups -> do
grs <-
if pending
if pending
then filterM (fmap pendingApproval . readTVarIO . groupRegStatus) groups
else pure groups
sendReply $ show (length grs) <> " registered group(s)" <> (if length grs > count then ", showing the last " <> show count else "")
@@ -643,7 +643,7 @@ getContact cc ctId = resp <$> sendChatCmd cc (APIGetChat (ChatRef CTDirect ctId)
where
resp :: ChatResponse -> Maybe Contact
resp = \case
CRApiChat _ (AChat SCTDirect Chat {chatInfo = DirectChat ct}) -> Just ct
CRApiChat _ (AChat SCTDirect Chat {chatInfo = DirectChat ct}) _ -> Just ct
_ -> Nothing
getGroup :: ChatController -> GroupId -> IO (Maybe GroupInfo)
+7 -6
View File
@@ -735,14 +735,14 @@ processChatCommand' vr = \case
APIGetChat (ChatRef cType cId) pagination search -> withUser $ \user -> case cType of
-- TODO optimize queries calculating ChatStats, currently they're disabled
CTDirect -> do
directChat <- withFastStore (\db -> getDirectChat db vr user cId pagination search)
pure $ CRApiChat user (AChat SCTDirect directChat)
(directChat, gap) <- withFastStore (\db -> getDirectChat db vr user cId pagination search)
pure $ CRApiChat user (AChat SCTDirect directChat) gap
CTGroup -> do
groupChat <- withFastStore (\db -> getGroupChat db vr user cId pagination search)
pure $ CRApiChat user (AChat SCTGroup groupChat)
(groupChat, gap) <- withFastStore (\db -> getGroupChat db vr user cId pagination search)
pure $ CRApiChat user (AChat SCTGroup groupChat) gap
CTLocal -> do
localChat <- withFastStore (\db -> getLocalChat db user cId pagination search)
pure $ CRApiChat user (AChat SCTLocal localChat)
(localChat, gap) <- withFastStore (\db -> getLocalChat db user cId pagination search)
pure $ CRApiChat user (AChat SCTLocal localChat) gap
CTContactRequest -> pure $ chatCmdError (Just user) "not implemented"
CTContactConnection -> pure $ chatCmdError (Just user) "not supported"
APIGetChatItems pagination search -> withUser $ \user -> do
@@ -8302,6 +8302,7 @@ chatCommandP =
<|> (CPAfter <$ "after=" <*> A.decimal <* A.space <* "count=" <*> A.decimal)
<|> (CPBefore <$ "before=" <*> A.decimal <* A.space <* "count=" <*> A.decimal)
<|> (CPAround <$ "around=" <*> A.decimal <* A.space <* "count=" <*> A.decimal)
<|> (CPInitial <$ "initial=" <*> A.decimal)
paginationByTimeP =
(PTLast <$ "count=" <*> A.decimal)
<|> (PTAfter <$ "after=" <*> strP <* A.space <* "count=" <*> A.decimal)
+7 -1
View File
@@ -572,7 +572,7 @@ data ChatResponse
| CRChatSuspended
| CRApiChats {user :: User, chats :: [AChat]}
| CRChats {chats :: [AChat]}
| CRApiChat {user :: User, chat :: AChat}
| CRApiChat {user :: User, chat :: AChat, gap :: Maybe ChatGap}
| CRChatItems {user :: User, chatName_ :: Maybe ChatName, chatItems :: [AChatItem]}
| CRChatItemInfo {user :: User, chatItem :: AChatItem, chatItemInfo :: ChatItemInfo}
| CRChatItemId User (Maybe ChatItemId)
@@ -840,6 +840,10 @@ data ChatPagination
| CPAfter ChatItemId Int
| CPBefore ChatItemId Int
| CPAround ChatItemId Int
| CPInitial Int
deriving (Show)
data ChatGap = ChatGap {index :: Maybe Int, size :: Int}
deriving (Show)
data PaginationByTime
@@ -1592,6 +1596,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RCSR") ''RemoteCtrlStopReason)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RHSR") ''RemoteHostStopReason)
$(JQ.deriveJSON defaultJSON ''ChatGap)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CR") ''ChatResponse)
$(JQ.deriveFromJSON defaultJSON ''ArchiveConfig)
+281 -73
View File
@@ -138,7 +138,7 @@ import Data.Time (addUTCTime)
import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Database.SQLite.Simple (NamedParam (..), Only (..), Query, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Controller (ChatListQuery (..), ChatPagination (..), PaginationByTime (..))
import Simplex.Chat.Controller (ChatGap (ChatGap, index, size), ChatListQuery (..), ChatPagination (..), PaginationByTime (..))
import Simplex.Chat.Markdown
import Simplex.Chat.Messages
import Simplex.Chat.Messages.CIContent
@@ -947,38 +947,62 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of
aChat = AChat SCTContactConnection $ Chat (ContactConnection conn) [] stats
in ACPD SCTContactConnection $ ContactConnectionPD updatedAt aChat
getDirectChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect)
getDirectChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect, Maybe ChatGap)
getDirectChat db vr user contactId pagination search_ = do
let search = fromMaybe "" search_
ct <- getContact db vr user contactId
case pagination of
CPLast count -> liftIO $ getDirectChatLast_ db user ct count search
CPLast count -> liftIO $ (,Nothing) <$> getDirectChatLast_ db user ct count search
CPAfter afterId count -> getDirectChatAfter_ db user ct afterId count search
CPBefore beforeId count -> getDirectChatBefore_ db user ct beforeId count search
CPAround aroundId count -> getDirectChatAround_ db user ct aroundId count search
CPInitial count -> do
unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search"
getDirectChatInitial_ db user ct count
-- the last items in reverse order (the last item in the conversation is the first in the returned list)
getDirectChatLast_ :: DB.Connection -> User -> Contact -> Int -> String -> IO (Chat 'CTDirect)
getDirectChatLast_ db user@User {userId} ct@Contact {contactId} count search = do
getDirectChatLast_ db user ct count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
chatItemIds <- getDirectChatItemIdsLast_
chatItemIds <- getDirectChatItemIdsLast_ db user ct count search
currentTs <- getCurrentTime
chatItems <- mapM (safeGetDirectItem db user ct currentTs) chatItemIds
pure $ Chat (DirectChat ct) (reverse chatItems) stats
where
getDirectChatItemIdsLast_ :: IO [ChatItemId]
getDirectChatItemIdsLast_ =
map fromOnly
<$> DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY created_at DESC, chat_item_id DESC
LIMIT ?
|]
(userId, contactId, search, count)
getDirectChatItemIdsLast_ :: DB.Connection -> User -> Contact -> Int -> String -> IO [ChatItemId]
getDirectChatItemIdsLast_ db User {userId} Contact {contactId} count search =
map fromOnly
<$> DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY created_at DESC, chat_item_id DESC
LIMIT ?
|]
(userId, contactId, search, count)
getDirectChatItemGapToLatest_ :: DB.Connection -> User -> Contact -> CChatItem 'CTDirect -> String -> IO Int
getDirectChatItemGapToLatest_ db User {userId} Contact {contactId} chatItem search = do
count <-
maybeFirstRow fromOnly $
DB.queryNamed
db
[sql|
SELECT COUNT(1)
FROM chat_items
WHERE user_id = :userId AND contact_id = :contactId AND item_text LIKE '%' || :search || '%'
AND (created_at > :itemCreatedAt OR (created_at = :itemCreatedAt AND chat_item_id > :chatItemId))
ORDER BY created_at DESC, chat_item_id DESC
|]
[ ":userId" := userId,
":contactId" := contactId,
":search" := search,
":itemCreatedAt" := chatItemCreatedAt chatItem,
":chatItemId" := cchatItemId chatItem
]
pure $ maybe 0 (\c -> max 0 (c - 1)) count
safeGetDirectItem :: DB.Connection -> User -> Contact -> UTCTime -> ChatItemId -> IO (CChatItem 'CTDirect)
safeGetDirectItem db user ct currentTs itemId =
@@ -1022,14 +1046,16 @@ getDirectChatItemLast db user@User {userId} contactId = do
(userId, contactId)
getDirectChatItem db user contactId chatItemId
getDirectChatAfter_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect)
getDirectChatAfter_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect, Maybe ChatGap)
getDirectChatAfter_ db user ct@Contact {contactId} afterChatItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
afterChatItem <- getDirectChatItem db user contactId afterChatItemId
chatItemIds <- liftIO $ getDirectChatItemIdsAfter_ db user ct afterChatItemId count search (chatItemCreatedAt afterChatItem)
currentTs <- liftIO getCurrentTime
chatItems <- liftIO $ mapM (safeGetDirectItem db user ct currentTs) chatItemIds
pure $ Chat (DirectChat ct) chatItems stats
gapToLatest <- liftIO $ getDirectChatItemGapToLatest_ db user ct (last chatItems) search
let chatGap = if gapToLatest > 0 then Just $ ChatGap {size = gapToLatest, index = Nothing} else Nothing
pure (Chat (DirectChat ct) chatItems stats, chatGap)
getDirectChatItemIdsAfter_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> UTCTime -> IO [ChatItemId]
getDirectChatItemIdsAfter_ db User {userId} Contact {contactId} afterChatItemId count search afterChatItemCreatedAt =
@@ -1046,14 +1072,14 @@ getDirectChatItemIdsAfter_ db User {userId} Contact {contactId} afterChatItemId
|]
(userId, contactId, search, afterChatItemCreatedAt, afterChatItemCreatedAt, afterChatItemId, count)
getDirectChatBefore_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect)
getDirectChatBefore_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect, Maybe ChatGap)
getDirectChatBefore_ db user ct@Contact {contactId} beforeChatItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
beforeChatItem <- getDirectChatItem db user contactId beforeChatItemId
chatItemIds <- liftIO $ getDirectChatItemsIdsBefore_ db user ct beforeChatItemId count search (chatItemCreatedAt beforeChatItem)
currentTs <- liftIO getCurrentTime
chatItems <- liftIO $ mapM (safeGetDirectItem db user ct currentTs) chatItemIds
pure $ Chat (DirectChat ct) (reverse chatItems) stats
pure (Chat (DirectChat ct) (reverse chatItems) stats, Nothing)
getDirectChatItemsIdsBefore_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> UTCTime -> IO [ChatItemId]
getDirectChatItemsIdsBefore_ db User {userId} Contact {contactId} beforeChatItemId count search beforeChatItemCreatedAt =
@@ -1070,7 +1096,7 @@ getDirectChatItemsIdsBefore_ db User {userId} Contact {contactId} beforeChatItem
|]
(userId, contactId, search, beforeChatItemCreatedAt, beforeChatItemCreatedAt, beforeChatItemId, count)
getDirectChatAround_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect)
getDirectChatAround_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect, Maybe ChatGap)
getDirectChatAround_ db user ct@Contact {contactId} aroundItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
let (fetchCountBefore, fetchCountAfter) = divideFetchCountAround_ (count - 1)
@@ -1081,39 +1107,101 @@ getDirectChatAround_ db user ct@Contact {contactId} aroundItemId count search =
beforeChatItems <- liftIO $ reverse <$> mapM (safeGetDirectItem db user ct currentTs) beforeIds
afterChatItems <- liftIO $ mapM (safeGetDirectItem db user ct currentTs) afterIds
let chatItems = beforeChatItems <> [middleChatItem] <> afterChatItems
pure $ Chat (DirectChat ct) chatItems stats
gapToLatest <- liftIO $ getDirectChatItemGapToLatest_ db user ct (last chatItems) search
let chatGap = if gapToLatest > 0 then Just $ ChatGap {size = gapToLatest, index = Nothing} else Nothing
pure (Chat (DirectChat ct) chatItems stats, chatGap)
getGroupChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup)
getDirectChatInitial_ :: DB.Connection -> User -> Contact -> Int -> ExceptT StoreError IO (Chat 'CTDirect, Maybe ChatGap)
getDirectChatInitial_ db user@User {userId} ct@Contact {contactId} count = do
firstUnreadItemId_ <- liftIO getDirectChatMinUnreadItemId_
case firstUnreadItemId_ of
Just firstUnreadItemId -> do
(chat, gap) <- getDirectChatAround_ db user ct firstUnreadItemId count ""
case gap of
Just ChatGap {size} -> do
if size > snd (divideFetchCountAround_ count)
then getLatestItems_ chat size
else pure (chat, Nothing)
Nothing -> pure (chat, Nothing)
Nothing -> liftIO $ (,Nothing) <$> getDirectChatLast_ db user ct count ""
where
getLatestItems_ :: Chat 'CTDirect -> Int -> ExceptT StoreError IO (Chat 'CTDirect, Maybe ChatGap)
getLatestItems_ c@Chat {chatItems} gapToLatest = do
currentTs <- liftIO getCurrentTime
latestItemIds <- liftIO $ getDirectChatItemIdsLast_ db user ct (min count gapToLatest) ""
latestItems <- liftIO $ mapM (safeGetDirectItem db user ct currentTs) latestItemIds
let allItems = chatItems <> latestItems
let chat = c {chatItems = allItems}
if gapToLatest > length latestItems
then pure (chat, Just $ ChatGap {size = gapToLatest - length latestItems, index = Just $ length chatItems})
else pure (chat, Nothing)
getDirectChatMinUnreadItemId_ :: IO (Maybe ChatItemId)
getDirectChatMinUnreadItemId_ =
fmap join . maybeFirstRow fromOnly $
DB.query
db
[sql|
SELECT MIN(chat_item_id)
FROM chat_items
WHERE user_id = ? AND contact_id = ? AND item_status = ?
|]
(userId, contactId, CISRcvNew)
getGroupChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe ChatGap)
getGroupChat db vr user groupId pagination search_ = do
let search = fromMaybe "" search_
g <- getGroupInfo db vr user groupId
case pagination of
CPLast count -> liftIO $ getGroupChatLast_ db user g count search
CPLast count -> liftIO $ (,Nothing) <$> getGroupChatLast_ db user g count search
CPAfter afterId count -> getGroupChatAfter_ db user g afterId count search
CPBefore beforeId count -> getGroupChatBefore_ db user g beforeId count search
CPAround aroundId count -> getGroupChatAround_ db user g aroundId count search
CPInitial count -> do
unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search"
getGroupChatInitial_ db user g count
getGroupChatLast_ :: DB.Connection -> User -> GroupInfo -> Int -> String -> IO (Chat 'CTGroup)
getGroupChatLast_ db user@User {userId} g@GroupInfo {groupId} count search = do
getGroupChatLast_ db user g count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
chatItemIds <- getGroupChatItemIdsLast_
chatItemIds <- getGroupChatItemIdsLast_ db user g count search
currentTs <- getCurrentTime
chatItems <- mapM (safeGetGroupItem db user g currentTs) chatItemIds
pure $ Chat (GroupChat g) (reverse chatItems) stats
where
getGroupChatItemIdsLast_ :: IO [ChatItemId]
getGroupChatItemIdsLast_ =
map fromOnly
<$> DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY item_ts DESC, chat_item_id DESC
LIMIT ?
|]
(userId, groupId, search, count)
getGroupChatItemIdsLast_ :: DB.Connection -> User -> GroupInfo -> Int -> String -> IO [ChatItemId]
getGroupChatItemIdsLast_ db User {userId} GroupInfo {groupId} count search =
map fromOnly
<$> DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY item_ts DESC, chat_item_id DESC
LIMIT ?
|]
(userId, groupId, search, count)
getGroupChatItemGapToLatest_ :: DB.Connection -> User -> GroupInfo -> CChatItem 'CTGroup -> String -> IO Int
getGroupChatItemGapToLatest_ db User {userId} GroupInfo {groupId} chatItem search = do
count <-
maybeFirstRow fromOnly $
DB.queryNamed
db
[sql|
SELECT COUNT(1)
FROM chat_items
WHERE user_id = :userId AND group_id = :groupId AND item_text LIKE '%' || :search || '%'
AND (created_at > :itemCreatedAt OR (created_at = :itemCreatedAt AND chat_item_id > :chatItemId))
ORDER BY created_at DESC, chat_item_id DESC
|]
[ ":userId" := userId,
":groupId" := groupId,
":search" := search,
":itemCreatedAt" := chatItemCreatedAt chatItem,
":chatItemId" := cchatItemId chatItem
]
pure $ maybe 0 (\c -> max 0 (c - 1)) count
safeGetGroupItem :: DB.Connection -> User -> GroupInfo -> UTCTime -> ChatItemId -> IO (CChatItem 'CTGroup)
safeGetGroupItem db user g currentTs itemId =
@@ -1157,14 +1245,16 @@ getGroupMemberChatItemLast db user@User {userId} groupId groupMemberId = do
(userId, groupId, groupMemberId)
getGroupChatItem db user groupId chatItemId
getGroupChatAfter_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup)
getGroupChatAfter_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe ChatGap)
getGroupChatAfter_ db user g@GroupInfo {groupId} afterChatItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
afterChatItem <- getGroupChatItem db user groupId afterChatItemId
chatItemIds <- liftIO $ getGroupChatItemIdsAfter_ db user g afterChatItemId count search (chatItemTs afterChatItem)
currentTs <- liftIO getCurrentTime
chatItems <- liftIO $ mapM (safeGetGroupItem db user g currentTs) chatItemIds
pure $ Chat (GroupChat g) chatItems stats
gapToLatest <- liftIO $ getGroupChatItemGapToLatest_ db user g (head chatItems) search
let chatGap = if gapToLatest > 0 then Just $ ChatGap {size = gapToLatest, index = Nothing} else Nothing
pure (Chat (GroupChat g) chatItems stats, chatGap)
getGroupChatItemIdsAfter_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> UTCTime -> IO [ChatItemId]
getGroupChatItemIdsAfter_ db User {userId} GroupInfo {groupId} afterChatItemId count search afterChatItemTs =
@@ -1181,14 +1271,14 @@ getGroupChatItemIdsAfter_ db User {userId} GroupInfo {groupId} afterChatItemId c
|]
(userId, groupId, search, afterChatItemTs, afterChatItemTs, afterChatItemId, count)
getGroupChatBefore_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup)
getGroupChatBefore_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe ChatGap)
getGroupChatBefore_ db user g@GroupInfo {groupId} beforeChatItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
beforeChatItem <- getGroupChatItem db user groupId beforeChatItemId
chatItemIds <- liftIO $ getGroupChatItemIdsBefore_ db user g beforeChatItemId count search (chatItemTs beforeChatItem)
currentTs <- liftIO getCurrentTime
chatItems <- liftIO $ mapM (safeGetGroupItem db user g currentTs) chatItemIds
pure $ Chat (GroupChat g) (reverse chatItems) stats
pure (Chat (GroupChat g) (reverse chatItems) stats, Nothing)
getGroupChatItemIdsBefore_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> UTCTime -> IO [ChatItemId]
getGroupChatItemIdsBefore_ db User {userId} GroupInfo {groupId} beforeChatItemId count search beforeChatItemTs =
@@ -1205,7 +1295,7 @@ getGroupChatItemIdsBefore_ db User {userId} GroupInfo {groupId} beforeChatItemId
|]
(userId, groupId, search, beforeChatItemTs, beforeChatItemTs, beforeChatItemId, count)
getGroupChatAround_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup)
getGroupChatAround_ :: DB.Connection -> User -> GroupInfo -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe ChatGap)
getGroupChatAround_ db user g@GroupInfo {groupId} aroundItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
let (fetchCountBefore, fetchCountAfter) = divideFetchCountAround_ (count - 1)
@@ -1216,39 +1306,101 @@ getGroupChatAround_ db user g@GroupInfo {groupId} aroundItemId count search = do
beforeChatItems <- liftIO $ reverse <$> mapM (safeGetGroupItem db user g currentTs) beforeIds
afterChatItems <- liftIO $ mapM (safeGetGroupItem db user g currentTs) afterIds
let chatItems = beforeChatItems <> [middleChatItem] <> afterChatItems
pure $ Chat (GroupChat g) chatItems stats
gapToLatest <- liftIO $ getGroupChatItemGapToLatest_ db user g (last chatItems) search
let chatGap = if gapToLatest > 0 then Just $ ChatGap {size = gapToLatest, index = Nothing} else Nothing
pure (Chat (GroupChat g) chatItems stats, chatGap)
getLocalChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTLocal)
getGroupChatInitial_ :: DB.Connection -> User -> GroupInfo -> Int -> ExceptT StoreError IO (Chat 'CTGroup, Maybe ChatGap)
getGroupChatInitial_ db user@User {userId} g@GroupInfo {groupId} count = do
firstUnreadItemId_ <- liftIO getGroupChatMinUnreadItemId_
case firstUnreadItemId_ of
Just firstUnreadItemId -> do
(chat, gap) <- getGroupChatAround_ db user g firstUnreadItemId count ""
case gap of
Just ChatGap {size} -> do
if size > snd (divideFetchCountAround_ count)
then getLatestItems_ chat size
else pure (chat, Nothing)
Nothing -> pure (chat, Nothing)
Nothing -> liftIO $ (,Nothing) <$> getGroupChatLast_ db user g count ""
where
getGroupChatMinUnreadItemId_ :: IO (Maybe ChatItemId)
getGroupChatMinUnreadItemId_ =
fmap join . maybeFirstRow fromOnly $
DB.query
db
[sql|
SELECT MIN(chat_item_id)
FROM chat_items
WHERE user_id = ? AND group_id = ? AND item_status = ?
|]
(userId, groupId, CISRcvNew)
getLatestItems_ :: Chat 'CTGroup -> Int -> ExceptT StoreError IO (Chat 'CTGroup, Maybe ChatGap)
getLatestItems_ c@Chat {chatItems} gapToLatest = do
currentTs <- liftIO getCurrentTime
latestItemIds <- liftIO $ getGroupChatItemIdsLast_ db user g (min count gapToLatest) ""
latestItems <- liftIO $ mapM (safeGetGroupItem db user g currentTs) latestItemIds
let allItems = chatItems <> latestItems
let chat = c {chatItems = allItems}
if gapToLatest > length latestItems
then pure (chat, Just $ ChatGap {size = gapToLatest - length latestItems, index = Just $ length chatItems})
else pure (chat, Nothing)
getLocalChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTLocal, Maybe ChatGap)
getLocalChat db user folderId pagination search_ = do
let search = fromMaybe "" search_
nf <- getNoteFolder db user folderId
case pagination of
CPLast count -> liftIO $ getLocalChatLast_ db user nf count search
CPLast count -> liftIO $ (,Nothing) <$> getLocalChatLast_ db user nf count search
CPAfter afterId count -> getLocalChatAfter_ db user nf afterId count search
CPBefore beforeId count -> getLocalChatBefore_ db user nf beforeId count search
CPAround aroundId count -> getLocalChatAround_ db user nf aroundId count search
CPInitial count -> do
unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search"
getLocalChatInitial_ db user nf count
getLocalChatLast_ :: DB.Connection -> User -> NoteFolder -> Int -> String -> IO (Chat 'CTLocal)
getLocalChatLast_ db user@User {userId} nf@NoteFolder {noteFolderId} count search = do
getLocalChatLast_ db user nf count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
chatItemIds <- getLocalChatItemIdsLast_
chatItemIds <- getLocalChatItemIdsLast_ db user nf count search
currentTs <- getCurrentTime
chatItems <- mapM (safeGetLocalItem db user nf currentTs) chatItemIds
pure $ Chat (LocalChat nf) (reverse chatItems) stats
where
getLocalChatItemIdsLast_ :: IO [ChatItemId]
getLocalChatItemIdsLast_ =
map fromOnly
<$> DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY created_at DESC, chat_item_id DESC
LIMIT ?
|]
(userId, noteFolderId, search, count)
getLocalChatItemIdsLast_ :: DB.Connection -> User -> NoteFolder -> Int -> String -> IO [ChatItemId]
getLocalChatItemIdsLast_ db User {userId} NoteFolder {noteFolderId} count search =
map fromOnly
<$> DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND note_folder_id = ? AND item_text LIKE '%' || ? || '%'
ORDER BY created_at DESC, chat_item_id DESC
LIMIT ?
|]
(userId, noteFolderId, search, count)
getLocalChatItemGapToLatest_ :: DB.Connection -> User -> NoteFolder -> CChatItem 'CTLocal -> String -> IO Int
getLocalChatItemGapToLatest_ db User {userId} NoteFolder {noteFolderId} chatItem search = do
count <-
maybeFirstRow fromOnly $
DB.queryNamed
db
[sql|
SELECT COUNT(1)
FROM chat_items
WHERE user_id = :userId AND note_folder_id = :noteFolderId AND item_text LIKE '%' || :search || '%'
AND (created_at > :itemCreatedAt OR (created_at = :itemCreatedAt AND chat_item_id > :chatItemId))
ORDER BY created_at DESC, chat_item_id DESC
|]
[ ":userId" := userId,
":noteFolderId" := noteFolderId,
":search" := search,
":itemCreatedAt" := chatItemCreatedAt chatItem,
":chatItemId" := cchatItemId chatItem
]
pure $ maybe 0 (\c -> max 0 (c - 1)) count
safeGetLocalItem :: DB.Connection -> User -> NoteFolder -> UTCTime -> ChatItemId -> IO (CChatItem 'CTLocal)
safeGetLocalItem db user NoteFolder {noteFolderId} currentTs itemId =
@@ -1276,14 +1428,16 @@ safeToLocalItem currentTs itemId = \case
file = Nothing
}
getLocalChatAfter_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal)
getLocalChatAfter_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal, Maybe ChatGap)
getLocalChatAfter_ db user nf@NoteFolder {noteFolderId} afterChatItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
afterChatItem <- getLocalChatItem db user noteFolderId afterChatItemId
chatItemIds <- liftIO $ getLocalChatItemIdsAfter_ db user nf afterChatItemId count search (chatItemCreatedAt afterChatItem)
currentTs <- liftIO getCurrentTime
chatItems <- liftIO $ mapM (safeGetLocalItem db user nf currentTs) chatItemIds
pure $ Chat (LocalChat nf) chatItems stats
gapToLatest <- liftIO $ getLocalChatItemGapToLatest_ db user nf (head chatItems) search
let chatGap = if gapToLatest > 0 then Just $ ChatGap {size = gapToLatest, index = Nothing} else Nothing
pure (Chat (LocalChat nf) chatItems stats, chatGap)
getLocalChatItemIdsAfter_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> UTCTime -> IO [ChatItemId]
getLocalChatItemIdsAfter_ db User {userId} NoteFolder {noteFolderId} afterChatItemId count search afterChatItemCreatedAt =
@@ -1300,14 +1454,14 @@ getLocalChatItemIdsAfter_ db User {userId} NoteFolder {noteFolderId} afterChatIt
|]
(userId, noteFolderId, search, afterChatItemCreatedAt, afterChatItemCreatedAt, afterChatItemId, count)
getLocalChatBefore_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal)
getLocalChatBefore_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal, Maybe ChatGap)
getLocalChatBefore_ db user nf@NoteFolder {noteFolderId} beforeChatItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
beforeChatItem <- getLocalChatItem db user noteFolderId beforeChatItemId
chatItemIds <- liftIO $ getLocalChatItemIdsBefore_ db user nf beforeChatItemId count search (chatItemCreatedAt beforeChatItem)
currentTs <- liftIO getCurrentTime
chatItems <- liftIO $ mapM (safeGetLocalItem db user nf currentTs) chatItemIds
pure $ Chat (LocalChat nf) (reverse chatItems) stats
pure (Chat (LocalChat nf) (reverse chatItems) stats, Nothing)
getLocalChatItemIdsBefore_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> UTCTime -> IO [ChatItemId]
getLocalChatItemIdsBefore_ db User {userId} NoteFolder {noteFolderId} beforeChatItemId count search beforeChatItemCreatedAt =
@@ -1324,7 +1478,7 @@ getLocalChatItemIdsBefore_ db User {userId} NoteFolder {noteFolderId} beforeChat
|]
(userId, noteFolderId, search, beforeChatItemCreatedAt, beforeChatItemCreatedAt, beforeChatItemId, count)
getLocalChatAround_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal)
getLocalChatAround_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal, Maybe ChatGap)
getLocalChatAround_ db user nf@NoteFolder {noteFolderId} aroundItemId count search = do
let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
let (fetchCountBefore, fetchCountAfter) = divideFetchCountAround_ (count - 1)
@@ -1335,7 +1489,45 @@ getLocalChatAround_ db user nf@NoteFolder {noteFolderId} aroundItemId count sear
beforeChatItems <- liftIO $ reverse <$> mapM (safeGetLocalItem db user nf currentTs) beforeIds
afterChatItems <- liftIO $ mapM (safeGetLocalItem db user nf currentTs) afterIds
let chatItems = beforeChatItems <> [middleChatItem] <> afterChatItems
pure $ Chat (LocalChat nf) chatItems stats
gapToLatest <- liftIO $ getLocalChatItemGapToLatest_ db user nf (head chatItems) search
let chatGap = if gapToLatest > 0 then Just $ ChatGap {size = gapToLatest, index = Nothing} else Nothing
pure (Chat (LocalChat nf) chatItems stats, chatGap)
getLocalChatInitial_ :: DB.Connection -> User -> NoteFolder -> Int -> ExceptT StoreError IO (Chat 'CTLocal, Maybe ChatGap)
getLocalChatInitial_ db user@User {userId} nf@NoteFolder {noteFolderId} count = do
firstUnreadItemId_ <- liftIO getLocalChatMinUnreadItemId_
case firstUnreadItemId_ of
Just firstUnreadItemId -> do
(chat, gap) <- getLocalChatAround_ db user nf firstUnreadItemId count ""
case gap of
Just ChatGap {size} -> do
if size > snd (divideFetchCountAround_ count)
then getLatestItems_ chat size
else pure (chat, Nothing)
Nothing -> pure (chat, Nothing)
Nothing -> liftIO $ (,Nothing) <$> getLocalChatLast_ db user nf count ""
where
getLocalChatMinUnreadItemId_ :: IO (Maybe ChatItemId)
getLocalChatMinUnreadItemId_ =
fmap join . maybeFirstRow fromOnly $
DB.query
db
[sql|
SELECT MIN(chat_item_id)
FROM chat_items
WHERE user_id = ? AND note_folder_id = ? AND item_status = ?
|]
(userId, noteFolderId, CISRcvNew)
getLatestItems_ :: Chat 'CTLocal -> Int -> ExceptT StoreError IO (Chat 'CTLocal, Maybe ChatGap)
getLatestItems_ c@Chat {chatItems} gapToLatest = do
currentTs <- liftIO getCurrentTime
latestItemIds <- liftIO $ getLocalChatItemIdsLast_ db user nf (min count gapToLatest) ""
latestItems <- liftIO $ mapM (safeGetLocalItem db user nf currentTs) latestItemIds
let allItems = chatItems <> latestItems
let chat = c {chatItems = allItems}
if gapToLatest > length latestItems
then pure (chat, Just $ ChatGap {size = gapToLatest - length latestItems, index = Just $ length chatItems})
else pure (chat, Nothing)
toChatItemRef :: (ChatItemId, Maybe Int64, Maybe Int64, Maybe Int64) -> Either StoreError (ChatRef, ChatItemId)
toChatItemRef = \case
@@ -1628,6 +1820,12 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
CPAfter afterId count -> liftIO . getAllChatItemsAfter_ afterId count . aChatItemTs =<< getAChatItem_ afterId
CPBefore beforeId count -> liftIO . getAllChatItemsBefore_ beforeId count . aChatItemTs =<< getAChatItem_ beforeId
CPAround aroundId count -> liftIO . getAllChatItemsAround_ aroundId count . aChatItemTs =<< getAChatItem_ aroundId
CPInitial count -> do
unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search"
firstUnreadItemId <- liftIO getFirstUnreadItemId_
case firstUnreadItemId of
Just itemId -> liftIO . getAllChatItemsAround_ itemId count . aChatItemTs =<< getAChatItem_ itemId
Nothing -> liftIO $ getAllChatItemsLast_ count
mapM (uncurry (getAChatItem db vr user)) itemRefs
where
search = fromMaybe "" search_
@@ -1686,6 +1884,16 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
item <- getChatItem aroundId
itemsAfter <- getAllChatItemsAfter_ aroundId fetchCountAfter aroundTs
pure $ itemsBefore <> item <> itemsAfter
getFirstUnreadItemId_ =
fmap join . maybeFirstRow fromOnly $
DB.query
db
[sql|
SELECT MIN(chat_item_id)
FROM chat_items
WHERE user_id = ? AND item_status = ?
|]
(userId, CISRcvNew)
getChatItemIdsByAgentMsgId :: DB.Connection -> Int64 -> AgentMsgId -> IO [ChatItemId]
getChatItemIdsByAgentMsgId db connId msgId =
+7 -1
View File
@@ -93,7 +93,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
CRChatSuspended -> ["chat suspended"]
CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [viewJSON chats]
CRChats chats -> viewChats ts tz chats
CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat]
CRApiChat u chat gap -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat] <> viewChatGap gap
CRApiParsedMarkdown ft -> [viewJSON ft]
CRUserProtoServers u userServers -> ttyUser u $ viewUserServers userServers testView
CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure
@@ -555,6 +555,12 @@ viewUsersList us =
viewGroupSubscribed :: GroupInfo -> [StyledString]
viewGroupSubscribed g = [membershipIncognito g <> ttyFullGroup g <> ": connected to server(s)"]
viewChatGap :: Maybe ChatGap -> [StyledString]
viewChatGap Nothing = []
viewChatGap (Just ChatGap {size})
| size < 1 = []
| otherwise = [sShow size <> " newer message(s) available"]
showSMPServer :: SMPServer -> String
showSMPServer ProtocolServer {host} = B.unpack $ strEncode host