multiline message entry field (#270)
This commit is contained in:
committed by
GitHub
parent
e424e9328b
commit
3d137995d8
@@ -55,9 +55,12 @@ final class ChatModel: ObservableObject {
|
||||
func addChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
||||
if let ix = chats.firstIndex(where: { $0.id == cInfo.id }) {
|
||||
chats[ix].chatItems = [cItem]
|
||||
withAnimation {
|
||||
let chat = chats.remove(at: ix)
|
||||
chats.insert(chat, at: 0)
|
||||
if ix > 0 {
|
||||
if chatId == nil {
|
||||
withAnimation { popChat(ix) }
|
||||
} else {
|
||||
DispatchQueue.main.async { self.popChat(ix) }
|
||||
}
|
||||
}
|
||||
}
|
||||
if chatId == cInfo.id {
|
||||
@@ -67,6 +70,11 @@ final class ChatModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func popChat(_ ix: Int) {
|
||||
let chat = chats.remove(at: ix)
|
||||
chats.insert(chat, at: 0)
|
||||
}
|
||||
|
||||
func removeChat(_ id: String) {
|
||||
withAnimation {
|
||||
chats.removeAll(where: { $0.id == id })
|
||||
|
||||
@@ -15,8 +15,8 @@ struct EmojiItemView: View {
|
||||
let sent = chatItem.chatDir.sent
|
||||
|
||||
VStack {
|
||||
Text(chatItem.content.text)
|
||||
.font(Font.custom("Emoji", size: 48, relativeTo: .largeTitle))
|
||||
Text(chatItem.content.text.trimmingCharacters(in: .whitespaces))
|
||||
.font(emojiFont)
|
||||
.padding(.top, 8)
|
||||
.padding(.horizontal, 6)
|
||||
.frame(maxWidth: .infinity, alignment: sent ? .trailing : .leading)
|
||||
|
||||
@@ -37,8 +37,8 @@ struct ChatItemView_Previews: PreviewProvider {
|
||||
ChatItemView(chatItem: chatItemSample(1, .directSnd, .now, "hello"), width: 360)
|
||||
ChatItemView(chatItem: chatItemSample(2, .directRcv, .now, "hello there too"), width: 360)
|
||||
ChatItemView(chatItem: chatItemSample(1, .directSnd, .now, "🙂"), width: 360)
|
||||
ChatItemView(chatItem: chatItemSample(2, .directRcv, .now, "👍👍👍"), width: 360)
|
||||
ChatItemView(chatItem: chatItemSample(2, .directRcv, .now, "👍👍👍👍"), width: 360)
|
||||
ChatItemView(chatItem: chatItemSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), width: 360)
|
||||
ChatItemView(chatItem: chatItemSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), width: 360)
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.onTapGesture {
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func scrollToBottom(_ proxy: ScrollViewProxy) {
|
||||
@@ -76,7 +79,8 @@ struct ChatView_Previews: PreviewProvider {
|
||||
chatItemSample(4, .directRcv, .now, "hello again"),
|
||||
chatItemSample(5, .directSnd, .now, "hi there!!!"),
|
||||
chatItemSample(6, .directSnd, .now, "how are you?"),
|
||||
chatItemSample(7, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||
chatItemSample(7, .directSnd, .now, "👍👍👍👍"),
|
||||
chatItemSample(8, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||
]
|
||||
return ChatView(chatInfo: sampleDirectChatInfo)
|
||||
.environmentObject(chatModel)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
private func isSimpleEmoji(_ c: Character) -> Bool {
|
||||
guard let firstScalar = c.unicodeScalars.first else { return false }
|
||||
@@ -23,5 +24,7 @@ func isEmoji(_ c: Character) -> Bool {
|
||||
|
||||
func isShortEmoji(_ str: String) -> Bool {
|
||||
let s = str.trimmingCharacters(in: .whitespaces)
|
||||
return s.count <= 3 && s.allSatisfy(isEmoji)
|
||||
return s.count > 0 && s.count <= 4 && s.allSatisfy(isEmoji)
|
||||
}
|
||||
|
||||
let emojiFont = Font.custom("Emoji", size: 48, relativeTo: .largeTitle)
|
||||
|
||||
@@ -11,36 +11,78 @@ import SwiftUI
|
||||
struct SendMessageView: View {
|
||||
var sendMessage: (String) -> Void
|
||||
var inProgress: Bool = false
|
||||
@State var command: String = ""
|
||||
@State private var message: String = "" //Lorem ipsum dolor sit amet, consectetur" // adipiscing elit, sed do eiusmod tempor incididunt ut labor7 et dolore magna aliqua. Ut enim ad minim veniam, quis"// nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."// Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
@Namespace var namespace
|
||||
@State private var teHeight: CGFloat = 42
|
||||
@State private var teFont: Font = .body
|
||||
var maxHeight: CGFloat = 360
|
||||
var minHeight: CGFloat = 37
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
TextField("Message...", text: $command)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.textInputAutocapitalization(.never)
|
||||
.disableAutocorrection(true)
|
||||
.onSubmit(submit)
|
||||
ZStack {
|
||||
HStack(alignment: .bottom) {
|
||||
ZStack(alignment: .leading) {
|
||||
Text(message)
|
||||
.font(teFont)
|
||||
.foregroundColor(.clear)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 8)
|
||||
.matchedGeometryEffect(id: "te", in: namespace)
|
||||
.background(GeometryReader(content: updateHeight))
|
||||
TextEditor(text: $message)
|
||||
.onSubmit(submit)
|
||||
.font(teFont)
|
||||
.textInputAutocapitalization(.never)
|
||||
.padding(.horizontal, 5)
|
||||
.allowsTightening(false)
|
||||
.frame(height: teHeight)
|
||||
}
|
||||
|
||||
if (inProgress) {
|
||||
ProgressView()
|
||||
.frame(width: 40, height: 20, alignment: .center)
|
||||
} else {
|
||||
Button("Send", action :submit)
|
||||
.disabled(command.isEmpty)
|
||||
if (inProgress) {
|
||||
ProgressView()
|
||||
.scaleEffect(1.4)
|
||||
.frame(width: 31, height: 31, alignment: .center)
|
||||
.padding([.bottom, .trailing], 3)
|
||||
} else {
|
||||
Button(action: submit) {
|
||||
Image(systemName: "arrow.up.circle.fill")
|
||||
.resizable()
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
.disabled(message.isEmpty)
|
||||
.frame(width: 29, height: 29)
|
||||
.padding([.bottom, .trailing], 4)
|
||||
}
|
||||
}
|
||||
|
||||
RoundedRectangle(cornerSize: CGSize(width: 20, height: 20))
|
||||
.strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)
|
||||
.frame(height: teHeight)
|
||||
}
|
||||
.frame(minHeight: 30)
|
||||
.padding(12)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
||||
func submit() {
|
||||
sendMessage(command)
|
||||
command = ""
|
||||
sendMessage(message)
|
||||
message = ""
|
||||
}
|
||||
|
||||
func updateHeight(_ g: GeometryProxy) -> Color {
|
||||
DispatchQueue.main.async {
|
||||
teHeight = min(max(g.frame(in: .local).size.height, minHeight), maxHeight)
|
||||
teFont = isShortEmoji(message) ? emojiFont : .body
|
||||
}
|
||||
return Color.clear
|
||||
}
|
||||
}
|
||||
|
||||
struct SendMessageView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SendMessageView(sendMessage: { print ($0) })
|
||||
VStack {
|
||||
Text("")
|
||||
Spacer(minLength: 0)
|
||||
SendMessageView(sendMessage: { print ($0) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,28 +12,35 @@ struct ContactRequestView: View {
|
||||
var contactRequest: UserContactRequest
|
||||
|
||||
var body: some View {
|
||||
return VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(alignment: .top) {
|
||||
Text(ChatInfo.contactRequest(contactRequest: contactRequest).chatViewName)
|
||||
.font(.title3)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.blue)
|
||||
.padding(.leading, 8)
|
||||
.padding(.top, 4)
|
||||
.frame(maxHeight: .infinity, alignment: .topLeading)
|
||||
Spacer()
|
||||
Text(getDateFormatter().string(from: contactRequest.createdAt))
|
||||
.font(.subheadline)
|
||||
.padding(.trailing, 28)
|
||||
.padding(.top, 4)
|
||||
.frame(minWidth: 60, alignment: .trailing)
|
||||
.foregroundColor(.secondary)
|
||||
return HStack(spacing: 8) {
|
||||
Image(systemName: "person.crop.circle.fill")
|
||||
.resizable()
|
||||
.foregroundColor(Color(uiColor: .secondarySystemBackground))
|
||||
.frame(width: 63, height: 63)
|
||||
.padding(.leading, 4)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(alignment: .top) {
|
||||
Text(ChatInfo.contactRequest(contactRequest: contactRequest).chatViewName)
|
||||
.font(.title3)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.blue)
|
||||
.padding(.leading, 8)
|
||||
.padding(.top, 4)
|
||||
.frame(maxHeight: .infinity, alignment: .topLeading)
|
||||
Spacer()
|
||||
Text(getDateFormatter().string(from: contactRequest.createdAt))
|
||||
.font(.subheadline)
|
||||
.padding(.trailing, 28)
|
||||
.padding(.top, 4)
|
||||
.frame(minWidth: 60, alignment: .trailing)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Text("wants to connect to you!")
|
||||
.frame(minHeight: 44, maxHeight: 44, alignment: .topLeading)
|
||||
.padding([.leading, .trailing], 8)
|
||||
.padding(.bottom, 4)
|
||||
.padding(.top, 1)
|
||||
}
|
||||
Text("wants to connect to you!")
|
||||
.frame(minHeight: 44, maxHeight: 44, alignment: .topLeading)
|
||||
.padding([.leading, .trailing], 8)
|
||||
.padding(.bottom, 4)
|
||||
.padding(.top, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user