Compare commits

..

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

View File

@ -61,20 +61,20 @@ struct PrivateChatView: View {
} }
.navigationTitle(toolbarTitle) .navigationTitle(toolbarTitle)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
// .navigationBarBackButtonHidden(true) .navigationBarBackButtonHidden(true)
// .toolbar { // .toolbar {
// ToolbarItem(placement: .principal) { // ToolbarItem(placement: .principal) {
// chatToolbarContent // chatToolbarContent
// } // }
// } // }
.toolbar { .toolbar {
// ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
// Button(action: { dismiss() }) { Button(action: { dismiss() }) {
// Image(systemName: "chevron.left") Image(systemName: "chevron.left")
// .font(.system(size: 17, weight: .semibold)) .font(.system(size: 17, weight: .semibold))
// .foregroundColor(.accentColor) .foregroundColor(.accentColor)
// } }
// } }
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
Button(action: openProfile) { Button(action: openProfile) {
@ -117,48 +117,47 @@ struct PrivateChatView: View {
} else if let error = viewModel.errorMessage, viewModel.messages.isEmpty { } else if let error = viewModel.errorMessage, viewModel.messages.isEmpty {
errorView(message: error) errorView(message: error)
} else { } else {
messagesList ScrollView {
} LazyVStack(alignment: .leading, spacing: 12) {
} if viewModel.isLoadingMore {
loadingMoreView
} else if viewModel.messages.isEmpty {
emptyState
}
private var messagesList: some View { ForEach(viewModel.messages) { message in
ScrollView { messageRow(for: message)
LazyVStack(alignment: .leading, spacing: 12) { .id(message.id)
if viewModel.isLoadingMore { .onAppear {
loadingMoreView guard hasPositionedToBottom else { return }
} else if viewModel.messages.isEmpty { viewModel.loadMoreIfNeeded(for: message)
emptyState }
}
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)
ForEach(viewModel.messages) { message in
messageRow(for: message)
.id(message.id)
.onAppear {
guard hasPositionedToBottom else { return }
viewModel.loadMoreIfNeeded(for: message)
}
}
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) .simultaneousGesture(
} DragGesture().onChanged { value in
.simultaneousGesture( guard value.translation.height > 0 else { return }
DragGesture().onChanged { value in isComposerFocused = false
guard value.translation.height > 0 else { return } }
isComposerFocused = false )
.refreshable {
viewModel.refresh()
} }
) }
} }
private var emptyState: some View { private var emptyState: some View {
@ -200,36 +199,37 @@ struct PrivateChatView: View {
return HStack(alignment: .bottom, spacing: 12) { return HStack(alignment: .bottom, spacing: 12) {
if isCurrentUser { Spacer(minLength: 32) } if isCurrentUser { Spacer(minLength: 32) }
messageBubble(for: message, isCurrentUser: isCurrentUser) VStack(alignment: isCurrentUser ? .trailing : .leading, spacing: 6) {
// if !isCurrentUser {
// Text(senderName(for: message))
// .font(.caption)
// .foregroundColor(.secondary)
// }
HStack(alignment: .bottom) {
Text(contentText(for: message))
.font(.body)
.foregroundColor(isCurrentUser ? .white : .primary)
.multilineTextAlignment(.leading)
Text(timestamp(for: message))
.font(.caption2)
.foregroundColor(isCurrentUser ? Color.white.opacity(0.8) : .secondary)
}
}
.padding(.vertical, 10)
.padding(.horizontal, 12)
.background(isCurrentUser ? Color.accentColor : Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.frame(maxWidth: messageBubbleMaxWidth, alignment: isCurrentUser ? .trailing : .leading)
.fixedSize(horizontal: false, vertical: true)
if !isCurrentUser { Spacer(minLength: 32) } if !isCurrentUser { Spacer(minLength: 32) }
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
} }
private func messageBubble(for message: MessageItem, isCurrentUser: Bool) -> some View {
let timeText = timestamp(for: message)
return VStack(alignment: isCurrentUser ? .trailing : .leading, spacing: 4) {
Text(contentText(for: message))
.font(.body)
.foregroundColor(isCurrentUser ? .white : .primary)
.multilineTextAlignment(.leading)
if !timeText.isEmpty {
Text(timeText)
.font(.caption2)
.foregroundColor(isCurrentUser ? Color.white.opacity(0.85) : .secondary)
}
}
.padding(.vertical, 10)
.padding(.horizontal, 12)
.background(isCurrentUser ? Color.accentColor : Color(.secondarySystemBackground))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.frame(maxWidth: messageBubbleMaxWidth, alignment: isCurrentUser ? .trailing : .leading)
.fixedSize(horizontal: false, vertical: true)
}
private var messageBubbleMaxWidth: CGFloat { private var messageBubbleMaxWidth: CGFloat {
min(UIScreen.main.bounds.width * 0.72, 360) min(UIScreen.main.bounds.width * 0.72, 360)
} }