scroll: more consistent scrolling behavior
Scrolling will always anchor the active note to the top of the screen This is less confusing overall Changelog-Changed: Clicking on a note will now always scroll it to the top Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -84,6 +84,7 @@
|
|||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
||||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
|
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
|
||||||
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
|
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
|
||||||
|
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
||||||
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
|
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
|
||||||
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
|
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
|
||||||
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */; };
|
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */; };
|
||||||
@@ -202,6 +203,7 @@
|
|||||||
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
||||||
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
||||||
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
|
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
|
||||||
|
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
||||||
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
||||||
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
|
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
|
||||||
4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = "<group>"; };
|
4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = "<group>"; };
|
||||||
@@ -370,6 +372,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */,
|
4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */,
|
||||||
|
4CD7641A28A1641400B6928F /* EndBlock.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -646,6 +649,7 @@
|
|||||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
||||||
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
||||||
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
|
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
|
||||||
|
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */,
|
||||||
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
|
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
|
||||||
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
|
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
|
||||||
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
||||||
|
|||||||
32
damus/Components/EndBlock.swift
Normal file
32
damus/Components/EndBlock.swift
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// EndBlock.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-08-08.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct EndBlock: View {
|
||||||
|
let height: CGFloat
|
||||||
|
|
||||||
|
init () {
|
||||||
|
self.height = 80.0
|
||||||
|
}
|
||||||
|
|
||||||
|
init (height: Float) {
|
||||||
|
self.height = CGFloat(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Color.white.opacity(0)
|
||||||
|
.id("endblock")
|
||||||
|
.frame(height: height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EndBlock_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
EndBlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -189,7 +189,9 @@ class ThreadModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if done {
|
if done {
|
||||||
loading = false
|
if (events.contains { ev in ev.id == initial_event.id }) {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct ChatroomView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
ScrollView {
|
ScrollView(.vertical) {
|
||||||
LazyVStack(alignment: .leading) {
|
LazyVStack(alignment: .leading) {
|
||||||
let count = thread.events.count
|
let count = thread.events.count
|
||||||
ForEach(Array(zip(thread.events, thread.events.indices)), id: \.0.id) { (ev, ind) in
|
ForEach(Array(zip(thread.events, thread.events.indices)), id: \.0.id) { (ev, ind) in
|
||||||
@@ -34,17 +34,20 @@ struct ChatroomView: View {
|
|||||||
}
|
}
|
||||||
.environmentObject(thread)
|
.environmentObject(thread)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EndBlock()
|
||||||
}
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .select_quote)) { notif in
|
.onReceive(NotificationCenter.default.publisher(for: .select_quote)) { notif in
|
||||||
let ev = notif.object as! NostrEvent
|
let ev = notif.object as! NostrEvent
|
||||||
if ev.id != thread.initial_event.id {
|
if ev.id != thread.initial_event.id {
|
||||||
thread.set_active_event(ev, privkey: damus.keypair.privkey)
|
thread.set_active_event(ev, privkey: damus.keypair.privkey)
|
||||||
}
|
}
|
||||||
scroll_to_event(scroller: scroller, id: ev.id, delay: 0, animate: true, anchor: .top)
|
scroll_to_event(scroller: scroller, id: ev.id, delay: 0, animate: true)
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
scroll_to_event(scroller: scroller, id: thread.initial_event.id, delay: 0.3, animate: false, anchor: .bottom)
|
scroll_to_event(scroller: scroller, id: thread.initial_event.id, delay: 0.1, animate: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ struct DMChatView: View {
|
|||||||
DMView(event: dms.events[ind], damus_state: damus_state)
|
DMView(event: dms.events[ind], damus_state: damus_state)
|
||||||
.event_context_menu(ev, privkey: damus_state.keypair.privkey)
|
.event_context_menu(ev, privkey: damus_state.keypair.privkey)
|
||||||
}
|
}
|
||||||
Color.white.opacity(0)
|
EndBlock()
|
||||||
.id("endblock")
|
|
||||||
.frame(height: 80)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ struct EventDetailView: View {
|
|||||||
@StateObject var thread: ThreadModel
|
@StateObject var thread: ThreadModel
|
||||||
@State var collapsed: Bool = true
|
@State var collapsed: Bool = true
|
||||||
|
|
||||||
func toggle_collapse_thread(scroller: ScrollViewProxy, id mid: String?, animate: Bool = true, anchor: UnitPoint = .center) {
|
func toggle_collapse_thread(scroller: ScrollViewProxy, id mid: String?, animate: Bool = true) {
|
||||||
self.collapsed = !self.collapsed
|
self.collapsed = !self.collapsed
|
||||||
if let id = mid {
|
if let id = mid {
|
||||||
if !self.collapsed {
|
if !self.collapsed {
|
||||||
scroll_to_event(scroller: scroller, id: id, delay: 0.1, animate: animate, anchor: anchor)
|
scroll_to_event(scroller: scroller, id: id, delay: 0.1, animate: animate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ struct EventDetailView: View {
|
|||||||
print("uncollapsing section at \(c.start) '\(ev.content.prefix(12))...'")
|
print("uncollapsing section at \(c.start) '\(ev.content.prefix(12))...'")
|
||||||
let start_id = ev.id
|
let start_id = ev.id
|
||||||
|
|
||||||
toggle_collapse_thread(scroller: scroller, id: start_id, animate: false, anchor: .top)
|
toggle_collapse_thread(scroller: scroller, id: start_id, animate: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CollapsedEventView(_ cev: CollapsedEvent, scroller: ScrollViewProxy) -> some View {
|
func CollapsedEventView(_ cev: CollapsedEvent, scroller: ScrollViewProxy) -> some View {
|
||||||
@@ -76,18 +76,17 @@ struct EventDetailView: View {
|
|||||||
thread.set_active_event(ev, privkey: damus.keypair.privkey)
|
thread.set_active_event(ev, privkey: damus.keypair.privkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear() {
|
|
||||||
if highlight.is_main {
|
|
||||||
scroll_to_event(scroller: scroller, id: ev.id, delay: 0.5, animate: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollViewReader { proxy in
|
ScrollViewReader { proxy in
|
||||||
ScrollView {
|
if thread.loading {
|
||||||
|
ProgressView().progressViewStyle(.circular)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView(.vertical) {
|
||||||
let collapsed_events = calculated_collapsed_events(
|
let collapsed_events = calculated_collapsed_events(
|
||||||
privkey: damus.keypair.privkey,
|
privkey: damus.keypair.privkey,
|
||||||
collapsed: self.collapsed,
|
collapsed: self.collapsed,
|
||||||
@@ -97,12 +96,27 @@ struct EventDetailView: View {
|
|||||||
ForEach(collapsed_events, id: \.id) { cev in
|
ForEach(collapsed_events, id: \.id) { cev in
|
||||||
CollapsedEventView(cev, scroller: proxy)
|
CollapsedEventView(cev, scroller: proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EndBlock(height: 600)
|
||||||
|
}
|
||||||
|
.onChange(of: thread.loading) { val in
|
||||||
|
scroll_after_load(proxy)
|
||||||
|
}
|
||||||
|
.onAppear() {
|
||||||
|
scroll_after_load(proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Thread")
|
.navigationBarTitle("Thread")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scroll_after_load(_ proxy: ScrollViewProxy) {
|
||||||
|
if !thread.loading {
|
||||||
|
let id = thread.initial_event.id
|
||||||
|
scroll_to_event(scroller: proxy, id: id, delay: 0.1, animate: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toggle_thread_view() {
|
func toggle_thread_view() {
|
||||||
NotificationCenter.default.post(name: .toggle_thread_view, object: nil)
|
NotificationCenter.default.post(name: .toggle_thread_view, object: nil)
|
||||||
}
|
}
|
||||||
@@ -273,14 +287,14 @@ func print_event(_ ev: NostrEvent) {
|
|||||||
print(ev.description)
|
print(ev.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
func scroll_to_event(scroller: ScrollViewProxy, id: String, delay: Double, animate: Bool, anchor: UnitPoint = .center) {
|
func scroll_to_event(scroller: ScrollViewProxy, id: String, delay: Double, animate: Bool) {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||||
if animate {
|
if animate {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
scroller.scrollTo(id, anchor: anchor)
|
scroller.scrollTo(id, anchor: .top)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scroller.scrollTo(id, anchor: anchor)
|
scroller.scrollTo(id, anchor: .top)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user