Skip to content

Commit 0d2aedf

Browse files
committed
Render sharp status icons with colored menus and retain routes through back transitions
1 parent cb8d8b7 commit 0d2aedf

13 files changed

Lines changed: 102 additions & 19 deletions

File tree

‎apple/Sources/Logseq/LGNavigationExtension.swift‎

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,9 @@ private struct LGNavigationContent: View {
476476
let composerDismissalEnabled: Bool
477477
let rootTransform: ((AnyView) -> AnyView)?
478478
@State private var path: [Int] = []
479+
#if os(iOS)
480+
@State private var transitions = LGNavigationTransitions()
481+
#endif
479482
@State private var usesSystemGroupedBackground = false
480483
@State private var visibleScrollTitle: String?
481484
@AppStorage("logseq.appearance") private var appearance = "system"
@@ -618,6 +621,9 @@ private struct LGNavigationContent: View {
618621
private var rootNavigationSurface: some View {
619622
transformedRootContent
620623
.padding(.top, navigationContentTopPadding)
624+
#if os(iOS)
625+
.background(LGNavigationControllerObserver(transitions: transitions).allowsHitTesting(false))
626+
#endif
621627
}
622628

623629

@@ -735,10 +741,12 @@ private struct LGNavigationContent: View {
735741
let removedCount = max(0, path.count - newPath.count)
736742
path = newPath
737743
if removedCount > 0 {
738-
Task { @MainActor in
739-
await Task.yield()
740-
emitBack(count: removedCount)
741-
}
744+
#if os(iOS)
745+
// Keep the outgoing route alive until UIKit finishes its pop.
746+
transitions.afterPop { emitBack(count: removedCount) }
747+
#else
748+
emitBack(count: removedCount)
749+
#endif
742750
}
743751
}
744752
)
@@ -763,6 +771,63 @@ private struct LGNavigationContent: View {
763771
}
764772
}
765773

