aboutsummaryrefslogtreecommitdiff
path: root/Hotline/macOS/MessageView.swift
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2024-05-03 12:49:52 -0700
committerDustin Mierau <dustin@mierau.me>2024-05-03 12:49:52 -0700
commit02b452ccaf54cb24eb38fcb752126888994c0a1b (patch)
treec30e71a1df363092a369fd7847c6223914662ce4 /Hotline/macOS/MessageView.swift
parent73208d70d21db7e526e371fa863acd0327caad9e (diff)
First pass at private messages UI.
Diffstat (limited to 'Hotline/macOS/MessageView.swift')
-rw-r--r--Hotline/macOS/MessageView.swift166
1 files changed, 72 insertions, 94 deletions
diff --git a/Hotline/macOS/MessageView.swift b/Hotline/macOS/MessageView.swift
index e880435..94d3d2e 100644
--- a/Hotline/macOS/MessageView.swift
+++ b/Hotline/macOS/MessageView.swift
@@ -1,130 +1,108 @@
import SwiftUI
-//extension View {
-// func endEditing() {
-// UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
-// }
-//}
-
struct MessageView: View {
@Environment(Hotline.self) private var model: Hotline
- @Environment(\.colorScheme) var colorScheme
+ @Environment(\.colorScheme) private var colorScheme
- @State var input: String = ""
+ @State private var input: String = ""
@State private var scrollPos: Int?
@State private var contentHeight: CGFloat = 0
+ @Namespace private var bottomID
+ @FocusState private var focusedField: FocusedField?
- @Namespace var bottomID
-
- let userID: UInt
-
+ var userID: UInt16
+
var body: some View {
- NavigationStack {
- VStack(spacing: 0) {
- ScrollViewReader { reader in
- ScrollView {
+ ScrollViewReader { reader in
+ VStack(alignment: .leading, spacing: 0) {
+
+ // MARK: Scroll View
+ GeometryReader { gm in
+ ScrollView(.vertical) {
LazyVStack(alignment: .leading) {
-// ForEach(model.chat) { msg in
-// if msg.type == .agreement {
-// VStack(alignment: .leading) {
-// VStack(alignment: .leading, spacing: 0) {
-// Text(msg.text)
-// .textSelection(.enabled)
-// .padding()
-// .opacity(0.75)
-// HStack {
-// Spacer()
-// Text((model.serverTitle) + " Server Agreement")
-// .font(.caption)
-// .fontWeight(.medium)
-// .opacity(0.4)
-// .lineLimit(1)
-// .truncationMode(.middle)
-// Spacer()
-// }
-// .padding()
-// .background(colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.9))
-// }
-// .background(colorScheme == .dark ? Color(white: 0.1) : Color(white: 0.96))
-// .cornerRadius(16)
-// .frame(maxWidth: .infinity)
-// }
-// .padding()
-// }
-// else if msg.type == .status {
-// HStack {
-// Spacer()
-// Text(msg.text)
-// .lineLimit(1)
-// .truncationMode(.middle)
-// .opacity(0.3)
-// Spacer()
-// }
-// .padding()
-// }
-// else {
-// HStack(alignment: .firstTextBaseline) {
-// if let username = msg.username {
-// Text("**\(username):** \(msg.text)")
-// }
-// else {
-// Text(msg.text)
-// .textSelection(.enabled)
-// }
-// Spacer()
-// }
-// .padding(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16))
-// }
-// }
+ ForEach(model.instantMessages[userID] ?? [InstantMessage]()) { msg in
+ HStack(alignment: .firstTextBaseline) {
+ if msg.direction == .outgoing {
+ Spacer()
+ }
+
+ Text(LocalizedStringKey(msg.text.convertLinksToMarkdown()))
+ .lineSpacing(4)
+ .multilineTextAlignment(.leading)
+ .textSelection(.enabled)
+ .tint(msg.direction == .outgoing ? Color("Outgoing Message Link") : Color("Link Color"))
+ .foregroundStyle(msg.direction == .outgoing ? Color("Outgoing Message Text") : Color("Incoming Message Text"))
+ .padding(EdgeInsets(top: 10, leading: 14, bottom: 10, trailing: 14))
+ .background(msg.direction == .outgoing ? Color("Outgoing Message Background") : Color("Incoming Message Background"))
+ .clipShape(RoundedRectangle(cornerRadius: 16))
+
+ if msg.direction == .incoming {
+ Spacer()
+ }
+ }
+ .padding(EdgeInsets(top: 2, leading: 0, bottom: 2, trailing: 0))
+ }
+
EmptyView().id(bottomID)
}
- .padding(.bottom, 12)
+ .padding()
}
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
.defaultScrollAnchor(.bottom)
- .onChange(of: model.chat.count) {
- withAnimation {
+ .onChange(of: model.instantMessages[userID]?.count) {
+ withAnimation(.easeOut(duration: 0.15).delay(0.25)) {
reader.scrollTo(bottomID, anchor: .bottom)
}
- print("SCROLLED TO BOTTOM")
+ model.markInstantMessagesAsRead(userID: userID)
}
.onAppear {
reader.scrollTo(bottomID, anchor: .bottom)
+ model.markInstantMessagesAsRead(userID: userID)
}
- .scrollDismissesKeyboard(.interactively)
- .onTapGesture {
-// self.endEditing()
+ .onChange(of: gm.size) {
+ reader.scrollTo(bottomID, anchor: .bottom)
}
- .textSelection(.enabled)
}
+ // MARK: Input Divider
Divider()
- HStack(alignment: .top) {
- Image(systemName: "chevron.right").opacity(0.4)
- TextField("", text: $input, axis: .vertical)
+ // MARK: Input Bar
+ HStack(alignment: .lastTextBaseline, spacing: 0) {
+ let user = model.users.first(where: { $0.id == userID })
+ TextField("Message \(user?.name ?? "")", text: $input, axis: .vertical)
+ .focused($focusedField, equals: .chatInput)
.textFieldStyle(.plain)
.lineLimit(1...5)
+ .multilineTextAlignment(.leading)
.onSubmit {
-// if !self.input.isEmpty {
-// model.sendChat(self.input)
-// }
+ if !self.input.isEmpty {
+ model.sendInstantMessage(self.input, userID: self.userID)
+ }
self.input = ""
}
.frame(maxWidth: .infinity)
- Button {
-// if !self.input.isEmpty {
-// model.sendChat(self.input)
-// }
- self.input = ""
- } label: {
- Image(systemName: self.input.isEmpty ? "arrow.up.circle" : "arrow.up.circle.fill")
- .resizable()
- .scaledToFit()
- .frame(width: 24.0, height: 24.0)
- .opacity(self.input.isEmpty ? 0.4 : 1.0)
+ .padding()
+ }
+ .frame(maxWidth: .infinity, minHeight: 28)
+ .padding(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16))
+ .overlay(alignment: .leadingFirstTextBaseline) {
+ Image(systemName: "chevron.right").opacity(0.4).offset(x: 16)
+ }
+ .onContinuousHover { phase in
+ switch phase {
+ case .active(_):
+ NSCursor.iBeam.set()
+ case .ended:
+ NSCursor.arrow.set()
+ break
}
- }.padding()
+ }
+ .onTapGesture {
+ focusedField = .chatInput
+ }
}
+ .background(Color(nsColor: .textBackgroundColor))
}
}
}