Archived
Fix reading-safety and accessibility majors from review
- Transcript: drag suspends auto-follow; long-press selection suspends following; Latest is a trailing outlined capsule that re-enables follow - Lightwell: pulse via isolated TimelineView (2.4s triangle wave), pauses when offscreen; Reduce Motion uses static geometry (no level resizing) - Increase Contrast: solid Rosewood lightwell fill, 2pt Rosewood selected outlines on mode buttons - Haptics: success only on entering ready, error only on entering failure
This commit is contained in:
1 parent
f043d6b078
commit
afe37ad5f4
3 files changed
+121
-55
No files matched your search
@@ -7,9 +7,15 @@ struct TranscriptListView: View {
|
||||
var onClear: () -> Void
|
||||
@State private var confirmClear = false
|
||||
@State private var followingLatest = true
|
||||
@State private var selectionActive = false
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
@Environment(\.dynamicTypeSize) private var typeSize
|
||||
|
||||
/// Auto-follow eligibility: never scroll while the user is selecting
|
||||
/// text or reading history.
|
||||
private var isFollowingEligible: Bool {
|
||||
followingLatest && !selectionActive
|
||||
}
|
||||
private static let deliveryMarkers = #"\[(?:appreciative|calm|concerned|curious|encouraging|excited|happy|joking|quiet|reassuring|reflective|sad|serious|surprised|sympathetic|urgent|whispering|laughter|sighing|breathing|neutral)\]\s*"#
|
||||
|
||||
var body: some View {
|
||||
@@ -39,8 +45,6 @@ struct TranscriptListView: View {
|
||||
}
|
||||
ForEach(events) { event in transcriptRow(event) }
|
||||
Color.clear.frame(height: 1).id("latest")
|
||||
.onAppear { followingLatest = true }
|
||||
.onDisappear { followingLatest = false }
|
||||
}
|
||||
.padding(.horizontal, Nearlight.s20)
|
||||
.padding(.top, Nearlight.s16)
|
||||
@@ -49,23 +53,43 @@ struct TranscriptListView: View {
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.defaultScrollAnchor(.top)
|
||||
.simultaneousGesture(DragGesture().onChanged { _ in
|
||||
// Any manual drag suspends automatic following.
|
||||
followingLatest = false
|
||||
})
|
||||
.onChange(of: events.last?.text) { _, _ in
|
||||
if followingLatest { proxy.scrollTo("latest", anchor: .bottom) }
|
||||
guard isFollowingEligible else { return }
|
||||
proxy.scrollTo("latest", anchor: .bottom)
|
||||
}
|
||||
.onChange(of: events.count) { _, _ in
|
||||
if followingLatest { proxy.scrollTo("latest", anchor: .bottom) }
|
||||
guard isFollowingEligible else { return }
|
||||
proxy.scrollTo("latest", anchor: .bottom)
|
||||
}
|
||||
.onChange(of: selectionActive) { _, active in
|
||||
// Beginning text selection suspends auto-follow so the
|
||||
// user's reading position is never disturbed.
|
||||
if active { followingLatest = false }
|
||||
}
|
||||
if !followingLatest && !events.isEmpty {
|
||||
Button {
|
||||
withAnimation(reduceMotion ? nil : Nearlight.standard) {
|
||||
proxy.scrollTo("latest", anchor: .bottom)
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
followingLatest = true
|
||||
withAnimation(reduceMotion ? nil : Nearlight.standard) {
|
||||
proxy.scrollTo("latest", anchor: .bottom)
|
||||
}
|
||||
} label: {
|
||||
Label("Latest", systemImage: "arrow.down")
|
||||
.font(NearlightFont.support(typeSize))
|
||||
.padding(.horizontal, Nearlight.s12)
|
||||
.frame(minHeight: 44)
|
||||
.background(Nearlight.surface, in: Capsule())
|
||||
.overlay(Capsule().strokeBorder(Nearlight.quiet, lineWidth: 1))
|
||||
}
|
||||
} label: {
|
||||
Label("Latest messages", systemImage: "arrow.down")
|
||||
.font(NearlightFont.support(typeSize))
|
||||
.frame(minHeight: 44)
|
||||
.accessibilityIdentifier("talk.latest")
|
||||
}
|
||||
.accessibilityIdentifier("talk.latest")
|
||||
.padding(.horizontal, Nearlight.s12)
|
||||
.padding(.bottom, Nearlight.s12)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,6 +128,11 @@ struct TranscriptListView: View {
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.onLongPressGesture(minimumDuration: 0.05, pressing: { pressing in
|
||||
// Text-selection interaction begins with long-press on the row;
|
||||
// suspend auto-follow while pressing/held.
|
||||
if pressing { selectionActive = true }
|
||||
}, perform: {})
|
||||
.contextMenu {
|
||||
Button("Copy message", systemImage: "doc.on.doc") { UIPasteboard.general.string = text }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user