774+
#if os(iOS)
775+
@MainActor
776+
private final class LGNavigationTransitions {
777+
weak var controller: UINavigationController?
778+
779+
func afterPop(_ completion: @escaping @MainActor () -> Void) {
780+
guard let coordinator = controller?.transitionCoordinator else {
781+
completion()
782+
return
783+
}
784+
_ = coordinator.animate(alongsideTransition: nil) { context in
785+
if !context.isCancelled { completion() }
786+
}
787+
}
788+
}
789+
790+
@MainActor
791+
private struct LGNavigationControllerObserver: UIViewControllerRepresentable {
792+
let transitions: LGNavigationTransitions
793+
794+
func makeUIViewController(context: Context) -> Observer {
795+
Observer(transitions: transitions)
796+
}
797+
798+
func updateUIViewController(_ controller: Observer, context: Context) {
799+
controller.resolveNavigationController()
800+
}
801+
802+
final class Observer: UIViewController {
803+
let transitions: LGNavigationTransitions
804+
805+
init(transitions: LGNavigationTransitions) {
806+
self.transitions = transitions
807+
super.init(nibName: nil, bundle: nil)
808+
}
809+
810+
required init?(coder: NSCoder) { fatalError("init(coder:) is unavailable") }
811+
812+
override func loadView() { view = UIView() }
813+
814+
override func didMove(toParent parent: UIViewController?) {
815+
super.didMove(toParent: parent)
816+
resolveNavigationController()
817+
}
818+
819+
override func viewDidAppear(_ animated: Bool) {
820+
super.viewDidAppear(animated)
821+
resolveNavigationController()
822+
}
823+
824+
func resolveNavigationController() {
825+
if let navigationController { transitions.controller = navigationController }
826+
}
827+
}
828+
}
829+
#endif
830+
766831
private struct LGBottomChromeSurface: ViewModifier {
767832
@ViewBuilder
768833
func body(content: Content) -> some View {

‎apple/Sources/Logseq/OutlinerRowExtension.swift‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import LogseqModel
44
import LUIAppleBackend
55
#if os(iOS)
66
import UniformTypeIdentifiers
7+
import UIKit
78
#endif
89

910
struct OutlinerRowPayload: Decodable, Equatable {
@@ -19,6 +20,15 @@ struct OutlinerRowPayload: Decodable, Equatable {
1920
let foreground: String?
2021
var id: String { uuid }
2122

23+
#if os(iOS)
24+
func menuImage(in semanticColors: [String: Color]) -> UIImage? {
25+
let key = icon.replacingOccurrences(of: "app:", with: "")
26+
guard case let .assetName(name) = LGIconPolicy.icons[key] else { return nil }
27+
return UIImage(named: name, in: .module, compatibleWith: nil)?
28+
.withTintColor(UIColor(color(in: semanticColors)), renderingMode: .alwaysOriginal)
29+
}
30+
#endif
31+
2232
func color(in semanticColors: [String: Color]) -> Color {
2333
guard let foreground else { return .primary }
2434
if let color = semanticColors[foreground] { return color }
@@ -131,7 +141,13 @@ struct OutlinerRowPayload: Decodable, Equatable {
131141
Menu {
132142
ForEach(Array(row.statuses.enumerated()), id: \.element.id) { index, item in
133143
Button { emit("set-status", extra: ["count": .int(index)]) } label: {
134-
SwiftUI.Label { Text(verbatim: item.title) } icon: { statusIcon(item) }
144+
SwiftUI.Label { Text(verbatim: item.title) } icon: {
145+
#if os(iOS)
146+
if let image = item.menuImage(in: semanticColors) { Image(uiImage: image) }
147+
#else
148+
statusIcon(item)
149+
#endif
150+
}
135151
}
136152
.accessibilityIdentifier("button.block-task-status-option." + item.uuid)
137153
}

‎apple/Sources/Logseq/OutlinerTextRow.swift‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct OutlinerTextRow: UIViewRepresentable {
1212

1313
func makeUIView(context: Context) -> OutlinerTextRowView { OutlinerTextRowView() }
1414
func updateUIView(_ view: OutlinerTextRowView, context: Context) {
15-
view.update(row, statusTint: UIColor(row.status?.color(in: semanticColors) ?? .primary), emit: emit)
15+
view.update(row, semanticColors: semanticColors, emit: emit)
1616
}
1717
func sizeThatFits(_ proposal: ProposedViewSize, uiView: OutlinerTextRowView, context: Context) -> CGSize? {
1818
guard let width = proposal.width else { return nil }
@@ -39,6 +39,7 @@ struct OutlinerTextRow: UIViewRepresentable {
3939
private let failure = UILabel()
4040
private var tags: [UIButton] = []
4141
private var row: OutlinerRowPayload?
42+
private var semanticColors: [String: Color] = [:]
4243
private var emit: ((String, String?, [String: LUIExtensionValue]) -> Void)?
4344

4445
override init(frame: CGRect) {
@@ -67,9 +68,10 @@ struct OutlinerTextRow: UIViewRepresentable {
6768
}
6869
required init?(coder: NSCoder) { fatalError("init(coder:) is unavailable") }
6970

70-
func update(_ row: OutlinerRowPayload, statusTint: UIColor, emit: @escaping (String, String?, [String: LUIExtensionValue]) -> Void) {
71+
func update(_ row: OutlinerRowPayload, semanticColors: [String: Color] = [:], emit: @escaping (String, String?, [String: LUIExtensionValue]) -> Void) {
7172
self.emit = emit
72-
status.tintColor = statusTint
73+
self.semanticColors = semanticColors
74+
status.tintColor = UIColor(row.status?.color(in: semanticColors) ?? .primary)
7375
guard self.row != row else { return }
7476
self.row = row
7577
guides.update(row)
@@ -100,7 +102,7 @@ struct OutlinerTextRow: UIViewRepresentable {
100102
status.menu = UIMenu(children: [UIDeferredMenuElement.uncached { [weak self] complete in
101103
guard let self, let row = self.row else { complete([]); return }
102104
complete(row.statuses.enumerated().map { index, item in
103-
UIAction(title: item.title, image: self.statusImage(item.icon)) { [weak self] _ in
105+
UIAction(title: item.title, image: item.menuImage(in: self.semanticColors)) { [weak self] _ in
104106
self?.emit?("set-status", nil, ["count": .int(index)])
105107
}
106108
})
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)