mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-06-04 01:41:43 +00:00
more changes
This commit is contained in:
@@ -322,7 +322,7 @@ let loadItemsPerPage = 50
|
||||
|
||||
func apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) {
|
||||
let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: pagination, search: search))
|
||||
if case let .apiChat(_, chat, navInfo) = r { return (Chat.init(chat), navInfo) }
|
||||
if case let .apiChat(_, chat, navInfo) = r { return (Chat.init(chat), navInfo ?? NavigationInfo()) }
|
||||
throw r
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ struct MergedItems {
|
||||
// chat item id, index in list
|
||||
let indexInParentItems: Dictionary<Int64, Int>
|
||||
|
||||
static func create(_ items: [ChatItem], _ unreadCount: Binding<Int>, _ revealedItems: Set<Int64>, _ chatState: ActiveChatState) -> MergedItems {
|
||||
static func create(_ items: [ChatItem], _ unreadCount: Int, _ revealedItems: Set<Int64>, _ chatState: ActiveChatState) -> MergedItems {
|
||||
if items.isEmpty {
|
||||
return MergedItems(items: [], splits: [], indexInParentItems: [:])
|
||||
}
|
||||
@@ -30,7 +30,7 @@ struct MergedItems {
|
||||
var unclosedSplitIndex: Int? = nil
|
||||
var unclosedSplitIndexInParent: Int? = nil
|
||||
var visibleItemIndexInParent = -1
|
||||
var unreadBefore = unreadCount.wrappedValue - chatState.unreadAfterNewestLoaded
|
||||
var unreadBefore = unreadCount - chatState.unreadAfterNewestLoaded
|
||||
var lastRevealedIdsInMergedItems: BoxedValue<[Int64]>? = nil
|
||||
var lastRangeInReversedForMergedItems: BoxedValue<ClosedRange<Int>>? = nil
|
||||
var recent: MergedItem? = nil
|
||||
@@ -42,14 +42,14 @@ struct MergedItems {
|
||||
let itemIsSplit = itemSplits.contains(item.id)
|
||||
|
||||
if item.id == unreadAfterItemId {
|
||||
unreadBefore = unreadCount.wrappedValue - chatState.unreadAfter
|
||||
unreadBefore = unreadCount - chatState.unreadAfter
|
||||
}
|
||||
if item.isRcvNew {
|
||||
unreadBefore -= 1
|
||||
}
|
||||
|
||||
let revealed = item.mergeCategory == nil || revealedItems.contains(item.id)
|
||||
if recent != nil, case let .grouped(items, _, _, _, mergeCategory, unreadIds, _) = recent, mergeCategory == category, let first = items.first, !revealedItems.contains(first.item.id) && !itemIsSplit {
|
||||
if recent != nil, case let .grouped(items, _, _, _, mergeCategory, _, _) = recent, mergeCategory == category, let first = items.first, !revealedItems.contains(first.item.id) && !itemIsSplit {
|
||||
let listItem = ListItem(item: item, prevItem: prev, nextItem: next, unreadBefore: unreadBefore)
|
||||
recent!.appendItem(listItem)
|
||||
|
||||
@@ -67,11 +67,11 @@ struct MergedItems {
|
||||
let listItem = ListItem(item: item, prevItem: prev, nextItem: next, unreadBefore: unreadBefore)
|
||||
if item.mergeCategory != nil {
|
||||
if item.mergeCategory != prev?.mergeCategory || lastRevealedIdsInMergedItems == nil {
|
||||
lastRevealedIdsInMergedItems?.boxedValue = revealedItems.contains(item.id) ? [item.id] : []
|
||||
} else if revealed, var lastRevealedIdsInMergedItems {
|
||||
lastRevealedIdsInMergedItems = BoxedValue(revealedItems.contains(item.id) ? [item.id] : [])
|
||||
} else if revealed, let lastRevealedIdsInMergedItems {
|
||||
lastRevealedIdsInMergedItems.boxedValue.append(item.id)
|
||||
}
|
||||
lastRangeInReversedForMergedItems?.boxedValue = index ... index
|
||||
lastRangeInReversedForMergedItems = BoxedValue(index ... index)
|
||||
recent = MergedItem.grouped(
|
||||
items: [listItem],
|
||||
revealed: revealed,
|
||||
@@ -114,7 +114,7 @@ struct MergedItems {
|
||||
}
|
||||
|
||||
|
||||
enum MergedItem {
|
||||
enum MergedItem: Hashable {
|
||||
// the item that is always single, cannot be grouped and always revealed
|
||||
case single(
|
||||
item: ListItem,
|
||||
@@ -158,8 +158,8 @@ enum MergedItem {
|
||||
}
|
||||
}
|
||||
|
||||
func reveal(reveal: Bool, revealedItems: Binding<Set<Int64>>) {
|
||||
if case .grouped(let items, let revealed, var revealedIdsWithinGroup, let rangeInReversed, let mergeCategory, let unreadIds, let startIndexInReversedItems) = self {
|
||||
func reveal(_ reveal: Bool, _ revealedItems: Binding<Set<Int64>>) {
|
||||
if case .grouped(let items, _, let revealedIdsWithinGroup, _, _, _, _) = self {
|
||||
var newRevealed = revealedItems.wrappedValue
|
||||
var i = 0
|
||||
if reveal {
|
||||
@@ -226,7 +226,7 @@ struct SplitRange {
|
||||
let indexRangeInParentItems: ClosedRange<Int>
|
||||
}
|
||||
|
||||
struct ListItem {
|
||||
struct ListItem: Hashable {
|
||||
let item: ChatItem
|
||||
let prevItem: ChatItem?
|
||||
let nextItem: ChatItem?
|
||||
@@ -278,7 +278,15 @@ class ActiveChatState {
|
||||
}
|
||||
}
|
||||
|
||||
class BoxedValue<T> {
|
||||
class BoxedValue<T: Hashable>: Hashable {
|
||||
static func == (lhs: BoxedValue<T>, rhs: BoxedValue<T>) -> Bool {
|
||||
lhs.boxedValue == rhs.boxedValue
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine("\(self)")
|
||||
}
|
||||
|
||||
var boxedValue : T
|
||||
init(_ value: T) {
|
||||
self.boxedValue = value
|
||||
@@ -286,7 +294,7 @@ class BoxedValue<T> {
|
||||
}
|
||||
|
||||
extension ReverseList.Controller {
|
||||
func visibleItemIndexesNonReversed(_ mergedItems: Binding<MergedItems>, _ scrollModel: ReverseListScrollModel) -> ClosedRange<Int> {
|
||||
func visibleItemIndexesNonReversed(_ mergedItems: Binding<MergedItems>) -> ClosedRange<Int> {
|
||||
let zero = 0 ... 0
|
||||
if itemCount == 0 {
|
||||
return zero
|
||||
|
||||
@@ -16,6 +16,7 @@ private let memberImageSize: CGFloat = 34
|
||||
struct ChatView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ObservedObject var im = ItemsModel.shared
|
||||
@State var revealedItems: Set<Int64> = Set()
|
||||
@State var theme: AppTheme = buildTheme()
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@@ -32,7 +33,6 @@ struct ChatView: View {
|
||||
@State private var connectionCode: String?
|
||||
@State private var loadingItems = false
|
||||
@State private var firstPage = false
|
||||
@State private var revealedChatItem: ChatItem?
|
||||
@State private var searchMode = false
|
||||
@State private var searchText: String = ""
|
||||
@FocusState private var searchFocussed
|
||||
@@ -168,11 +168,13 @@ struct ChatView: View {
|
||||
}
|
||||
.onAppear {
|
||||
selectedChatItems = nil
|
||||
revealedItems = Set()
|
||||
initChatView()
|
||||
}
|
||||
.onChange(of: chatModel.chatId) { cId in
|
||||
showChatInfoSheet = false
|
||||
selectedChatItems = nil
|
||||
revealedItems = Set()
|
||||
scrollModel.scrollToBottom()
|
||||
stopAudioPlayer()
|
||||
if let cId {
|
||||
@@ -185,14 +187,20 @@ struct ChatView: View {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.onChange(of: revealedChatItem) { _ in
|
||||
.onChange(of: revealedItems) { _ in
|
||||
NotificationCenter.postReverseListNeedsLayout()
|
||||
}
|
||||
.onChange(of: im.isLoading) { isLoading in
|
||||
if !isLoading,
|
||||
im.reversedChatItems.count <= loadItemsPerPage,
|
||||
filtered(im.reversedChatItems).count < 10 {
|
||||
loadChatItems(chat.chatInfo)
|
||||
let pagination: ChatPagination =
|
||||
if let lastItem = im.reversedChatItems.last {
|
||||
.before(chatItemId: lastItem.id, count: loadItemsPerPage)
|
||||
} else {
|
||||
.last(count: loadItemsPerPage)
|
||||
}
|
||||
loadChatItems(chat.chatInfo, pagination)
|
||||
}
|
||||
}
|
||||
.environmentObject(scrollModel)
|
||||
@@ -425,7 +433,11 @@ struct ChatView: View {
|
||||
let cInfo = chat.chatInfo
|
||||
let mergedItems = filtered(im.reversedChatItems)
|
||||
return GeometryReader { g in
|
||||
ReverseList(items: mergedItems, scrollState: $scrollModel.state) { ci in
|
||||
ReverseList(items: mergedItems, revealedItems: $revealedItems, unreadCount: Binding.constant(chat.chatStats.unreadCount), scrollState: $scrollModel.state) { index, mergedItem in
|
||||
let ci = switch mergedItem {
|
||||
case let .single(item, _): item.item
|
||||
case let .grouped(items, _, _, _, _, _, _): items.last!.item
|
||||
}
|
||||
let voiceNoFrame = voiceWithoutFrame(ci)
|
||||
let maxWidth = cInfo.chatType == .group
|
||||
? voiceNoFrame
|
||||
@@ -435,19 +447,22 @@ struct ChatView: View {
|
||||
? (g.size.width - 32)
|
||||
: (g.size.width - 32) * 0.84
|
||||
return ChatItemWithMenu(
|
||||
index: index,
|
||||
isLastItem: index == mergedItems.items.count - 1,
|
||||
chat: $chat,
|
||||
chatItem: ci,
|
||||
item: mergedItem,
|
||||
maxWidth: maxWidth,
|
||||
composeState: $composeState,
|
||||
selectedMember: $selectedMember,
|
||||
showChatInfoSheet: $showChatInfoSheet,
|
||||
revealedChatItem: $revealedChatItem,
|
||||
revealedItems: $revealedItems,
|
||||
selectedChatItems: $selectedChatItems,
|
||||
forwardedChatItems: $forwardedChatItems
|
||||
)
|
||||
.id(ci.id) // Required to trigger `onAppear` on iOS15
|
||||
} loadPage: {
|
||||
loadChatItems(cInfo)
|
||||
} loadItems: { pagination, visibleItemIndexesNonReversed in
|
||||
loadChatItems(cInfo, pagination, visibleItemIndexesNonReversed)
|
||||
}
|
||||
.opacity(ItemsModel.shared.isLoading ? 0 : 1)
|
||||
.padding(.vertical, -InvertedTableView.inset)
|
||||
@@ -847,41 +862,31 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func loadChatItems(_ cInfo: ChatInfo) {
|
||||
private func loadChatItems(_ cInfo: ChatInfo, _ pagination: ChatPagination, _ visibleItemIndexesNonReversed: @escaping () -> ClosedRange<Int> = { 0 ... 0 }) {
|
||||
Task {
|
||||
if loadingItems || firstPage { return }
|
||||
loadingItems = true
|
||||
do {
|
||||
var reversedPage = Array<ChatItem>()
|
||||
var chatItemsAvailable = true
|
||||
// Load additional items until the page is +50 large after merging
|
||||
while chatItemsAvailable && filtered(reversedPage).count < loadItemsPerPage {
|
||||
let pagination: ChatPagination =
|
||||
if let lastItem = reversedPage.last ?? im.reversedChatItems.last {
|
||||
.before(chatItemId: lastItem.id, count: loadItemsPerPage)
|
||||
} else {
|
||||
.last(count: loadItemsPerPage)
|
||||
}
|
||||
let chatItems = try await apiGetChatItems(
|
||||
type: cInfo.chatType,
|
||||
id: cInfo.apiId,
|
||||
pagination: pagination,
|
||||
search: searchText
|
||||
)
|
||||
chatItemsAvailable = !chatItems.isEmpty
|
||||
reversedPage.append(contentsOf: chatItems.reversed())
|
||||
var chatItemsAvailable = true
|
||||
var itemsCountChanged = false
|
||||
// Load additional items until the page is +50 large after merging
|
||||
while chatItemsAvailable && filtered(im.reversedChatItems).count < loadItemsPerPage {
|
||||
let oldCount = im.reversedChatItems.count
|
||||
await apiLoadMessages(
|
||||
cInfo.chatType,
|
||||
cInfo.apiId,
|
||||
pagination,
|
||||
im.chatState,
|
||||
searchText,
|
||||
visibleItemIndexesNonReversed
|
||||
)
|
||||
itemsCountChanged = im.reversedChatItems.count != oldCount
|
||||
chatItemsAvailable = itemsCountChanged
|
||||
}
|
||||
await MainActor.run {
|
||||
if !itemsCountChanged {
|
||||
firstPage = true
|
||||
}
|
||||
await MainActor.run {
|
||||
if reversedPage.count == 0 {
|
||||
firstPage = true
|
||||
} else {
|
||||
im.reversedChatItems.append(contentsOf: reversedPage)
|
||||
}
|
||||
loadingItems = false
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("apiGetChat error: \(responseError(error))")
|
||||
await MainActor.run { loadingItems = false }
|
||||
loadingItems = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -897,12 +902,15 @@ struct ChatView: View {
|
||||
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileRadius = defaultProfileImageCorner
|
||||
@Binding @ObservedObject var chat: Chat
|
||||
@ObservedObject var dummyModel: ChatItemDummyModel = .shared
|
||||
let index: Int
|
||||
let isLastItem: Bool
|
||||
let chatItem: ChatItem
|
||||
let merged: MergedItem
|
||||
let maxWidth: CGFloat
|
||||
@Binding var composeState: ComposeState
|
||||
@Binding var selectedMember: GMember?
|
||||
@Binding var showChatInfoSheet: Bool
|
||||
@Binding var revealedChatItem: ChatItem?
|
||||
@Binding var revealedItems: Set<Int64>
|
||||
|
||||
@State private var deletingItem: ChatItem? = nil
|
||||
@State private var showDeleteMessage = false
|
||||
@@ -918,7 +926,7 @@ struct ChatView: View {
|
||||
@State private var allowMenu: Bool = true
|
||||
@State private var markedRead = false
|
||||
|
||||
var revealed: Bool { chatItem == revealedChatItem }
|
||||
var revealed: Bool { revealedItems.contains(chatItem.id) }
|
||||
|
||||
typealias ItemSeparation = (timestamp: Bool, largeGap: Bool, date: Date?)
|
||||
|
||||
@@ -940,46 +948,44 @@ struct ChatView: View {
|
||||
var body: some View {
|
||||
let currIndex = m.getChatItemIndex(chatItem)
|
||||
let ciCategory = chatItem.mergeCategory
|
||||
let (prevHidden, prevItem) = m.getPrevShownChatItem(currIndex, ciCategory)
|
||||
let range = itemsRange(currIndex, prevHidden)
|
||||
let timeSeparation = getItemSeparation(chatItem, at: currIndex)
|
||||
let im = ItemsModel.shared
|
||||
|
||||
let last = isLastItem ? im.reversedChatItems.last : nil
|
||||
let listItem = merged.newest()
|
||||
let item = listItem.item
|
||||
let range: ClosedRange<Int>? = if case let .grouped(_, _, _, rangeInReversed, _, _, _) = merged {
|
||||
rangeInReversed.boxedValue
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
let itemSeparation: ItemSeparation
|
||||
let prevItemSeparationLargeGap: Boolean
|
||||
let single = switch merged { case .single: true; default: false }
|
||||
// if single || revealed {
|
||||
// let prev = listItem.prevItem
|
||||
// itemSeparation = getItemSeparation(item, prev)
|
||||
// let nextForGap = if (item.mergeCategory != nil && item.mergeCategory == prev?.mergeCategory) || isLastItem { nil } else { listItem.nextItem }
|
||||
// prevItemSeparationLargeGap = if nextForGap == nil { false } else { getItemSeparationLargeGap(nextForGap, item) }
|
||||
// } else {
|
||||
// itemSeparation = getItemSeparation(item, nil)
|
||||
// prevItemSeparationLargeGap = false
|
||||
// }
|
||||
Group {
|
||||
if revealed, let range = range {
|
||||
let items = Array(zip(Array(range), im.reversedChatItems[range]))
|
||||
VStack(spacing: 0) {
|
||||
ForEach(items.reversed(), id: \.1.viewId) { (i: Int, ci: ChatItem) in
|
||||
let prev = i == prevHidden ? prevItem : im.reversedChatItems[i + 1]
|
||||
chatItemView(ci, nil, prev, getItemSeparation(ci, at: i))
|
||||
.overlay {
|
||||
if let selected = selectedChatItems, ci.canBeDeletedForSelf {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
let checked = selected.contains(ci.id)
|
||||
selectUnselectChatItem(select: !checked, ci)
|
||||
}
|
||||
VStack(spacing: 0) {
|
||||
chatItemView(item, range, listItem.prevItem, itemSeparation)
|
||||
// if let date = timeSeparation.date {
|
||||
// DateSeparator(date: date).padding(8)
|
||||
// }
|
||||
.overlay {
|
||||
if let selected = selectedChatItems, chatItem.canBeDeletedForSelf {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
let checked = selected.contains(chatItem.id)
|
||||
selectUnselectChatItem(select: !checked, chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
VStack(spacing: 0) {
|
||||
chatItemView(chatItem, range, prevItem, timeSeparation)
|
||||
if let date = timeSeparation.date {
|
||||
DateSeparator(date: date).padding(8)
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
if let selected = selectedChatItems, chatItem.canBeDeletedForSelf {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
let checked = selected.contains(chatItem.id)
|
||||
selectUnselectChatItem(select: !checked, chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -1072,7 +1078,7 @@ struct ChatView: View {
|
||||
@ViewBuilder func chatItemView(_ ci: ChatItem, _ range: ClosedRange<Int>?, _ prevItem: ChatItem?, _ itemSeparation: ItemSeparation) -> some View {
|
||||
let bottomPadding: Double = itemSeparation.largeGap ? 10 : 2
|
||||
if case let .groupRcv(member) = ci.chatDir,
|
||||
case let .group(groupInfo) = chat.chatInfo {
|
||||
case .group = chat.chatInfo {
|
||||
let (prevMember, memCount): (GroupMember?, Int) =
|
||||
if let range = range {
|
||||
m.getPrevHiddenMember(member, range)
|
||||
@@ -1597,7 +1603,7 @@ struct ChatView: View {
|
||||
private func hideButton() -> Button<some View> {
|
||||
Button {
|
||||
withConditionalAnimation {
|
||||
revealedChatItem = nil
|
||||
merged.reveal(false, $revealedItems)
|
||||
}
|
||||
} label: {
|
||||
Label(
|
||||
@@ -1672,7 +1678,7 @@ struct ChatView: View {
|
||||
private func revealButton(_ ci: ChatItem) -> Button<some View> {
|
||||
Button {
|
||||
withConditionalAnimation {
|
||||
revealedChatItem = ci
|
||||
merged.reveal(true, $revealedItems)
|
||||
}
|
||||
} label: {
|
||||
Label(
|
||||
@@ -1685,7 +1691,7 @@ struct ChatView: View {
|
||||
private func expandButton() -> Button<some View> {
|
||||
Button {
|
||||
withConditionalAnimation {
|
||||
revealedChatItem = chatItem
|
||||
merged.reveal(true, $revealedItems)
|
||||
}
|
||||
} label: {
|
||||
Label(
|
||||
@@ -1698,7 +1704,7 @@ struct ChatView: View {
|
||||
private func shrinkButton() -> Button<some View> {
|
||||
Button {
|
||||
withConditionalAnimation {
|
||||
revealedChatItem = nil
|
||||
merged.reveal(false, $revealedItems)
|
||||
}
|
||||
} label: {
|
||||
Label (
|
||||
|
||||
@@ -13,13 +13,17 @@ import SimpleXChat
|
||||
/// A List, which displays it's items in reverse order - from bottom to top
|
||||
struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
let items: Array<ChatItem>
|
||||
@Binding var revealedItems: Set<Int64>
|
||||
@Binding var unreadCount: Int
|
||||
|
||||
@Binding var scrollState: ReverseListScrollModel.State
|
||||
|
||||
/// Closure, that returns user interface for a given item
|
||||
let content: (ChatItem) -> Content
|
||||
/// Index, merged item
|
||||
let content: (Int, MergedItem) -> Content
|
||||
|
||||
let loadPage: () -> Void
|
||||
// pagination, visibleItemIndexesNonReversed
|
||||
let loadItems: (ChatPagination, @escaping () -> ClosedRange<Int>) -> Void
|
||||
|
||||
func makeUIViewController(context: Context) -> Controller {
|
||||
Controller(representer: self)
|
||||
@@ -46,13 +50,19 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
public class Controller: UITableViewController {
|
||||
private enum Section { case main }
|
||||
var representer: ReverseList
|
||||
private var dataSource: UITableViewDiffableDataSource<Section, ChatItem>!
|
||||
var itemCount: Int = 0
|
||||
private var dataSource: UITableViewDiffableDataSource<Section, MergedItem>!
|
||||
var itemCount: Int {
|
||||
get {
|
||||
mergedItems.items.count
|
||||
}
|
||||
}
|
||||
private var mergedItems: MergedItems
|
||||
private let updateFloatingButtons = PassthroughSubject<Void, Never>()
|
||||
private var bag = Set<AnyCancellable>()
|
||||
|
||||
init(representer: ReverseList) {
|
||||
self.representer = representer
|
||||
self.mergedItems = MergedItems.create(representer.items, representer.unreadCount, Set(), ItemsModel.shared.chatState)
|
||||
super.init(style: .plain)
|
||||
|
||||
// 1. Style
|
||||
@@ -75,20 +85,22 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
}
|
||||
|
||||
// 3. Configure data source
|
||||
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
|
||||
self.dataSource = UITableViewDiffableDataSource<Section, MergedItem>(
|
||||
tableView: tableView
|
||||
) { (tableView, indexPath, item) -> UITableViewCell? in
|
||||
if indexPath.item > self.itemCount - 8 {
|
||||
self.representer.loadPage()
|
||||
logger.debug("LALAL ITEM \(indexPath.item)")
|
||||
let pagination = ChatPagination.last(count: 0)
|
||||
self.representer.loadItems(pagination, { self.visibleItemIndexesNonReversed(Binding.constant(self.mergedItems)) })
|
||||
}
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
|
||||
if #available(iOS 16.0, *) {
|
||||
cell.contentConfiguration = UIHostingConfiguration { self.representer.content(item) }
|
||||
cell.contentConfiguration = UIHostingConfiguration { self.representer.content(indexPath.item, item) }
|
||||
.margins(.all, 0)
|
||||
.minSize(height: 1) // Passing zero will result in system default of 44 points being used
|
||||
} else {
|
||||
if let cell = cell as? HostingCell<Content> {
|
||||
cell.set(content: self.representer.content(item), parent: self)
|
||||
cell.set(content: self.representer.content(indexPath.item, item), parent: self)
|
||||
} else {
|
||||
fatalError("Unexpected Cell Type for: \(item)")
|
||||
}
|
||||
@@ -185,22 +197,23 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
}
|
||||
|
||||
func update(items: [ChatItem]) {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, ChatItem>()
|
||||
let wasCount = itemCount
|
||||
self.mergedItems = MergedItems.create(items, representer.unreadCount, representer.revealedItems, ItemsModel.shared.chatState)
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, MergedItem>()
|
||||
snapshot.appendSections([.main])
|
||||
snapshot.appendItems(items)
|
||||
snapshot.appendItems(mergedItems.items)
|
||||
dataSource.defaultRowAnimation = .none
|
||||
dataSource.apply(
|
||||
snapshot,
|
||||
animatingDifferences: itemCount != 0 && abs(items.count - itemCount) == 1
|
||||
animatingDifferences: wasCount != 0 && abs(mergedItems.items.count - wasCount) == 1
|
||||
)
|
||||
// Sets content offset on initial load
|
||||
if itemCount == 0 {
|
||||
if wasCount == 0 {
|
||||
tableView.setContentOffset(
|
||||
CGPoint(x: 0, y: -InvertedTableView.inset),
|
||||
animated: false
|
||||
)
|
||||
}
|
||||
itemCount = items.count
|
||||
updateFloatingButtons.send()
|
||||
}
|
||||
|
||||
|
||||
@@ -213,8 +213,7 @@ public func chatResponse(_ s: String) -> ChatResponse {
|
||||
if let jApiChat = jResp["apiChat"] as? NSDictionary,
|
||||
let user: UserRef = try? decodeObject(jApiChat["user"] as Any),
|
||||
let jChat = jApiChat["chat"] as? NSDictionary,
|
||||
let jNavInfo = jApiChat["navInfo"] as? NSDictionary,
|
||||
let (chat, navInfo) = try? parseChatData(jChat, jNavInfo) {
|
||||
let (chat, navInfo) = try? parseChatData(jChat, jApiChat["navInfo"] as? NSDictionary) {
|
||||
return .apiChat(user: user, chat: chat, navInfo: navInfo)
|
||||
}
|
||||
} else if type == "chatCmdError" {
|
||||
|
||||
@@ -563,7 +563,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case chatStopped
|
||||
case chatSuspended
|
||||
case apiChats(user: UserRef, chats: [ChatData])
|
||||
case apiChat(user: UserRef, chat: ChatData, navInfo: NavigationInfo)
|
||||
case apiChat(user: UserRef, chat: ChatData, navInfo: NavigationInfo?)
|
||||
case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo)
|
||||
case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?)
|
||||
case serverOperatorConditions(conditions: ServerOperatorConditions)
|
||||
@@ -1977,6 +1977,11 @@ public struct ChatSettings: Codable, Hashable {
|
||||
public struct NavigationInfo: Decodable {
|
||||
public var afterUnread: Int = 0
|
||||
public var afterTotal: Int = 0
|
||||
|
||||
public init(afterUnread: Int = 0, afterTotal: Int = 0) {
|
||||
self.afterUnread = afterUnread
|
||||
self.afterTotal = afterTotal
|
||||
}
|
||||
}
|
||||
|
||||
public enum MsgFilter: String, Codable, Hashable {
|
||||
|
||||
Reference in New Issue
Block a user