ios: fix inaccurate floating unread counters in chat message view

This commit is contained in:
Levitating Pineapple
2024-08-27 23:00:34 +03:00
parent 8a6bf05773
commit fbe26ed7ff
5 changed files with 55 additions and 33 deletions
@@ -12,7 +12,7 @@ import SimpleXChat
struct FramedItemView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@EnvironmentObject var scrollModel: ReverseListScrollModel<ChatItem>
@EnvironmentObject var scrollModel: ReverseListScrollModel
@ObservedObject var chat: Chat
var chatItem: ChatItem
var preview: UIImage?
@@ -13,7 +13,7 @@ import AVKit
struct FullScreenMediaView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var scrollModel: ReverseListScrollModel<ChatItem>
@EnvironmentObject var scrollModel: ReverseListScrollModel
@State var chatItem: ChatItem
@State var image: UIImage?
@State var player: AVPlayer? = nil
+26 -17
View File
@@ -22,8 +22,8 @@ struct ChatView: View {
@Environment(\.presentationMode) var presentationMode
@Environment(\.scenePhase) var scenePhase
@State @ObservedObject var chat: Chat
@StateObject private var scrollModel = ReverseListScrollModel<ChatItem>()
@StateObject private var floatingButtonModel = FloatingButtonModel()
@StateObject private var scrollModel = ReverseListScrollModel()
@StateObject private var floatingButtonModel: FloatingButtonModel = .shared
@State private var showChatInfoSheet: Bool = false
@State private var showAddMembersSheet: Bool = false
@State private var composeState = ComposeState()
@@ -464,9 +464,11 @@ struct ChatView: View {
case chatItemsChanged
}
static let shared = FloatingButtonModel()
@Published var unreadChatItemCounts: UnreadChatItemCounts
private let events = PassthroughSubject<Event, Never>()
let visibleItems = PassthroughSubject<[String], Never>()
private var bag = Set<AnyCancellable>()
init() {
@@ -475,34 +477,41 @@ struct ChatView: View {
isReallyNearBottom: true,
unreadBelow: 0
)
events
visibleItems
.receive(on: DispatchQueue.global(qos: .background))
.scan(Set<String>()) { itemsInView, event in
var updated = itemsInView
switch event {
case let .appeared(viewId): updated.insert(viewId)
case let .disappeared(viewId): updated.remove(viewId)
case .chatItemsChanged: ()
}
return updated
}
.map { ChatModel.shared.unreadChatItemCounts(itemsInView: $0) }
.map { ChatModel.shared.unreadChatItemCounts(itemsInView: Set($0)) }
.removeDuplicates()
.throttle(for: .seconds(0.2), scheduler: DispatchQueue.main, latest: true)
.assign(to: \.unreadChatItemCounts, on: self)
.store(in: &bag)
// events
// .receive(on: DispatchQueue.global(qos: .background))
// .scan(Set<String>()) { itemsInView, event in
// var updated = itemsInView
// switch event {
// case let .appeared(viewId): updated.insert(viewId)
// case let .disappeared(viewId): updated.remove(viewId)
// case .chatItemsChanged: ()
// }
// return updated
// }
// .map { ChatModel.shared.unreadChatItemCounts(itemsInView: $0) }
// .removeDuplicates()
// .throttle(for: .seconds(0.2), scheduler: DispatchQueue.main, latest: true)
// .assign(to: \.unreadChatItemCounts, on: self)
// .store(in: &bag)
}
func appeared(viewId: String) {
events.send(.appeared(viewId))
// events.send(.appeared(viewId))
}
func disappeared(viewId: String) {
events.send(.disappeared(viewId))
// events.send(.disappeared(viewId))
}
func chatItemsChanged() {
events.send(.chatItemsChanged)
// events.send(.chatItemsChanged)
}
}
+25 -12
View File
@@ -8,15 +8,16 @@
import SwiftUI
import Combine
import SimpleXChat
/// A List, which displays it's items in reverse order - from bottom to top
struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIViewControllerRepresentable {
let items: Array<Item>
struct ReverseList<Content: View>: UIViewControllerRepresentable {
let items: Array<ChatItem>
@Binding var scrollState: ReverseListScrollModel<Item>.State
@Binding var scrollState: ReverseListScrollModel.State
/// Closure, that returns user interface for a given item
let content: (Item) -> Content
let content: (ChatItem) -> Content
let loadPage: () -> Void
@@ -25,6 +26,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
}
func updateUIViewController(_ controller: Controller, context: Context) {
controller.representer = self
if case let .scrollingTo(destination) = scrollState, !items.isEmpty {
switch destination {
case .nextPage:
@@ -42,8 +44,8 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
/// Controller, which hosts SwiftUI cells
class Controller: UITableViewController {
private enum Section { case main }
private let representer: ReverseList
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
var representer: ReverseList
private var dataSource: UITableViewDiffableDataSource<Section, ChatItem>!
private var itemCount: Int = 0
private var bag = Set<AnyCancellable>()
@@ -71,7 +73,7 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
}
// 3. Configure data source
self.dataSource = UITableViewDiffableDataSource<Section, Item>(
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
tableView: tableView
) { (tableView, indexPath, item) -> UITableViewCell? in
if indexPath.item > self.itemCount - 8, self.itemCount > 8 {
@@ -171,8 +173,8 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
Task { representer.scrollState = .atDestination }
}
func update(items: Array<Item>) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
func update(items: [ChatItem]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, ChatItem>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.defaultRowAnimation = .none
@@ -189,6 +191,17 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
}
itemCount = items.count
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
ChatView.FloatingButtonModel.shared.visibleItems.send(
(tableView.indexPathsForVisibleRows ?? [])
.compactMap { indexPath -> String? in
if indexPath.item < representer.items.count {
representer.items[indexPath.item].viewId
} else { nil }
}
)
}
}
/// `UIHostingConfiguration` back-port for iOS14 and iOS15
@@ -232,12 +245,12 @@ struct ReverseList<Item: Identifiable & Hashable & Sendable, Content: View>: UIV
}
/// Manages ``ReverseList`` scrolling
class ReverseListScrollModel<Item: Identifiable>: ObservableObject {
class ReverseListScrollModel: ObservableObject {
/// Represents Scroll State of ``ReverseList``
enum State: Equatable {
enum Destination: Equatable {
case nextPage
case item(Item.ID)
case item(ChatItem.ID)
case bottom
}
@@ -255,7 +268,7 @@ class ReverseListScrollModel<Item: Identifiable>: ObservableObject {
state = .scrollingTo(.bottom)
}
func scrollToItem(id: Item.ID) {
func scrollToItem(id: ChatItem.ID) {
state = .scrollingTo(.item(id))
}
}
@@ -324,12 +324,12 @@ struct ChatPreviewView: View {
case let .image(_, image):
smallContentPreview(size: dynamicMediaSize) {
CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery)
.environmentObject(ReverseListScrollModel<ChatItem>())
.environmentObject(ReverseListScrollModel())
}
case let .video(_,image, duration):
smallContentPreview(size: dynamicMediaSize) {
CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery)
.environmentObject(ReverseListScrollModel<ChatItem>())
.environmentObject(ReverseListScrollModel())
}
case let .voice(_, duration):
smallContentPreviewVoice(size: dynamicMediaSize) {