blob: 625b9e4be5f4094ed27fb1bdd8f2b29fa484c135 [file] [log] [blame]
/*
* Copyright (C) 2024 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import SwiftUI
struct MessageTopBaseView<Content>: View where Content: View {
var model: MessagePanelVM
let padding: CGFloat = 10
let content: Content
let closeAction: () -> Void
init(model: MessagePanelVM, closeAction: @escaping () -> Void, @ViewBuilder content: () -> Content) {
self.closeAction = closeAction
self.content = content()
self.model = model
}
var body: some View {
Rectangle()
.frame(height: 1)
.foregroundColor(model.styling.secondaryTextColor)
.padding(.horizontal, padding * 0.5)
HStack(alignment: .center) {
Spacer().frame(width: padding)
content
Spacer()
Button(action: closeAction, label: {
Image(systemName: "xmark.circle")
.resizable()
.font(Font.title.weight(.light))
.imageScale(.small)
.scaledToFit()
.padding(9)
.frame(width: 40, height: 40)
.foregroundColor(model.styling.secondaryTextColor)
})
}
.padding(.vertical, padding)
.padding(.horizontal, 0)
.background(Color(UIColor.systemBackground))
.cornerRadius(10)
}
}
struct ReplyViewInMessagePanel: View {
var messageToReply: MessageContentVM
@StateObject var model: MessagePanelVM
let padding: CGFloat = 10
var body: some View {
MessageTopBaseView(model: model, closeAction: model.cancelReply) {
VStack(alignment: .leading, spacing: 6) {
(Text(L10n.Conversation.inReplyTo) +
Text(" \(model.inReplyTo)").bold())
.font(model.styling.secondaryFont)
.lineLimit(1)
.truncationMode(.tail)
.foregroundColor(model.styling.textColor)
Text(messageToReply.message.content)
.font(model.styling.secondaryFont)
.lineLimit(1)
.truncationMode(.middle)
.foregroundColor(model.styling.secondaryTextColor)
}
Spacer()
.frame(width: padding)
if messageToReply.type == .fileTransfer {
if let player = messageToReply.player, player.hasVideo.value {
PlayerSwiftUI(model: messageToReply, player: player, onLongGesture: {}, ratio: 0.4, withControls: false, customCornerRadius: 10)
} else if let image = messageToReply.finalImage {
ImageOrGifView(message: messageToReply, image: image, onLongGesture: {}, minHeight: 20, maxHeight: 50, customCornerRadius: 10)
}
} else if messageToReply.type == .text,
let metadata = messageToReply.metadata {
URLPreview(metadata: metadata, maxDimension: 50)
.cornerRadius(messageToReply.cornerRadius)
}
}
}
}
struct EditMessagePanel: View {
var messageToEdit: MessageContentVM
@StateObject var model: MessagePanelVM
@Binding var text: String
let padding: CGFloat = 10
var body: some View {
MessageTopBaseView(model: model, closeAction: {
model.cancelEdit()
text = ""
}) {
Text(L10n.Global.editing)
.font(model.styling.secondaryFont)
.foregroundColor(Color(UIColor.systemBlue))
Spacer()
.frame(width: 5)
Text(messageToEdit.message.content)
.font(model.styling.secondaryFont)
.lineLimit(1)
.truncationMode(.middle)
.foregroundColor(model.styling.secondaryTextColor)
}
}
}
struct MessagePanelView: View {
@StateObject var model: MessagePanelVM
@Binding var isFocused: Bool
@SwiftUI.State private var text: String = ""
@SwiftUI.State private var textHeight: CGFloat = 0
let padding: CGFloat = 10
struct MessagePanelImageButton: View {
let model: MessagePanelVM
let systemName: String
let width: CGFloat
let height: CGFloat
var body: some View {
Image(systemName: systemName)
.resizable()
.font(Font.title.weight(.light))
.imageScale(.small)
.scaledToFit()
.padding(.horizontal, 9)
.padding(.top, 13)
.padding(.bottom, 5)
.frame(width: width, height: height)
.foregroundColor(model.styling.secondaryTextColor)
}
}
var body: some View {
VStack {
if let message = model.messageToReply {
ReplyViewInMessagePanel(messageToReply: message, model: model)
} else if let editMesage = model.messageToEdit {
EditMessagePanel(messageToEdit: editMesage, model: model, text: $text)
.onAppear {
text = editMesage.content
}
}
HStack(alignment: .bottom, spacing: 1) {
Menu(content: menuContent, label: {
MessagePanelImageButton(model: model, systemName: "plus.circle", width: 42, height: 42)
})
Button(action: {
self.model.sendPhoto()
}, label: {
MessagePanelImageButton(model: model, systemName: "camera", width: 44, height: 42)
})
Spacer()
.frame(width: 5)
UITextViewWrapper(text: $text, isFocused: $isFocused, dynamicHeight: $textHeight)
.frame(minHeight: textHeight, maxHeight: textHeight)
.cornerRadius(18)
.placeholder(when: text.isEmpty, alignment: .leading) {
Text(model.placeholder)
.padding(.horizontal, 12)
.padding(.vertical, 8)
.lineLimit(1)
.truncationMode(.tail)
.font(model.styling.textFont)
.foregroundColor(model.styling.secondaryTextColor)
.cornerRadius(18)
}
Spacer()
.frame(width: 10)
Button(action: {
self.model.sendMessage(text: text)
cleanState()
}, label: {
if text.isEmpty {
Text(model.defaultEmoji)
.font(.title)
.frame(width: 36, height: 36)
.padding(.bottom, 2)
} else {
MessagePanelImageButton(model: model, systemName: "paperplane", width: 42, height: 42)
}
})
.animation(.default, value: text.isEmpty)
}
}
.padding(padding)
.background(
VisualEffect(style: .regular, withVibrancy: false)
.ignoresSafeArea(edges: [.leading, .trailing, .bottom])
)
.onChange(of: model.isEdit) { _ in
isFocused = model.isEdit
}
}
private func menuContent() -> some View {
Group {
Button(action: {
model.recordAudio()
}) {
Label(MessagePanelState.recordAudio.toString(), systemImage: MessagePanelState.recordAudio.imageName())
}
Button(action: {
model.recordVideo()
}) {
Label(MessagePanelState.recordVido.toString(), systemImage: MessagePanelState.recordVido.imageName())
}
Button(action: {
model.shareLocation()
}) {
Label(MessagePanelState.shareLocation.toString(), systemImage: MessagePanelState.shareLocation.imageName())
}
Button(action: {
model.sendFile()
}) {
Label(MessagePanelState.sendFile.toString(), systemImage: MessagePanelState.sendFile.imageName())
}
Button(action: {
model.openGalery()
}) {
Label(MessagePanelState.openGalery.toString(), systemImage: MessagePanelState.openGalery.imageName())
}
}
}
func cleanState() {
text = ""
model.cancelReply()
model.cancelEdit()
}
}