Add a "load more" button instead of always inserting events in timelines
Changelog-Added: Add a "load more" button instead of always inserting events in timelines
This commit is contained in:
@@ -156,6 +156,9 @@
|
|||||||
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
||||||
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; };
|
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; };
|
||||||
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
||||||
|
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
|
||||||
|
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
||||||
|
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.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 */; };
|
||||||
@@ -468,6 +471,9 @@
|
|||||||
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
|
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
|
||||||
4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; };
|
4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; };
|
||||||
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
||||||
|
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
|
||||||
|
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
|
||||||
|
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerTimelineView.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>"; };
|
||||||
@@ -684,6 +690,7 @@
|
|||||||
4C75EFA227FA576C0006080F /* Views */ = {
|
4C75EFA227FA576C0006080F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4CE0E2B029A3DF4700DB4CA2 /* Timeline */,
|
||||||
4CE879562996C44A00F758CC /* Zaps */,
|
4CE879562996C44A00F758CC /* Zaps */,
|
||||||
4CB9D4A52992D01900A9A7E4 /* Profile */,
|
4CB9D4A52992D01900A9A7E4 /* Profile */,
|
||||||
4CAAD8AE29888A9B00060CEA /* Relays */,
|
4CAAD8AE29888A9B00060CEA /* Relays */,
|
||||||
@@ -796,6 +803,7 @@
|
|||||||
3AB72AB8298ECF30004BB58C /* Translator.swift */,
|
3AB72AB8298ECF30004BB58C /* Translator.swift */,
|
||||||
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */,
|
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */,
|
||||||
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
||||||
|
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -858,6 +866,15 @@
|
|||||||
path = Events;
|
path = Events;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4CE0E2B029A3DF4700DB4CA2 /* Timeline */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */,
|
||||||
|
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */,
|
||||||
|
);
|
||||||
|
path = Timeline;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4CE4F9DF285287A000C00DD9 /* Components */ = {
|
4CE4F9DF285287A000C00DD9 /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1231,6 +1248,8 @@
|
|||||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
||||||
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
||||||
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
||||||
|
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
|
||||||
|
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */,
|
||||||
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
||||||
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
||||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||||
@@ -1298,6 +1317,7 @@
|
|||||||
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */,
|
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */,
|
||||||
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
|
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
|
||||||
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
|
||||||
|
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
|
||||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
||||||
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
|
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
|
||||||
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ struct ContentView: View {
|
|||||||
@State var filter_state : FilterState = .posts_and_replies
|
@State var filter_state : FilterState = .posts_and_replies
|
||||||
@State private var isSideBarOpened = false
|
@State private var isSideBarOpened = false
|
||||||
@StateObject var home: HomeModel = HomeModel()
|
@StateObject var home: HomeModel = HomeModel()
|
||||||
|
|
||||||
// connect retry timer
|
// connect retry timer
|
||||||
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ struct ContentView: View {
|
|||||||
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
|
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if let damus = self.damus_state {
|
if let damus = self.damus_state {
|
||||||
TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
|
TimelineView(events: home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,7 @@ struct ContentView: View {
|
|||||||
case .notifications:
|
case .notifications:
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Divider()
|
Divider()
|
||||||
TimelineView(events: $home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true })
|
TimelineView(events: home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true })
|
||||||
}
|
}
|
||||||
case .dms:
|
case .dms:
|
||||||
DirectMessagesView(damus_state: damus_state!)
|
DirectMessagesView(damus_state: damus_state!)
|
||||||
|
|||||||
@@ -50,19 +50,22 @@ class HomeModel: ObservableObject {
|
|||||||
let profiles_subid = UUID().description
|
let profiles_subid = UUID().description
|
||||||
|
|
||||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||||
@Published var notifications: [NostrEvent] = []
|
@Published var notifications: EventHolder
|
||||||
@Published var dms: DirectMessagesModel
|
@Published var dms: DirectMessagesModel
|
||||||
@Published var events: [NostrEvent] = []
|
@Published var events: EventHolder
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
@Published var signal: SignalModel = SignalModel()
|
@Published var signal: SignalModel = SignalModel()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.events = EventHolder()
|
||||||
|
self.notifications = EventHolder()
|
||||||
self.damus_state = DamusState.empty
|
self.damus_state = DamusState.empty
|
||||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
self.dms = DirectMessagesModel(our_pubkey: "")
|
||||||
self.setup_debouncer()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init(damus_state: DamusState) {
|
init(damus_state: DamusState) {
|
||||||
|
self.events = EventHolder()
|
||||||
|
self.notifications = EventHolder()
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
||||||
self.setup_debouncer()
|
self.setup_debouncer()
|
||||||
@@ -140,7 +143,7 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !insert_uniq_sorted_event(events: ¬ifications, new_ev: ev, cmp: { $0.created_at > $1.created_at }) {
|
if !notifications.insert(ev) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,9 +195,9 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func filter_muted() {
|
func filter_muted() {
|
||||||
self.events = events.filter { !damus_state.contacts.is_muted($0.pubkey) }
|
events.filter { !damus_state.contacts.is_muted($0.pubkey) }
|
||||||
self.dms.dms = dms.dms.filter { !damus_state.contacts.is_muted($0.0) }
|
self.dms.dms = dms.dms.filter { !damus_state.contacts.is_muted($0.0) }
|
||||||
self.notifications = notifications.filter { !damus_state.contacts.is_muted($0.pubkey) }
|
notifications.filter { !damus_state.contacts.is_muted($0.pubkey) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_delete_event(_ ev: NostrEvent) {
|
func handle_delete_event(_ ev: NostrEvent) {
|
||||||
@@ -319,7 +322,7 @@ class HomeModel: ObservableObject {
|
|||||||
dms.append(contentsOf: incoming_dms)
|
dms.append(contentsOf: incoming_dms)
|
||||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: dms, damus_state: damus_state)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: dms, damus_state: damus_state)
|
||||||
} else if sub_id == notifications_subid {
|
} else if sub_id == notifications_subid {
|
||||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: notifications, damus_state: damus_state)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: notifications.all_events, damus_state: damus_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.loading = false
|
self.loading = false
|
||||||
@@ -458,10 +461,10 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !insert_uniq_sorted_event(events: ¬ifications, new_ev: ev, cmp: { $0.created_at > $1.created_at }) {
|
if !notifications.insert(ev) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_last_event(ev: ev, timeline: .notifications)
|
handle_last_event(ev: ev, timeline: .notifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,8 +475,7 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func insert_home_event(_ ev: NostrEvent) {
|
func insert_home_event(_ ev: NostrEvent) {
|
||||||
let ok = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at })
|
if events.insert(ev) {
|
||||||
if ok {
|
|
||||||
handle_last_event(ev: ev, timeline: .home)
|
handle_last_event(ev: ev, timeline: .home)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ProfileModel: ObservableObject, Equatable {
|
class ProfileModel: ObservableObject, Equatable {
|
||||||
@Published var events: [NostrEvent] = []
|
var events: EventHolder = EventHolder()
|
||||||
@Published var contacts: NostrEvent? = nil
|
@Published var contacts: NostrEvent? = nil
|
||||||
@Published var following: Int = 0
|
@Published var following: Int = 0
|
||||||
@Published var relays: [String: RelayInfo]? = nil
|
@Published var relays: [String: RelayInfo]? = nil
|
||||||
@@ -111,7 +111,9 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ev.is_textlike || ev.known_kind == .boost {
|
if ev.is_textlike || ev.known_kind == .boost {
|
||||||
insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at})
|
if self.events.insert(ev) {
|
||||||
|
self.objectWillChange.send()
|
||||||
|
}
|
||||||
} else if ev.known_kind == .contacts {
|
} else if ev.known_kind == .contacts {
|
||||||
handle_profile_contact_event(ev)
|
handle_profile_contact_event(ev)
|
||||||
} else if ev.known_kind == .metadata {
|
} else if ev.known_kind == .metadata {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
/// The data model for the SearchHome view, typically something global-like
|
/// The data model for the SearchHome view, typically something global-like
|
||||||
class SearchHomeModel: ObservableObject {
|
class SearchHomeModel: ObservableObject {
|
||||||
@Published var events: [NostrEvent] = []
|
var events: EventHolder = EventHolder()
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
var seen_pubkey: Set<String> = Set()
|
var seen_pubkey: Set<String> = Set()
|
||||||
@@ -31,7 +31,8 @@ class SearchHomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func filter_muted() {
|
func filter_muted() {
|
||||||
events = events.filter { should_show_event(contacts: damus_state.contacts, ev: $0) }
|
events.filter { should_show_event(contacts: damus_state.contacts, ev: $0) }
|
||||||
|
self.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
@@ -61,8 +62,8 @@ class SearchHomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
seen_pubkey.insert(ev.pubkey)
|
seen_pubkey.insert(ev.pubkey)
|
||||||
|
|
||||||
insert_uniq_sorted_event(events: &events, new_ev: ev) {
|
if self.events.insert(ev) {
|
||||||
$0.created_at > $1.created_at
|
self.objectWillChange.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .notice(let msg):
|
case .notice(let msg):
|
||||||
@@ -75,7 +76,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
// global events are not realtime
|
// global events are not realtime
|
||||||
unsubscribe(to: relay_id)
|
unsubscribe(to: relay_id)
|
||||||
|
|
||||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events, damus_state: damus_state)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events.all_events, damus_state: damus_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
class SearchModel: ObservableObject {
|
class SearchModel: ObservableObject {
|
||||||
@Published var events: [NostrEvent] = []
|
var events: EventHolder = EventHolder()
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
@Published var channel_name: String? = nil
|
@Published var channel_name: String? = nil
|
||||||
|
|
||||||
@@ -26,7 +26,8 @@ class SearchModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func filter_muted() {
|
func filter_muted() {
|
||||||
self.events = self.events.filter { should_show_event(contacts: contacts, ev: $0) }
|
self.events.filter { should_show_event(contacts: contacts, ev: $0) }
|
||||||
|
self.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
@@ -57,7 +58,7 @@ class SearchModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at } ) {
|
if self.events.insert(ev) {
|
||||||
objectWillChange.send()
|
objectWillChange.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
95
damus/Util/EventHolder.swift
Normal file
95
damus/Util/EventHolder.swift
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
//
|
||||||
|
// EventHolder.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Used for holding back events until they're ready to be displayed
|
||||||
|
class EventHolder: ObservableObject {
|
||||||
|
private var has_event: Set<String>
|
||||||
|
@Published var events: [NostrEvent]
|
||||||
|
@Published var incoming: [NostrEvent]
|
||||||
|
@Published var should_queue: Bool
|
||||||
|
|
||||||
|
var queued: Int {
|
||||||
|
return incoming.count
|
||||||
|
}
|
||||||
|
|
||||||
|
var has_incoming: Bool {
|
||||||
|
return queued > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var all_events: [NostrEvent] {
|
||||||
|
events + incoming
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.should_queue = false
|
||||||
|
self.events = []
|
||||||
|
self.incoming = []
|
||||||
|
self.has_event = Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(events: [NostrEvent], incoming: [NostrEvent]) {
|
||||||
|
self.should_queue = false
|
||||||
|
self.events = events
|
||||||
|
self.incoming = incoming
|
||||||
|
self.has_event = Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
func filter(_ isIncluded: (NostrEvent) -> Bool) {
|
||||||
|
self.events = self.events.filter(isIncluded)
|
||||||
|
self.incoming = self.incoming.filter(isIncluded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert(_ ev: NostrEvent) -> Bool {
|
||||||
|
if should_queue {
|
||||||
|
return insert_queued(ev)
|
||||||
|
} else {
|
||||||
|
return insert_immediate(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func insert_immediate(_ ev: NostrEvent) -> Bool {
|
||||||
|
if has_event.contains(ev.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
has_event.insert(ev.id)
|
||||||
|
|
||||||
|
if insert_uniq_sorted_event_created(events: &self.events, new_ev: ev) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func insert_queued(_ ev: NostrEvent) -> Bool {
|
||||||
|
if has_event.contains(ev.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
has_event.insert(ev.id)
|
||||||
|
|
||||||
|
incoming.append(ev)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func flush() {
|
||||||
|
var changed = false
|
||||||
|
for event in incoming {
|
||||||
|
if insert_uniq_sorted_event_created(events: &events, new_ev: event) {
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
self.objectWillChange.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.incoming = []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,6 +59,13 @@ func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func insert_uniq_sorted_event_created(events: inout [NostrEvent], new_ev: NostrEvent) -> Bool {
|
||||||
|
return insert_uniq_sorted_event(events: &events, new_ev: new_ev) {
|
||||||
|
$0.created_at > $1.created_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func insert_uniq_sorted_event(events: inout [NostrEvent], new_ev: NostrEvent, cmp: (NostrEvent, NostrEvent) -> Bool) -> Bool {
|
func insert_uniq_sorted_event(events: inout [NostrEvent], new_ev: NostrEvent, cmp: (NostrEvent, NostrEvent) -> Bool) -> Bool {
|
||||||
var i: Int = 0
|
var i: Int = 0
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ struct ChatView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ref_id = thread.replies.lookup(event.id) {
|
if let _ = thread.replies.lookup(event.id) {
|
||||||
if !is_reply_to_prev() {
|
if !is_reply_to_prev() {
|
||||||
/*
|
/*
|
||||||
ReplyQuoteView(keypair: damus_state.keypair, quoter: event, event_id: ref_id, profiles: damus_state.profiles, previews: damus_state.previews)
|
ReplyQuoteView(keypair: damus_state.keypair, quoter: event, event_id: ref_id, profiles: damus_state.profiles, previews: damus_state.previews)
|
||||||
|
|||||||
@@ -404,10 +404,10 @@ struct ProfileView: View {
|
|||||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||||
|
|
||||||
if filter_state == FilterState.posts {
|
if filter_state == FilterState.posts {
|
||||||
InnerTimelineView(events: $profile.events, damus: damus_state, show_friend_icon: false, filter: FilterState.posts.filter)
|
InnerTimelineView(events: profile.events, damus: damus_state, show_friend_icon: false, filter: FilterState.posts.filter)
|
||||||
}
|
}
|
||||||
if filter_state == FilterState.posts_and_replies {
|
if filter_state == FilterState.posts_and_replies {
|
||||||
InnerTimelineView(events: $profile.events, damus: damus_state, show_friend_icon: false, filter: FilterState.posts_and_replies.filter)
|
InnerTimelineView(events: profile.events, damus: damus_state, show_friend_icon: false, filter: FilterState.posts_and_replies.filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, Theme.safeAreaInsets?.left)
|
.padding(.horizontal, Theme.safeAreaInsets?.left)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ struct SearchHomeView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var GlobalContent: some View {
|
var GlobalContent: some View {
|
||||||
return TimelineView(events: $model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: { _ in true })
|
return TimelineView(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: { _ in true })
|
||||||
.refreshable {
|
.refreshable {
|
||||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
// Fetch new information by unsubscribing and resubscribing to the relay
|
||||||
model.unsubscribe()
|
model.unsubscribe()
|
||||||
@@ -90,7 +90,7 @@ struct SearchHomeView: View {
|
|||||||
self.model.filter_muted()
|
self.model.filter_muted()
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if model.events.isEmpty {
|
if model.events.events.isEmpty {
|
||||||
model.subscribe()
|
model.subscribe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ struct SearchView: View {
|
|||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TimelineView(events: $search.events, loading: $search.loading, damus: appstate, show_friend_icon: true, filter: { _ in true })
|
TimelineView(events: search.events, loading: $search.loading, damus: appstate, show_friend_icon: true, filter: { _ in true })
|
||||||
.navigationBarTitle(describe_search(search.search))
|
.navigationBarTitle(describe_search(search.search))
|
||||||
.onReceive(handle_notify(.switched_timeline)) { obj in
|
.onReceive(handle_notify(.switched_timeline)) { obj in
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|||||||
59
damus/Views/Timeline/InnerTimelineView.swift
Normal file
59
damus/Views/Timeline/InnerTimelineView.swift
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// InnerTimelineView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
|
struct InnerTimelineView: View {
|
||||||
|
@ObservedObject var events: EventHolder
|
||||||
|
let damus: DamusState
|
||||||
|
let show_friend_icon: Bool
|
||||||
|
let filter: (NostrEvent) -> Bool
|
||||||
|
@State var nav_target: NostrEvent? = nil
|
||||||
|
@State var navigating: Bool = false
|
||||||
|
|
||||||
|
var MaybeBuildThreadView: some View {
|
||||||
|
Group {
|
||||||
|
if let ev = nav_target {
|
||||||
|
BuildThreadV2View(damus: damus, event_id: (ev.inner_event ?? ev).id)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationLink(destination: MaybeBuildThreadView, isActive: $navigating) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
LazyVStack(spacing: 0) {
|
||||||
|
let events = self.events.events
|
||||||
|
if events.isEmpty {
|
||||||
|
EmptyTimelineView()
|
||||||
|
} else {
|
||||||
|
ForEach(events.filter(filter), id: \.id) { (ev: NostrEvent) in
|
||||||
|
EventView(damus: damus, event: ev, has_action_bar: true)
|
||||||
|
.onTapGesture {
|
||||||
|
nav_target = ev
|
||||||
|
navigating = true
|
||||||
|
}
|
||||||
|
.padding(.top, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct InnerTimelineView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
InnerTimelineView(events: test_event_holder, damus: test_damus_state(), show_friend_icon: true, filter: { _ in true }, nav_target: nil, navigating: false)
|
||||||
|
.frame(width: 300, height: 500)
|
||||||
|
.border(Color.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
50
damus/Views/Timeline/LoadMoreButton.swift
Normal file
50
damus/Views/Timeline/LoadMoreButton.swift
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// LoadMoreButton.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-02-20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LoadMoreButton: View {
|
||||||
|
@ObservedObject var events: EventHolder
|
||||||
|
let scroller: ScrollViewProxy?
|
||||||
|
|
||||||
|
func click() {
|
||||||
|
events.flush()
|
||||||
|
guard let ev = events.events.first, let scroller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scroll_to_event(scroller: scroller, id: ev.id, delay: 0.1, animate: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if events.queued > 0 {
|
||||||
|
Button(action: click) {
|
||||||
|
Text("Load \(events.queued) more")
|
||||||
|
}
|
||||||
|
.font(.system(size: 14, weight: .bold))
|
||||||
|
.padding(10)
|
||||||
|
.frame(height: 30)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.background(LINEAR_GRADIENT)
|
||||||
|
.clipShape(Capsule())
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoadMoreButton_Previews: PreviewProvider {
|
||||||
|
@StateObject static var events: EventHolder = test_event_holder
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
LoadMoreButton(events: events, scroller: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let test_event_holder = EventHolder(events: [], incoming: [test_event])
|
||||||
@@ -12,50 +12,12 @@ enum TimelineAction {
|
|||||||
case navigating
|
case navigating
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InnerTimelineView: View {
|
|
||||||
@Binding var events: [NostrEvent]
|
|
||||||
let damus: DamusState
|
|
||||||
let show_friend_icon: Bool
|
|
||||||
let filter: (NostrEvent) -> Bool
|
|
||||||
@State var nav_target: NostrEvent? = nil
|
|
||||||
@State var navigating: Bool = false
|
|
||||||
|
|
||||||
var MaybeBuildThreadView: some View {
|
|
||||||
Group {
|
|
||||||
if let ev = nav_target {
|
|
||||||
BuildThreadV2View(damus: damus, event_id: (ev.inner_event ?? ev).id)
|
|
||||||
} else {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationLink(destination: MaybeBuildThreadView, isActive: $navigating) {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
LazyVStack(spacing: 0) {
|
|
||||||
if events.isEmpty {
|
|
||||||
EmptyTimelineView()
|
|
||||||
} else {
|
|
||||||
ForEach(events.filter(filter), id: \.id) { (ev: NostrEvent) in
|
|
||||||
EventView(damus: damus, event: ev, has_action_bar: true)
|
|
||||||
.onTapGesture {
|
|
||||||
nav_target = ev
|
|
||||||
navigating = true
|
|
||||||
}
|
|
||||||
.padding(.top, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimelineView: View {
|
struct TimelineView: View {
|
||||||
|
@ObservedObject var events: EventHolder
|
||||||
@Binding var events: [NostrEvent]
|
|
||||||
@Binding var loading: Bool
|
@Binding var loading: Bool
|
||||||
|
@State var offset = CGFloat.zero
|
||||||
|
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
let show_friend_icon: Bool
|
let show_friend_icon: Bool
|
||||||
@@ -65,37 +27,66 @@ struct TimelineView: View {
|
|||||||
MainContent
|
MainContent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handle_scroll(_ proxy: GeometryProxy) {
|
||||||
|
let offset = -proxy.frame(in: .named("scroll")).origin.y
|
||||||
|
guard offset != -0.0 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.events.should_queue = offset > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var realtime_bar_opacity: Double {
|
||||||
|
colorScheme == .dark ? 0.2 : 0.1
|
||||||
|
}
|
||||||
|
|
||||||
var MainContent: some View {
|
var MainContent: some View {
|
||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
ScrollView {
|
ZStack {
|
||||||
InnerTimelineView(events: loading ? .constant(Constants.EXAMPLE_EVENTS) : $events, damus: damus, show_friend_icon: show_friend_icon, filter: loading ? { _ in true } : filter)
|
VStack {
|
||||||
.redacted(reason: loading ? .placeholder : [])
|
LoadMoreButton(events: events, scroller: scroller)
|
||||||
.shimmer(loading)
|
.padding([.top], 10)
|
||||||
.disabled(loading)
|
Spacer()
|
||||||
}
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .scroll_to_top)) { _ in
|
.zIndex(10.0)
|
||||||
guard let event = events.filter(self.filter).first else {
|
|
||||||
return
|
ScrollView {
|
||||||
|
InnerTimelineView(events: events, damus: damus, show_friend_icon: show_friend_icon, filter: loading ? { _ in true } : filter)
|
||||||
|
.redacted(reason: loading ? .placeholder : [])
|
||||||
|
.shimmer(loading)
|
||||||
|
.disabled(loading)
|
||||||
|
.background(GeometryReader { proxy -> Color in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
handle_scroll(proxy)
|
||||||
|
}
|
||||||
|
return Color.clear
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.overlay(
|
||||||
|
Rectangle()
|
||||||
|
.fill(RECTANGLE_GRADIENT.opacity(realtime_bar_opacity))
|
||||||
|
.offset(y: -1)
|
||||||
|
.frame(height: events.should_queue ? 0 : 8)
|
||||||
|
,
|
||||||
|
alignment: .top
|
||||||
|
)
|
||||||
|
.buttonStyle(BorderlessButtonStyle())
|
||||||
|
.coordinateSpace(name: "scroll")
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: .scroll_to_top)) { _ in
|
||||||
|
guard let event = events.events.filter(self.filter).first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scroll_to_event(scroller: scroller, id: event.id, delay: 0.0, animate: true, anchor: .top)
|
||||||
}
|
}
|
||||||
scroll_to_event(scroller: scroller, id: event.id, delay: 0.0, animate: true, anchor: .top)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimelineView_Previews: PreviewProvider {
|
struct TimelineView_Previews: PreviewProvider {
|
||||||
|
@StateObject static var events = test_event_holder
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
TimelineView(events: .constant(Constants.EXAMPLE_EVENTS), loading: .constant(true), damus: Constants.EXAMPLE_DEMOS, show_friend_icon: true, filter: { _ in true })
|
TimelineView(events: events, loading: .constant(true), damus: Constants.EXAMPLE_DEMOS, show_friend_icon: true, filter: { _ in true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct NavigationLazyView<Content: View>: View {
|
|
||||||
let build: () -> Content
|
|
||||||
init(_ build: @autoclosure @escaping () -> Content) {
|
|
||||||
self.build = build
|
|
||||||
}
|
|
||||||
var body: Content {
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user