diff options
| author | Dustin Mierau <dustin@mierau.me> | 2024-05-03 12:49:52 -0700 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2024-05-03 12:49:52 -0700 |
| commit | 02b452ccaf54cb24eb38fcb752126888994c0a1b (patch) | |
| tree | c30e71a1df363092a369fd7847c6223914662ce4 /Hotline/macOS/MessageView.swift | |
| parent | 73208d70d21db7e526e371fa863acd0327caad9e (diff) | |
First pass at private messages UI.
Diffstat (limited to 'Hotline/macOS/MessageView.swift')
| -rw-r--r-- | Hotline/macOS/MessageView.swift | 166 |
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)) } } } |