Compare commits

..

No commits in common. "f69f0ae59e6a8030724ab781416ad5c78acc4cfc" and "2396a707ec4c52490982cd375b30622382e1b9b3" have entirely different histories.

3 changed files with 22 additions and 76 deletions

View File

@ -280,9 +280,6 @@
"Блокировка контакта \"%1$@\" появится позже." : {
"comment" : "Contacts block placeholder message"
},
"Больше сообщений нет" : {
"comment" : "Chat history top reached"
},
"Бот" : {
"comment" : "Тип сессии — бот"
},

View File

@ -7,13 +7,13 @@ final class PrivateChatViewModel: ObservableObject {
@Published private(set) var isLoadingMore: Bool = false
@Published var errorMessage: String?
@Published private(set) var isSending: Bool = false
@Published private(set) var hasMore: Bool = true
private let chatService: ChatService
private let chatId: String
private let currentUserId: String?
private let pageSize: Int
private let maxMessageLength: Int = 4096
private var hasMore: Bool = true
private var didLoadInitially: Bool = false
private var messageObserver: NSObjectProtocol?
@ -127,21 +127,11 @@ final class PrivateChatViewModel: ObservableObject {
func loadMoreIfNeeded(for message: MessageItem) {
guard didLoadInitially, !isInitialLoading, hasMore, !isLoadingMore else { return }
guard let messageIndex = messages.firstIndex(where: { $0.id == message.id }) else {
return
}
let threshold = 10
guard messageIndex < threshold else {
return
}
guard let oldestMessage = messages.first else { return }
guard let first = messages.first, first.id == message.id else { return }
isLoadingMore = true
chatService.fetchPrivateChatHistory(chatId: chatId, beforeMessageId: oldestMessage.id, limit: pageSize) { [weak self] result in
chatService.fetchPrivateChatHistory(chatId: chatId, beforeMessageId: message.id, limit: pageSize) { [weak self] result in
guard let self else { return }
switch result {

View File

@ -24,10 +24,6 @@ struct PrivateChatView: View {
@EnvironmentObject private var messageCenter: IncomingMessageCenter
@Environment(\.dismiss) private var dismiss
@State private var previousStandardAppearance: UINavigationBarAppearance?
@State private var previousScrollEdgeAppearance: UINavigationBarAppearance?
@State private var previousCompactAppearance: UINavigationBarAppearance?
init(chat: PrivateChatListItem, currentUserId: String?) {
self.chat = chat
self.currentUserId = currentUserId
@ -40,15 +36,14 @@ struct PrivateChatView: View {
ZStack(alignment: .bottomTrailing) {
content
.onChange(of: viewModel.messages.count) { _ in
if isBottomAnchorVisible {
scrollToBottom(proxy: proxy)
}
guard !viewModel.isLoadingMore else { return }
scrollToBottom(proxy: proxy)
}
.onChange(of: scrollToBottomTrigger) { _ in
scrollToBottom(proxy: proxy)
}
if !isBottomAnchorVisible && !viewModel.isInitialLoading {
if !isBottomAnchorVisible {
scrollToBottomButton(proxy: proxy)
.padding(.trailing, 12)
.padding(.bottom, 4)
@ -98,24 +93,10 @@ struct PrivateChatView: View {
}
.onAppear {
messageCenter.activeChatId = chat.chatId
previousStandardAppearance = UINavigationBar.appearance().standardAppearance
previousScrollEdgeAppearance = UINavigationBar.appearance().scrollEdgeAppearance
previousCompactAppearance = UINavigationBar.appearance().compactAppearance
let appearance = UINavigationBarAppearance()
appearance.configureWithDefaultBackground()
// appearance.shadowColor = .clear
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
}
.onChange(of: viewModel.isInitialLoading) { isLoading in
if isLoading {
hasPositionedToBottom = false
} else if !viewModel.messages.isEmpty {
scrollToBottomTrigger = .init()
}
}
.safeAreaInset(edge: .bottom) {
@ -125,12 +106,6 @@ struct PrivateChatView: View {
if messageCenter.activeChatId == chat.chatId {
messageCenter.activeChatId = nil
}
if let standard = previousStandardAppearance {
UINavigationBar.appearance().standardAppearance = standard
}
UINavigationBar.appearance().scrollEdgeAppearance = previousScrollEdgeAppearance
UINavigationBar.appearance().compactAppearance = previousCompactAppearance
}
}
@ -149,43 +124,35 @@ struct PrivateChatView: View {
private var messagesList: some View {
ScrollView {
LazyVStack(alignment: .leading, spacing: 12) {
Color.clear
.frame(height: 1)
.id(bottomAnchorId)
.onAppear { isBottomAnchorVisible = true }
.onDisappear { isBottomAnchorVisible = false }
if let message = viewModel.errorMessage,
!message.isEmpty,
!viewModel.messages.isEmpty {
errorBanner(message: message)
.scaleEffect(x: 1, y: -1, anchor: .center)
if viewModel.isLoadingMore {
loadingMoreView
} else if viewModel.messages.isEmpty {
emptyState
}
ForEach(viewModel.messages.reversed()) { message in
ForEach(viewModel.messages) { message in
messageRow(for: message)
.id(message.id)
.scaleEffect(x: 1, y: -1, anchor: .center)
.onAppear {
guard hasPositionedToBottom else { return }
viewModel.loadMoreIfNeeded(for: message)
}
}
if viewModel.isLoadingMore {
loadingMoreView
.scaleEffect(x: 1, y: -1, anchor: .center)
} else if !viewModel.hasMore && !viewModel.messages.isEmpty {
noMoreMessagesView
.scaleEffect(x: 1, y: -1, anchor: .center)
} else if viewModel.messages.isEmpty {
emptyState
.scaleEffect(x: 1, y: -1, anchor: .center)
if let message = viewModel.errorMessage,
!message.isEmpty,
!viewModel.messages.isEmpty {
errorBanner(message: message)
}
Color.clear
.frame(height: 1)
.id(bottomAnchorId)
.onAppear { isBottomAnchorVisible = true }
.onDisappear { isBottomAnchorVisible = false }
}
.padding(.vertical, 12)
}
.scaleEffect(x: 1, y: -1, anchor: .center)
.simultaneousGesture(
DragGesture().onChanged { value in
guard value.translation.height > 0 else { return }
@ -212,14 +179,6 @@ struct PrivateChatView: View {
.padding(.vertical, 8)
}
private var noMoreMessagesView: some View {
Text(NSLocalizedString("Больше сообщений нет", comment: "Chat history top reached"))
.font(.caption)
.foregroundColor(.secondary)
.padding(.vertical, 16)
.frame(maxWidth: .infinity)
}
private func errorView(message: String) -> some View {
VStack(spacing: 12) {
Text(message)
@ -482,7 +441,7 @@ struct PrivateChatView: View {
DispatchQueue.main.async {
withAnimation(.easeInOut(duration: 0.2)) {
proxy.scrollTo(targetId, anchor: .top)
proxy.scrollTo(targetId, anchor: .bottom)
}
}
}