diff --git a/Mactrix/Views/ChatView/ChatInputView/ChatInputView.swift b/Mactrix/Views/ChatView/ChatInputView/ChatInputView.swift index a647a3c..d1a49f8 100644 --- a/Mactrix/Views/ChatView/ChatInputView/ChatInputView.swift +++ b/Mactrix/Views/ChatView/ChatInputView/ChatInputView.swift @@ -3,6 +3,8 @@ import OSLog import SwiftUI struct ChatInputView: View { + @Environment(\.accessibilityReduceTransparency) var reduceTransparency + let room: Room let timeline: LiveTimeline @Binding var replyTo: MatrixRustSDK.EventTimelineItem? @@ -115,22 +117,21 @@ struct ChatInputView: View { return .ready(content: replyTo.content, sender: replyTo.sender, senderProfile: replyTo.senderProfile, timestamp: replyTo.timestamp, eventOrTransactionId: replyTo.eventOrTransactionId) } - var body: some View { + var content: some View { VStack(alignment: .leading) { if let replyEmbeddedDetails { EmbeddedMessageView(embeddedEvent: replyEmbeddedDetails) { replyTo = nil } } - ChatTextView(text: $chatInput, disabled: !isDraftLoaded, onSubmit: { Task { await sendMessage() }}) + ChatTextView( + text: $chatInput, + placeholder: "Message \(room.displayName() ?? "room")", + disabled: !isDraftLoaded, + onSubmit: { Task { await sendMessage() } } + ) } .font(.system(size: .init(fontSize))) - .background(Color(NSColor.textBackgroundColor)) - .cornerRadius(4) - .overlay( - RoundedRectangle(cornerRadius: 4) - .stroke(Color(NSColor.separatorColor), lineWidth: 1) - ) .task(id: chatInput) { await chatInputChanged() } @@ -142,8 +143,34 @@ struct ChatInputView: View { // (in case the draft holds a reply) await loadDraft() } - .pointerStyle(.horizontalText) - .padding([.horizontal, .bottom], 10) + } + + @available(macOS 26.0, *) + var tahoeView: some View { + content + .glassEffect(in: .rect(cornerRadius: 16.0)) + .padding(.horizontal) + .padding(.bottom, 10) + } + + var oldView: some View { + content + .background(Color(NSColor.textBackgroundColor)) + .cornerRadius(4) + .overlay( + RoundedRectangle(cornerRadius: 16.0) + .stroke(Color(NSColor.separatorColor), lineWidth: 1) + ) + .padding(.horizontal) + .padding(.bottom, 10) + } + + var body: some View { + if #available(macOS 26.0, *), !reduceTransparency { + tahoeView + } else { + oldView + } } } diff --git a/Mactrix/Views/ChatView/ChatInputView/ChatTextView.swift b/Mactrix/Views/ChatView/ChatInputView/ChatTextView.swift index 07cf226..444e23f 100644 --- a/Mactrix/Views/ChatView/ChatInputView/ChatTextView.swift +++ b/Mactrix/Views/ChatView/ChatInputView/ChatTextView.swift @@ -6,6 +6,7 @@ struct ChatTextView: NSViewRepresentable { typealias NSViewRepresentableType = NSTextView let text: Binding + let placeholder: String let disabled: Bool let onSubmit: () -> Void @@ -14,10 +15,18 @@ struct ChatTextView: NSViewRepresentable { textView.onSubmit = onSubmit + textView.placeholderAttributedString = NSAttributedString( + string: placeholder, + attributes: [.foregroundColor: NSColor.secondaryLabelColor] + ) + + textView.backgroundColor = .clear + textView.drawsBackground = false + context.coordinator.textView = textView textView.delegate = context.coordinator - textView.textContainerInset = NSSize(width: DynamicTextView.padding, height: DynamicTextView.padding) + textView.textContainerInset = DynamicTextView.padding textView.isVerticallyResizable = true textView.isHorizontallyResizable = false @@ -39,6 +48,13 @@ struct ChatTextView: NSViewRepresentable { textView.invalidateIntrinsicContentSize() } + if textView.placeholderAttributedString?.string != placeholder { + textView.placeholderAttributedString = NSAttributedString( + string: placeholder, + attributes: [.foregroundColor: NSColor.secondaryLabelColor] + ) + } + if textView.isEditable != !disabled { textView.isEditable = !disabled textView.isSelectable = !disabled @@ -72,7 +88,9 @@ struct ChatTextView: NSViewRepresentable { } class DynamicTextView: NSTextView { - static let padding = 4 + @objc var placeholderAttributedString: NSAttributedString? + + static let padding = NSSize(width: 10, height: 10) var onSubmit: (() -> Void)? @@ -86,7 +104,7 @@ class DynamicTextView: NSTextView { let usedRect = manager.usedRect(for: container) // Return a flexible width but a fixed height based on text - return NSSize(width: NSView.noIntrinsicMetric, height: ceil(usedRect.height) + CGFloat(2 * Self.padding)) + return NSSize(width: NSView.noIntrinsicMetric, height: ceil(usedRect.height) + CGFloat(2 * Self.padding.height)) } override func setFrameSize(_ newSize: NSSize) { diff --git a/Mactrix/Views/ChatView/ChatView.swift b/Mactrix/Views/ChatView/ChatView.swift index 7c54388..084c1fe 100644 --- a/Mactrix/Views/ChatView/ChatView.swift +++ b/Mactrix/Views/ChatView/ChatView.swift @@ -38,6 +38,7 @@ struct ChatJoinedRoom: View { var body: some View { TimelineViewRepresentable(timeline: timeline, items: timeline.timelineItems) + .ignoresSafeArea(edges: .top) .safeAreaInset(edge: .bottom, spacing: 8) { ChatInputView(room: room.room, timeline: timeline, replyTo: $timeline.sendReplyTo) } diff --git a/Mactrix/Views/ChatView/TimelineView/TimelineTableView.swift b/Mactrix/Views/ChatView/TimelineView/TimelineTableView.swift index e7eb8e2..3aa7d53 100644 --- a/Mactrix/Views/ChatView/TimelineView/TimelineTableView.swift +++ b/Mactrix/Views/ChatView/TimelineView/TimelineTableView.swift @@ -136,6 +136,9 @@ class TimelineViewController: NSViewController { scrollView.documentView = tableView scrollView.hasVerticalScroller = true + + scrollView.automaticallyAdjustsContentInsets = false + scrollView.drawsBackground = false tableView.backgroundColor = .clear view = scrollView @@ -235,7 +238,7 @@ class TimelineViewController: NSViewController { // If the IDs haven't changed, reload all rows in place (content-only update: reactions, read receipts, etc.) // Reloads all rows rather than just visible ones to avoid stale content in NSTableView's prepared/cached views. if oldIds == newIds { - tableView.reloadData(forRowIndexes: IndexSet(integersIn: 0..