diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 313ec0d419..159d01bbc9 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -12,7 +12,7 @@ import SimpleXChat struct FramedItemView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @EnvironmentObject var scrollModel: ReverseListScrollModel + @EnvironmentObject var scrollModel: ReverseListScrollModel @ObservedObject var chat: Chat var chatItem: ChatItem var preview: UIImage? diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift index a80c5412b6..044ee2a26d 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift @@ -13,7 +13,7 @@ import AVKit struct FullScreenMediaView: View { @EnvironmentObject var m: ChatModel - @EnvironmentObject var scrollModel: ReverseListScrollModel + @EnvironmentObject var scrollModel: ReverseListScrollModel @State var chatItem: ChatItem @State var image: UIImage? @State var player: AVPlayer? = nil diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index e9e86c31d7..703be5d65f 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -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() - @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() + let visibleItems = PassthroughSubject<[String], Never>() private var bag = Set() init() { @@ -475,34 +477,41 @@ struct ChatView: View { isReallyNearBottom: true, unreadBelow: 0 ) - events + visibleItems .receive(on: DispatchQueue.global(qos: .background)) - .scan(Set()) { 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()) { 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) } } diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index 94d160e1b4..5edc051ff3 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -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: UIViewControllerRepresentable { - let items: Array +struct ReverseList: UIViewControllerRepresentable { + let items: Array - @Binding var scrollState: ReverseListScrollModel.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: 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: UIV /// Controller, which hosts SwiftUI cells class Controller: UITableViewController { private enum Section { case main } - private let representer: ReverseList - private var dataSource: UITableViewDiffableDataSource! + var representer: ReverseList + private var dataSource: UITableViewDiffableDataSource! private var itemCount: Int = 0 private var bag = Set() @@ -71,7 +73,7 @@ struct ReverseList: UIV } // 3. Configure data source - self.dataSource = UITableViewDiffableDataSource( + self.dataSource = UITableViewDiffableDataSource( tableView: tableView ) { (tableView, indexPath, item) -> UITableViewCell? in if indexPath.item > self.itemCount - 8, self.itemCount > 8 { @@ -171,8 +173,8 @@ struct ReverseList: UIV Task { representer.scrollState = .atDestination } } - func update(items: Array) { - var snapshot = NSDiffableDataSourceSnapshot() + func update(items: [ChatItem]) { + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(items) dataSource.defaultRowAnimation = .none @@ -189,6 +191,17 @@ struct ReverseList: 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: UIV } /// Manages ``ReverseList`` scrolling -class ReverseListScrollModel: 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: ObservableObject { state = .scrollingTo(.bottom) } - func scrollToItem(id: Item.ID) { + func scrollToItem(id: ChatItem.ID) { state = .scrollingTo(.item(id)) } } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 9e6d3005b6..43892ec469 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -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()) + .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()) + .environmentObject(ReverseListScrollModel()) } case let .voice(_, duration): smallContentPreviewVoice(size: dynamicMediaSize) {