another.im-ios/AnotherIM/View/Main/Conversation/ConversationMessageRow.swift
2024-10-31 12:43:16 +01:00

82 lines
3.1 KiB
Swift

import Foundation
import SwiftUI
struct ConversationMessageRow: View {
@EnvironmentObject var messages: MessagesStore
let message: Message
@State private var offset: CGSize = .zero
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 0) {
if isOutgoing() {
Spacer()
MessageAttr(message: message)
.padding(.trailing, 4)
}
ConversationMessageContainer(message: message, isOutgoing: isOutgoing())
.background(isOutgoing() ? Color.Material.Shape.alternate : Color.Material.Shape.white)
.clipShape(ConversationMessageBubble(isOutgoing: isOutgoing()))
if !isOutgoing() {
MessageAttr(message: message)
.padding(.leading, 4)
Spacer()
}
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
.background(Color.clearTappable)
.offset(offset)
.gesture(
DragGesture(minimumDistance: 30, coordinateSpace: .local)
.onChanged { value in
var width = value.translation.width
width = width > 0 ? 0 : width
offset = CGSize(width: width, height: 0)
}
.onEnded { value in
let targetWidth: CGFloat = -90
withAnimation(.easeOut(duration: 0.1)) {
if value.translation.width <= targetWidth {
Vibration.success.vibrate()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
withAnimation(.easeOut(duration: 0.1)) {
offset = .zero
}
}
} else {
offset = .zero
}
}
if value.translation.width <= targetWidth {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) {
messages.replyText = message.body ?? ""
}
}
}
)
}
.listRowInsets(.zero)
.listRowSeparator(.hidden)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.Material.Background.light)
}
private func isOutgoing() -> Bool {
message.from == messages.roster.bareJid
}
}
struct ConversationMessageBubble: Shape {
let isOutgoing: Bool
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(
roundedRect: rect,
byRoundingCorners: isOutgoing ? [.topLeft, .bottomLeft, .bottomRight] : [.topRight, .bottomLeft, .bottomRight],
cornerRadii: CGSize(width: 8, height: 10)
)
return Path(path.cgPath)
}
}