Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b0c773d
Begin preparing for appkit backed rows
viktorstrate Sep 24, 2026
8a4c751
Implement profile row in AppKit
viktorstrate Sep 24, 2026
81a79a2
Add typing indicator in AppKit
viktorstrate Sep 24, 2026
40f8672
Start porting message rows
viktorstrate Sep 25, 2026
01f755a
Replace text body with NSTextView and disable automaticHeights
viktorstrate Sep 25, 2026
af7a138
First draft of the new hover actions
viktorstrate Sep 25, 2026
339ebb3
Implement hover buttons and fix hover cursor
viktorstrate Sep 25, 2026
c57d9bd
Implement virtual item row in AppKit
viktorstrate Sep 25, 2026
3e8fa80
Implement state AppKit row
viktorstrate Sep 25, 2026
8d44ae3
Split message rows into content and outer
viktorstrate Sep 25, 2026
0043337
Port message replies to AppKit
viktorstrate Sep 27, 2026
5d331be
Port message thread link to AppKit
viktorstrate Sep 27, 2026
b575a14
Port message reactions to AppKit
viktorstrate Sep 27, 2026
2d0d61a
Port message read recipts to AppKit
viktorstrate Sep 28, 2026
ad8724a
Synchronize timeline row selection with underlying NSTableView
viktorstrate Sep 28, 2026
15fe23f
Move over other message types to AppKit
viktorstrate Sep 28, 2026
f48bdc6
Prepare for different content message types
viktorstrate Sep 28, 2026
09269f9
Implement image rows in AppKit
viktorstrate Sep 28, 2026
d700675
Implement video rows in AppKit
viktorstrate Sep 28, 2026
c654d2d
Port file messages to AppKit
viktorstrate Sep 28, 2026
ca128d7
Clean up old SwiftUI code in timeline
viktorstrate Sep 28, 2026
e256cc4
Make timeline update only affected rows.
viktorstrate Sep 28, 2026
01494ef
Remove old unused files after rewrite
viktorstrate Sep 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Mactrix.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,6 @@
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_STRICT_MEMORY_SAFETY = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 6.0;
};
Expand Down Expand Up @@ -396,7 +395,6 @@
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_STRICT_MEMORY_SAFETY = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 6.0;
};
Expand Down
10 changes: 10 additions & 0 deletions Mactrix/Extensions/NSColor+userID.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import AppKit

public extension NSColor {
convenience init(userID: String) {
unsafe self.init(named: String(
format: "Username%02d",
userID.unicodeScalars.reduce(0) { $0 + Int($1.value) } % 16 + 1
))!
}
}
21 changes: 15 additions & 6 deletions Mactrix/Models/LiveTimeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ import AsyncAlgorithms
import Foundation
import MatrixRustSDK
import OSLog
import SwiftUI

/// Delegate subscribed to by the `NSTimelineView` to update the focused timeline row.
@MainActor
public protocol LiveTimelineFocusDelegate: AnyObject {
func focusTimelineEvent(id: EventOrTransactionId)
}

@MainActor
protocol LiveTimelineDiffDelegate: AnyObject {
func timelineDidApply(diffs: [TimelineDiff])
}

@MainActor @Observable
public final class LiveTimeline {
Expand All @@ -14,17 +24,15 @@ public final class LiveTimeline {
@ObservationIgnored private var timelineHandle: TaskHandle?
@ObservationIgnored private var paginateHandle: TaskHandle?

public var scrollPosition = ScrollPosition(idType: TimelineGroup.ID.self, edge: .bottom)
public var errorMessage: String?

public private(set) var focusedTimelineEventId: EventOrTransactionId?
// public private(set) var focusedTimelineGroupId: String?
@ObservationIgnored public weak var focusDelegate: (any LiveTimelineFocusDelegate)?
@ObservationIgnored weak var diffDelegate: (any LiveTimelineDiffDelegate)?

public var sendReplyTo: MatrixRustSDK.EventTimelineItem?

public private(set) var timelineItems: [TimelineItem] = []
public private(set) var loadedReplyDetails: [String: InReplyToDetails] = [:]
// public private(set) var timelineGroups: TimelineGroups = .init()

public private(set) var paginating: PaginationStatus = .idle(hitTimelineStart: false)
public private(set) var hitTimelineStart: Bool = false
Expand Down Expand Up @@ -157,7 +165,7 @@ public final class LiveTimeline {

public func focusEvent(id eventId: EventOrTransactionId) {
Logger.liveTimeline.info("focus event: \(eventId.id)")
focusedTimelineEventId = eventId
focusDelegate?.focusTimelineEvent(id: eventId)
}
}

Expand Down Expand Up @@ -194,6 +202,7 @@ extension LiveTimeline {
hitTimelineStart = true
}

diffDelegate?.timelineDidApply(diffs: diff)
loadPendingReplyDetails()
}

Expand Down
11 changes: 5 additions & 6 deletions Mactrix/Models/MatrixClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,19 +255,18 @@ extension MatrixClient: UI.ImageLoader {
imageCache.setObject(image, forKey: key, cost: cost)
}

func cachedImage(matrixUrl: String) -> Image? {
guard let nsImage = Self.imageCache.object(forKey: NSString(string: matrixUrl)) else { return nil }
return Image(nsImage: nsImage)
func cachedImage(matrixUrl: String) -> NSImage? {
return Self.imageCache.object(forKey: NSString(string: matrixUrl))
}

func loadImage(matrixUrl: String, size: CGSize?) async throws -> Image? {
func loadImage(matrixUrl: String, size: CGSize?) async throws -> NSImage? {
let cacheKey = if let size {
NSString(string: "\(matrixUrl)_\(Int(size.width))x\(Int(size.height))")
} else {
NSString(string: matrixUrl)
}
if let cached = Self.imageCache.object(forKey: cacheKey) {
return Image(nsImage: cached)
return cached
}

let mediaSource = try MediaSource.fromUrl(url: matrixUrl)
Expand All @@ -284,7 +283,7 @@ extension MatrixClient: UI.ImageLoader {
do {
let nsImage = try imageData.toOrientedImage(contentType: imageData.computeMimeType())
Self.setCachedImage(nsImage, forKey: cacheKey)
return Image(nsImage: nsImage)
return nsImage
} catch {
Logger.matrixClient.error("failed convert matrix media data to Image: \(error) \(imageData)")
throw error
Expand Down
162 changes: 0 additions & 162 deletions Mactrix/Models/TimelineGroups.swift

This file was deleted.

9 changes: 1 addition & 8 deletions Mactrix/Models/WindowState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,8 @@ final class WindowState {
return
}

/* guard let focusItem = roomTimeline.timelineItems?.first(where: { $0.asEvent()?.eventOrTransactionId.id == eventId }) else {
Logger.windowState.warning("focus message failed, message not found")
return
} */

Logger.windowState.warning("scrolling to message \(eventId)")
withAnimation {
roomTimeline.scrollPosition.scrollTo(id: eventId)
}
roomTimeline.focusEvent(id: .eventId(eventId: eventId))
}

func focusThread(rootEventId: String) {
Expand Down
1 change: 0 additions & 1 deletion Mactrix/Views/ChatView/ChatInputView/ChatInputView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ struct ChatInputView: View {

chatInput = ""
replyTo = nil
timeline.scrollPosition.scrollTo(edge: .bottom)
}

private func saveDraft() async {
Expand Down
Loading
Loading