From 9ca68efb32247127ed9f524cb6ef232569387700 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sat, 17 Dec 2022 22:00:00 -0700 Subject: [PATCH 01/13] Some layout changes to the profile view --- damus/Views/EventView.swift | 4 +++- damus/Views/ProfileView.swift | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift index e16eaf14..23392eb8 100644 --- a/damus/Views/EventView.swift +++ b/damus/Views/EventView.swift @@ -93,9 +93,11 @@ struct EventView: View { } .buttonStyle(PlainButtonStyle()) TextEvent(inner_ev, pubkey: inner_ev.pubkey) + .padding([.top], 2) } } else { TextEvent(event, pubkey: pubkey) + .padding([.top], 6) } } } @@ -146,7 +148,7 @@ struct EventView: View { .background(event_validity_color(event.validity)) .id(event.id) .frame(maxWidth: .infinity, minHeight: PFP_SIZE) - .padding([.bottom], 4) + .padding([.bottom], 2) .event_context_menu(event, privkey: damus.keypair.privkey) } } diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift index 5194dd37..09be945d 100644 --- a/damus/Views/ProfileView.swift +++ b/damus/Views/ProfileView.swift @@ -55,7 +55,7 @@ struct ProfileNameView: View { if let real_name = profile?.display_name { VStack(alignment: .leading) { Text(real_name) - .font(.title) + .font(.title3.weight(.bold)) ProfileName(pubkey: pubkey, profile: profile, prefix: "@", contacts: contacts, show_friend_confirmed: true) .font(.callout) .foregroundColor(.gray) @@ -103,8 +103,6 @@ struct ProfileView: View { HStack(alignment: .center) { ProfilePicView(pubkey: profile.pubkey, size: PFP_SIZE, highlight: .custom(Color.black, 2), profiles: damus_state.profiles) - - ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts) Spacer() @@ -119,6 +117,9 @@ struct ProfileView: View { FollowButtonView(target: profile.get_follow_target(), follow_state: damus_state.contacts.follow_state(profile.pubkey)) } + ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts) + .padding(.bottom) + KeyView(pubkey: profile.pubkey) .padding(.bottom, 10) .pubkey_context_menu(bech32_pubkey: bech32_pubkey(profile.pubkey) ?? profile.pubkey) @@ -158,6 +159,7 @@ struct ProfileView: View { VStack(alignment: .leading) { ScrollView { TopSection + .padding(.horizontal) Divider() @@ -165,7 +167,6 @@ struct ProfileView: View { } .frame(maxHeight: .infinity, alignment: .topLeading) } - .padding([.leading, .trailing], 6) .frame(maxWidth: .infinity, alignment: .topLeading) .navigationBarTitle("Profile") .onReceive(handle_notify(.switched_timeline)) { _ in From 8c010e864afe347e87c5f96056763fef0a5a6ee8 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sat, 17 Dec 2022 23:28:35 -0700 Subject: [PATCH 02/13] Some more improvements to the two views. Looking better for sure --- damus/Views/FollowButtonView.swift | 15 ++++++++++-- damus/Views/ProfileView.swift | 39 ++++++++++++++++-------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/damus/Views/FollowButtonView.swift b/damus/Views/FollowButtonView.swift index bd765415..ea8280e5 100644 --- a/damus/Views/FollowButtonView.swift +++ b/damus/Views/FollowButtonView.swift @@ -12,10 +12,21 @@ struct FollowButtonView: View { @State var follow_state: FollowState var body: some View { - Button("\(follow_btn_txt(follow_state))") { + Button { follow_state = perform_follow_btn_action(follow_state, target: target) + } label: { + Text(follow_btn_txt(follow_state)) + .padding(.horizontal, 20) + .padding(.vertical, 7) + .font(.caption.weight(.bold)) + .foregroundColor(follow_state == .unfollows ? .white : .black) + .background(follow_state == .unfollows ? .black : .white) + .cornerRadius(20) + .overlay { + RoundedRectangle(cornerRadius: 16) + .stroke(follow_state == .unfollows ? .white : .gray, lineWidth: 1) + } } - .buttonStyle(.bordered) .onReceive(handle_notify(.followed)) { notif in let pk = notif.object as! String if pk != target.pubkey { diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift index 09be945d..7e06393a 100644 --- a/damus/Views/ProfileView.swift +++ b/damus/Views/ProfileView.swift @@ -82,8 +82,10 @@ struct ProfileView: View { Button(action: { UIApplication.shared.open(url) }) { - Label("", systemImage: "bolt.fill") - .foregroundColor(.orange) + Image(systemName: "bolt.circle") + .symbolRenderingMode(.palette) + .foregroundStyle(.black, .gray) + .font(.system(size: 27).weight(.thin)) } } @@ -92,9 +94,11 @@ struct ProfileView: View { let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey) .environmentObject(dm_model) return NavigationLink(destination: dmview) { - Label("", systemImage: "text.bubble") + Image(systemName: "bubble.left.circle") + .symbolRenderingMode(.palette) + .font(.system(size: 29).weight(.thin)) + .foregroundStyle(.black, .gray) } - .buttonStyle(PlainButtonStyle()) } var TopSection: some View { @@ -106,13 +110,14 @@ struct ProfileView: View { Spacer() + KeyView(pubkey: profile.pubkey) + .pubkey_context_menu(bech32_pubkey: bech32_pubkey(profile.pubkey) ?? profile.pubkey) + if let lnuri = data?.lightning_uri { LNButton(lnuri) - .padding([.trailing], 20) } DMButton - .padding([.trailing], 20) FollowButtonView(target: profile.get_follow_target(), follow_state: damus_state.contacts.follow_state(profile.pubkey)) } @@ -120,10 +125,6 @@ struct ProfileView: View { ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts) .padding(.bottom) - KeyView(pubkey: profile.pubkey) - .padding(.bottom, 10) - .pubkey_context_menu(bech32_pubkey: bech32_pubkey(profile.pubkey) ?? profile.pubkey) - Text(data?.about ?? "") Divider() @@ -209,18 +210,20 @@ struct KeyView: View { @Environment(\.colorScheme) var colorScheme + @State private var isCopied = false + var body: some View { let col = id_to_color(pubkey) let bech32 = bech32_pubkey(pubkey) ?? pubkey - let half = bech32.count / 2 - VStack { - Text("\(String(bech32.prefix(half)))") - .foregroundColor(colorScheme == .light ? .black : col) - .font(.footnote.monospaced()) - Text("\(String(bech32.suffix(half)))") - .font(.footnote.monospaced()) - .foregroundColor(colorScheme == .light ? .black : col) + Button { + UIPasteboard.general.string = bech32 + isCopied = true + } label: { + Label(isCopied ? "Copied" : "", systemImage: "key.fill") + .font(isCopied ? .caption : .system(size: 15).weight(.light)) + .symbolRenderingMode(.hierarchical) + .foregroundColor(isCopied ? .gray : col) } } } From 1ef85b5c852462c08b72c8b15b1be485b2feacef Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sat, 17 Dec 2022 23:54:57 -0700 Subject: [PATCH 03/13] Moved the key over across from the name. Almost like a temporary verifier in a way. I think I would also like to organize this file eventually so its a bit easier to find things --- damus/Views/ProfileView.swift | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift index 7e06393a..ca6ebd1b 100644 --- a/damus/Views/ProfileView.swift +++ b/damus/Views/ProfileView.swift @@ -54,14 +54,24 @@ struct ProfileNameView: View { Group { if let real_name = profile?.display_name { VStack(alignment: .leading) { - Text(real_name) - .font(.title3.weight(.bold)) + HStack { + Text(real_name) + .font(.title3.weight(.bold)) + + KeyView(pubkey: pubkey) + .pubkey_context_menu(bech32_pubkey: pubkey) + } ProfileName(pubkey: pubkey, profile: profile, prefix: "@", contacts: contacts, show_friend_confirmed: true) .font(.callout) .foregroundColor(.gray) } } else { - ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true) + HStack { + ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true) + + KeyView(pubkey: pubkey) + .pubkey_context_menu(bech32_pubkey: pubkey) + } } } } @@ -110,9 +120,6 @@ struct ProfileView: View { Spacer() - KeyView(pubkey: profile.pubkey) - .pubkey_context_menu(bech32_pubkey: bech32_pubkey(profile.pubkey) ?? profile.pubkey) - if let lnuri = data?.lightning_uri { LNButton(lnuri) } @@ -169,7 +176,6 @@ struct ProfileView: View { .frame(maxHeight: .infinity, alignment: .topLeading) } .frame(maxWidth: .infinity, alignment: .topLeading) - .navigationBarTitle("Profile") .onReceive(handle_notify(.switched_timeline)) { _ in dismiss() } From fbde055fc6e146457e53c4c1538cda5224420ed6 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sun, 18 Dec 2022 16:37:53 -0700 Subject: [PATCH 04/13] Making some changes to the view model. braking but we will survive --- damus.xcodeproj/project.pbxproj | 25 +++ .../xcshareddata/swiftpm/Package.resolved | 9 + damus/ContentView.swift | 168 ++---------------- damus/DamusViewModel.swift | 155 ++++++++++++++++ damus/Util/Constants.swift | 25 +++ damus/Views/TimelineView.swift | 31 +++- 6 files changed, 255 insertions(+), 158 deletions(-) create mode 100644 damus/DamusViewModel.swift create mode 100644 damus/Util/Constants.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 5b8e3207..1c788c32 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 3169CAEB294FCABA00EE4006 /* Shimmer in Frameworks */ = {isa = PBXBuildFile; productRef = 3169CAEA294FCABA00EE4006 /* Shimmer */; }; + 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; + 3169CAEF294FD4C400EE4006 /* DamusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEE294FD4C400EE4006 /* DamusViewModel.swift */; }; 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; }; 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; }; 4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; }; @@ -146,6 +149,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; }; + 3169CAEE294FD4C400EE4006 /* DamusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusViewModel.swift; sourceTree = ""; }; 4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = ""; }; 4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = ""; }; 4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = ""; }; @@ -307,6 +312,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3169CAEB294FCABA00EE4006 /* Shimmer in Frameworks */, 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */, 4CE6DF1227F7A2B300C66700 /* Starscream in Frameworks */, 4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */, @@ -493,6 +499,7 @@ 4C285C8B28398BC6008A31F1 /* Keys.swift */, 4C90BD19283AA67F008EE7EF /* Bech32.swift */, 4C216F352870A9A700040376 /* InputDismissKeyboard.swift */, + 3169CAEC294FCCFC00EE4006 /* Constants.swift */, ); path = Util; sourceTree = ""; @@ -542,6 +549,7 @@ 4C75EFA72804823E0006080F /* Info.plist */, 4C75EFA227FA576C0006080F /* Views */, 4CE6DEE627F7A08100C66700 /* damusApp.swift */, + 3169CAEE294FD4C400EE4006 /* DamusViewModel.swift */, 4CE6DEE827F7A08100C66700 /* ContentView.swift */, 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */, 4CE6DEEC27F7A08200C66700 /* Preview Content */, @@ -606,6 +614,7 @@ 4CE6DF1127F7A2B300C66700 /* Starscream */, 4C649880286E0EE300EAE2B3 /* secp256k1 */, 4C06670328FC7EC500038D2A /* Kingfisher */, + 3169CAEA294FCABA00EE4006 /* Shimmer */, ); productName = damus; productReference = 4CE6DEE327F7A08100C66700 /* damus.app */; @@ -684,6 +693,7 @@ 4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */, 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */, 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */, + 3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */, ); productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */; projectDirPath = ""; @@ -729,6 +739,7 @@ files = ( 4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */, 4C363A8A28236B57006E126D /* MentionView.swift in Sources */, + 3169CAEF294FD4C400EE4006 /* DamusViewModel.swift in Sources */, 4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */, 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */, 4C216F34286F5ACD00040376 /* DMView.swift in Sources */, @@ -834,6 +845,7 @@ 4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */, 4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */, 4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */, + 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1185,6 +1197,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/joshuajhomann/Shimmer"; + requirement = { + branch = master; + kind = branch; + }; + }; 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/onevcat/Kingfisher"; @@ -1212,6 +1232,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 3169CAEA294FCABA00EE4006 /* Shimmer */ = { + isa = XCSwiftPackageProductDependency; + package = 3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */; + productName = Shimmer; + }; 4C06670328FC7EC500038D2A /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; package = 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */; diff --git a/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 18554801..58468d85 100644 --- a/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/damus.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -17,6 +17,15 @@ "revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9" } }, + { + "identity" : "shimmer", + "kind" : "remoteSourceControl", + "location" : "https://github.com/joshuajhomann/Shimmer", + "state" : { + "branch" : "master", + "revision" : "2fde687b3f1d9c5409c53da095d3686361e41343" + } + }, { "identity" : "starscream", "kind" : "remoteSourceControl", diff --git a/damus/ContentView.swift b/damus/ContentView.swift index ac8a863e..cc976717 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -9,65 +9,9 @@ import SwiftUI import Starscream import Kingfisher -var BOOTSTRAP_RELAYS = [ - "wss://relay.damus.io", - "wss://nostr-relay.wlvs.space", - "wss://nostr.oxtr.dev", -] - -struct TimestampedProfile { - let profile: Profile - let timestamp: Int64 -} - -enum Sheets: Identifiable { - case post - case reply(NostrEvent) - - var id: String { - switch self { - case .post: return "post" - case .reply(let ev): return "reply-" + ev.id - } - } -} - -enum ThreadState { - case event_details - case chatroom -} - -enum FilterState : Int { - case posts_and_replies = 1 - case posts = 0 -} - struct ContentView: View { - let keypair: Keypair - var pubkey: String { - return keypair.pubkey - } - - var privkey: String? { - return keypair.privkey - } - - @State var status: String = "Not connected" - @State var active_sheet: Sheets? = nil - @State var damus_state: DamusState? = nil - @State var selected_timeline: Timeline? = .home - @State var is_thread_open: Bool = false - @State var is_profile_open: Bool = false - @State var event: NostrEvent? = nil - @State var active_profile: String? = nil - @State var active_search: NostrFilter? = nil - @State var active_event_id: String? = nil - @State var profile_open: Bool = false - @State var thread_open: Bool = false - @State var search_open: Bool = false - @State var filter_state : FilterState = .posts_and_replies - @StateObject var home: HomeModel = HomeModel() + @EnvironmentObject var viewModel: DamusViewModel // connect retry timer let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect() @@ -80,9 +24,9 @@ struct ContentView: View { VStack{ ZStack { if let damus = self.damus_state { - TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter_event) + TimelineView(events: viewModel.$home.events, loading: viewModel.$home.loading, damus: damus, show_friend_icon: false, filter: filter_event) } - if privkey != nil { + if viewModel.privkey != nil { PostButtonContainer { self.active_sheet = .post } @@ -103,7 +47,7 @@ struct ContentView: View { var FiltersView: some View { VStack{ - Picker("Filter State", selection: $filter_state) { + Picker("Filter State", selection: $viewModel.filter_state) { Text("Posts").tag(FilterState.posts) Text("Posts & Replies").tag(FilterState.posts_and_replies) } @@ -112,7 +56,7 @@ struct ContentView: View { } func filter_event(_ ev: NostrEvent) -> Bool { - if self.filter_state == .posts { + if viewModel.filter_state == .posts { return !ev.is_reply(nil) } @@ -121,18 +65,18 @@ struct ContentView: View { func MainContent(damus: DamusState) -> some View { VStack { - NavigationLink(destination: MaybeProfileView, isActive: $profile_open) { + NavigationLink(destination: MaybeProfileView, isActive: $viewModel.profile_open) { EmptyView() } - NavigationLink(destination: MaybeThreadView, isActive: $thread_open) { + NavigationLink(destination: MaybeThreadView, isActive: $viewModel.thread_open) { EmptyView() } - NavigationLink(destination: MaybeSearchView, isActive: $search_open) { + NavigationLink(destination: MaybeSearchView, isActive: $viewModel.search_open) { EmptyView() } switch selected_timeline { case .search: - SearchHomeView(damus_state: damus_state!, model: SearchHomeModel(damus_state: damus_state!)) + SearchHomeView(damus_state: viewModel.state!, model: SearchHomeModel(damus_state: damus_state!)) case .home: PostingTimelineView @@ -209,18 +153,16 @@ struct ContentView: View { } ToolbarItem(placement: .navigationBarTrailing) { - HStack(alignment: .center) { - if home.signal.signal != home.signal.max_signal { - Text("\(home.signal.signal)/\(home.signal.max_signal)") - .font(.callout) - .foregroundColor(.gray) - } - NavigationLink(destination: ConfigView(state: damus_state!)) { - Label("", systemImage: "gear") + if #available(iOS 16.0, *) { + Image(systemName: "chart.bar.fill", variableValue: Double(home.signal.signal) / Double(home.signal.max_signal)) + .font(.body.weight(.ultraLight)) + .symbolRenderingMode(.hierarchical) + } else { + // Fallback on earlier versions + } } .buttonStyle(PlainButtonStyle()) - } } } } @@ -359,54 +301,6 @@ struct ContentView: View { self.damus_state?.pool.connect_to_disconnected() } } - - func switch_timeline(_ timeline: Timeline) { - NotificationCenter.default.post(name: .switched_timeline, object: timeline) - - if timeline == self.selected_timeline { - NotificationCenter.default.post(name: .scroll_to_top, object: nil) - return - } - - self.selected_timeline = timeline - //NotificationCenter.default.post(name: .switched_timeline, object: timeline) - //self.selected_timeline = timeline - } - - func add_relay(_ pool: RelayPool, _ relay: String) { - //add_rw_relay(pool, "wss://nostr-pub.wellorder.net") - add_rw_relay(pool, relay) - /* - let profile = Profile(name: relay, about: nil, picture: nil) - let ts = Int64(Date().timeIntervalSince1970) - let tsprofile = TimestampedProfile(profile: profile, timestamp: ts) - damus!.profiles.add(id: relay, profile: tsprofile) - */ - } - - func connect() { - let pool = RelayPool() - - for relay in BOOTSTRAP_RELAYS { - add_relay(pool, relay) - } - - pool.register_handler(sub_id: sub_id, handler: home.handle_event) - - self.damus_state = DamusState(pool: pool, keypair: keypair, - likes: EventCounter(our_pubkey: pubkey), - boosts: EventCounter(our_pubkey: pubkey), - contacts: Contacts(), - tips: TipCounter(our_pubkey: pubkey), - profiles: Profiles(), - dms: home.dms - ) - home.damus_state = self.damus_state! - - pool.connect() - } - - } struct ContentView_Previews: PreviewProvider { @@ -424,31 +318,6 @@ func get_since_time(last_event: NostrEvent?) -> Int64? { return nil } -func ws_nostr_event(relay: String, ev: WebSocketEvent) -> NostrEvent? { - switch ev { - case .binary(let dat): - return NostrEvent(content: "binary data? \(dat.count) bytes", pubkey: relay) - case .cancelled: - return NostrEvent(content: "cancelled", pubkey: relay) - case .connected: - return NostrEvent(content: "connected", pubkey: relay) - case .disconnected: - return NostrEvent(content: "disconnected", pubkey: relay) - case .error(let err): - return NostrEvent(content: "error \(err.debugDescription)", pubkey: relay) - case .text(let txt): - return NostrEvent(content: "text \(txt)", pubkey: relay) - case .pong: - return NostrEvent(content: "pong", pubkey: relay) - case .ping: - return NostrEvent(content: "ping", pubkey: relay) - case .viabilityChanged(let b): - return NostrEvent(content: "viabilityChanged \(b)", pubkey: relay) - case .reconnectSuggested(let b): - return NostrEvent(content: "reconnectSuggested \(b)", pubkey: relay) - } -} - func is_notification(ev: NostrEvent, pubkey: String) -> Bool { if ev.pubkey == pubkey { return false @@ -468,11 +337,6 @@ extension UINavigationController: UIGestureRecognizerDelegate { } } -struct LastNotification { - let id: String - let created_at: Int64 -} - func get_last_event(_ timeline: Timeline) -> LastNotification? { let str = timeline.rawValue let last = UserDefaults.standard.string(forKey: "last_\(str)") diff --git a/damus/DamusViewModel.swift b/damus/DamusViewModel.swift new file mode 100644 index 00000000..ea86ffb0 --- /dev/null +++ b/damus/DamusViewModel.swift @@ -0,0 +1,155 @@ +// +// DamusViewModel.swift +// damus +// +// Created by Sam DuBois on 12/18/22. +// + +import SwiftUI +import Starscream +import Kingfisher + +class DamusViewModel: ObservableObject { + + // MARK: Constants and Variables + + /// User Keypair object + let keypair: Keypair + + var pubkey: String { + return keypair.pubkey + } + + var privkey: String? { + return keypair.privkey + } + + /// Default relays to be used when setting up the user's account. + var BOOTSTRAP_RELAYS = [ + "wss://relay.damus.io", + "wss://nostr-relay.wlvs.space", + "wss://nostr.oxtr.dev", + ] + + @Published var status: String = "Not connected" + @Published var state: DamusState? = nil + @Published var active_sheet: Sheets? = nil + @Published var selected_timeline: Timeline? = .home + @Published var is_thread_open: Bool = false + @Published var is_profile_open: Bool = false + @Published var event: NostrEvent? = nil + @Published var active_profile: String? = nil + @Published var active_search: String? = nil + @Published var active_event_id: String? = nil + @Published var profile_open: Bool = false + @Published var thread_open: Bool = false + @Published var search_open: Bool = false + @Published var filter_state: FilterState = .posts_and_replies + @Published var home: HomeModel = HomeModel() + + // MARK: Functionality + + func switch_timeline(_ timeline: Timeline) { + NotificationCenter.default.post(name: .switched_timeline, object: timeline) + + if timeline == self.selected_timeline { + NotificationCenter.default.post(name: .scroll_to_top, object: nil) + return + } + + self.selected_timeline = timeline + //NotificationCenter.default.post(name: .switched_timeline, object: timeline) + //self.selected_timeline = timeline + } + + func add_relay(_ pool: RelayPool, _ relay: String) { + //add_rw_relay(pool, "wss://nostr-pub.wellorder.net") + add_rw_relay(pool, relay) + /* + let profile = Profile(name: relay, about: nil, picture: nil) + let ts = Int64(Date().timeIntervalSince1970) + let tsprofile = TimestampedProfile(profile: profile, timestamp: ts) + damus!.profiles.add(id: relay, profile: tsprofile) + */ + } + + func connect() { + let pool = RelayPool() + + for relay in BOOTSTRAP_RELAYS { + add_relay(pool, relay) + } + + pool.register_handler(sub_id: sub_id, handler: home.handle_event) + + self.state = DamusState(pool: pool, keypair: keypair, + likes: EventCounter(our_pubkey: pubkey), + boosts: EventCounter(our_pubkey: pubkey), + contacts: Contacts(), + tips: TipCounter(our_pubkey: pubkey), + profiles: Profiles(), + dms: home.dms + ) + home.damus_state = self.state! + + pool.connect() + } + +} + +struct TimestampedProfile { + let profile: Profile + let timestamp: Int64 +} + +enum Sheets: Identifiable { + case post + case reply(NostrEvent) + + var id: String { + switch self { + case .post: return "post" + case .reply(let ev): return "reply-" + ev.id + } + } +} + +enum ThreadState { + case event_details + case chatroom +} + +enum FilterState : Int { + case posts_and_replies = 1 + case posts = 0 +} + +func ws_nostr_event(relay: String, ev: WebSocketEvent) -> NostrEvent? { + switch ev { + case .binary(let dat): + return NostrEvent(content: "binary data? \(dat.count) bytes", pubkey: relay) + case .cancelled: + return NostrEvent(content: "cancelled", pubkey: relay) + case .connected: + return NostrEvent(content: "connected", pubkey: relay) + case .disconnected: + return NostrEvent(content: "disconnected", pubkey: relay) + case .error(let err): + return NostrEvent(content: "error \(err.debugDescription)", pubkey: relay) + case .text(let txt): + return NostrEvent(content: "text \(txt)", pubkey: relay) + case .pong: + return NostrEvent(content: "pong", pubkey: relay) + case .ping: + return NostrEvent(content: "ping", pubkey: relay) + case .viabilityChanged(let b): + return NostrEvent(content: "viabilityChanged \(b)", pubkey: relay) + case .reconnectSuggested(let b): + return NostrEvent(content: "reconnectSuggested \(b)", pubkey: relay) + } +} + +struct LastNotification { + let id: String + let created_at: Int64 +} diff --git a/damus/Util/Constants.swift b/damus/Util/Constants.swift new file mode 100644 index 00000000..cca0fb44 --- /dev/null +++ b/damus/Util/Constants.swift @@ -0,0 +1,25 @@ +// +// Constants.swift +// damus +// +// Created by Sam DuBois on 12/18/22. +// + +import Foundation + +public class Constants { + + static let PUB_KEY = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681" + + static let EXAMPLE_DEMOS = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: PUB_KEY, privkey: "privkey"), likes: EventCounter(our_pubkey: PUB_KEY), boosts: EventCounter(our_pubkey: PUB_KEY), contacts: Contacts(), tips: TipCounter(our_pubkey: PUB_KEY), profiles: Profiles(), dms: DirectMessagesModel()) + + static let EXAMPLE_EVENTS = [ + NostrEvent(content: "Icecream", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"), + NostrEvent(content: "This is a test for a really long note that somebody sent because they thought they were super cool or maybe they were just really excited to share something with the world.", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"), + NostrEvent(content: "Bonjour Le Monde", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"), + NostrEvent(content: "Why am I helping on this app? Because it's fun!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"), + NostrEvent(content: "PIzza", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"), + NostrEvent(content: "Hello World! This is so cool!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"), + NostrEvent(content: "Nostr - Damus... Haha get it?", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"), + ] +} diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift index d36e33c4..9c5d58eb 100644 --- a/damus/Views/TimelineView.swift +++ b/damus/Views/TimelineView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Shimmer enum TimelineAction { case chillin @@ -33,6 +34,28 @@ struct InnerTimelineView: View { } } .padding(.horizontal) + .refreshable { + <#code#> + } + } +} + +struct InnerTimelineRedactedView: View { + let events: [NostrEvent] + let damus: DamusState + let show_friend_icon: Bool + + var body: some View { + VStack { + ForEach(events, id: \.id) { event in + EventView(event: event, highlight: .none, has_action_bar: true, damus: damus, show_friend_icon: show_friend_icon) + .buttonStyle(PlainButtonStyle()) + } + } + .shimmer() + .redacted(reason: .placeholder) + .padding(.horizontal) + .disabled(true) } } @@ -52,8 +75,7 @@ struct TimelineView: View { ScrollViewReader { scroller in ScrollView { if loading { - ProgressView() - .progressViewStyle(.circular) + InnerTimelineRedactedView(events: Constants.EXAMPLE_EVENTS, damus: damus, show_friend_icon: true) } InnerTimelineView(events: $events, damus: damus, show_friend_icon: show_friend_icon, filter: filter) } @@ -67,13 +89,11 @@ struct TimelineView: View { } } -/* struct TimelineView_Previews: PreviewProvider { static var previews: some View { - TimelineView() + TimelineView(events: .constant(Constants.EXAMPLE_EVENTS), loading: .constant(true), damus: Constants.EXAMPLE_DEMOS, show_friend_icon: true, filter: { _ in true }) } } - */ struct NavigationLazyView: View { @@ -85,4 +105,3 @@ struct NavigationLazyView: View { build() } } - From 5d0e5c32edcf9d322374d3d6755d73533545d689 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sun, 18 Dec 2022 17:01:58 -0700 Subject: [PATCH 05/13] =?UTF-8?q?DONE=20=F0=9F=99=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- damus/ContentView.swift | 107 ++++++++++++++++----------------- damus/DamusViewModel.swift | 25 +++++--- damus/Views/TimelineView.swift | 2 +- damus/damusApp.swift | 3 +- 4 files changed, 72 insertions(+), 65 deletions(-) diff --git a/damus/ContentView.swift b/damus/ContentView.swift index cc976717..912bdbc9 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -15,20 +15,18 @@ struct ContentView: View { // connect retry timer let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect() - - let sub_id = UUID().description @Environment(\.colorScheme) var colorScheme var PostingTimelineView: some View { VStack{ ZStack { - if let damus = self.damus_state { - TimelineView(events: viewModel.$home.events, loading: viewModel.$home.loading, damus: damus, show_friend_icon: false, filter: filter_event) + if let damus = viewModel.state { + TimelineView(events: $viewModel.home.events, loading: $viewModel.home.loading, damus: damus, show_friend_icon: false, filter: filter_event) } if viewModel.privkey != nil { PostButtonContainer { - self.active_sheet = .post + viewModel.active_sheet = .post } } } @@ -74,32 +72,32 @@ struct ContentView: View { NavigationLink(destination: MaybeSearchView, isActive: $viewModel.search_open) { EmptyView() } - switch selected_timeline { + switch viewModel.selected_timeline { case .search: - SearchHomeView(damus_state: viewModel.state!, model: SearchHomeModel(damus_state: damus_state!)) + SearchHomeView(damus_state: viewModel.state!, model: SearchHomeModel(damus_state: viewModel.state!)) case .home: PostingTimelineView case .notifications: - TimelineView(events: $home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true }) + TimelineView(events: $viewModel.home.notifications, loading: $viewModel.home.loading, damus: damus, show_friend_icon: true, filter: { _ in true }) .navigationTitle("Notifications") case .dms: - DirectMessagesView(damus_state: damus_state!) - .environmentObject(home.dms) + DirectMessagesView(damus_state: viewModel.state!) + .environmentObject(viewModel.home.dms) case .none: EmptyView() } } - .navigationBarTitle(selected_timeline == .home ? "Home" : "Global", displayMode: .inline) + .navigationBarTitle(viewModel.selected_timeline == .home ? "Home" : "Global", displayMode: .inline) } var MaybeSearchView: some View { Group { - if let search = self.active_search { - SearchView(appstate: damus_state!, search: SearchModel(pool: damus_state!.pool, search: search)) + if let search = viewModel.active_search { + SearchView(appstate: viewModel.state!, search: SearchModel(pool: viewModel.state!.pool, search: search)) } else { EmptyView() } @@ -108,9 +106,9 @@ struct ContentView: View { var MaybeThreadView: some View { Group { - if let evid = self.active_event_id { - let thread_model = ThreadModel(evid: evid, damus_state: damus_state!) - ThreadView(thread: thread_model, damus: damus_state!, is_chatroom: false) + if let evid = viewModel.active_event_id { + let thread_model = ThreadModel(evid: evid, damus_state: viewModel.state!) + ThreadView(thread: thread_model, damus: viewModel.state!, is_chatroom: false) } else { EmptyView() } @@ -119,10 +117,10 @@ struct ContentView: View { var MaybeProfileView: some View { Group { - if let pk = self.active_profile { - let profile_model = ProfileModel(pubkey: pk, damus: damus_state!) - let followers = FollowersModel(damus_state: damus_state!, target: pk) - ProfileView(damus_state: damus_state!, profile: profile_model, followers: followers) + if let pk = viewModel.active_profile { + let profile_model = ProfileModel(pubkey: pk, damus: viewModel.state!) + let followers = FollowersModel(damus_state: viewModel.state!, target: pk) + ProfileView(damus_state: viewModel.state!, profile: profile_model, followers: followers) } else { EmptyView() } @@ -131,20 +129,20 @@ struct ContentView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - if let damus = self.damus_state { + if let damus = viewModel.state { NavigationView { MainContent(damus: damus) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - let profile_model = ProfileModel(pubkey: damus_state!.pubkey, damus: damus_state!) - let followers_model = FollowersModel(damus_state: damus_state!, target: damus_state!.pubkey) - let prof_dest = ProfileView(damus_state: damus_state!, profile: profile_model, followers: followers_model) + let profile_model = ProfileModel(pubkey: viewModel.state!.pubkey, damus: viewModel.state!) + let followers_model = FollowersModel(damus_state: viewModel.state!, target: viewModel.state!.pubkey) + let prof_dest = ProfileView(damus_state: viewModel.state!, profile: profile_model, followers: followers_model) NavigationLink(destination: prof_dest) { /// Verify that the user has a profile picture, if not display a generic SF Symbol /// (Resolves an in-app error where ``Robohash`` pictures are not generated so the button dissapears - if let picture = damus_state?.profiles.lookup(id: pubkey)?.picture { - ProfilePicView(pubkey: damus_state!.pubkey, size: 32, highlight: .none, profiles: damus_state!.profiles, picture: picture) + if let picture = viewModel.state?.profiles.lookup(id: viewModel.pubkey)?.picture { + ProfilePicView(pubkey: viewModel.state!.pubkey, size: 32, highlight: .none, profiles: viewModel.state!.profiles, picture: picture) } else { Image(systemName: "person.fill") } @@ -153,9 +151,9 @@ struct ContentView: View { } ToolbarItem(placement: .navigationBarTrailing) { - NavigationLink(destination: ConfigView(state: damus_state!)) { + NavigationLink(destination: ConfigView(state: viewModel.state!)) { if #available(iOS 16.0, *) { - Image(systemName: "chart.bar.fill", variableValue: Double(home.signal.signal) / Double(home.signal.max_signal)) + Image(systemName: "chart.bar.fill", variableValue: Double(viewModel.home.signal.signal) / Double(viewModel.home.signal.max_signal)) .font(.body.weight(.ultraLight)) .symbolRenderingMode(.hierarchical) } else { @@ -169,19 +167,19 @@ struct ContentView: View { .navigationViewStyle(.stack) } - TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline) + TabBar(new_events: $viewModel.home.new_events, selected: $viewModel.selected_timeline, action: viewModel.switch_timeline) } .onAppear() { - self.connect() + viewModel.connect() //KingfisherManager.shared.cache.clearDiskCache() setup_notifications() } - .sheet(item: $active_sheet) { item in + .sheet(item: $viewModel.active_sheet) { item in switch item { case .post: PostView(replying_to: nil, references: []) case .reply(let event): - ReplyView(replying_to: event, damus: damus_state!) + ReplyView(replying_to: event, damus: viewModel.state!) } } .onOpenURL { url in @@ -192,28 +190,28 @@ struct ContentView: View { switch link { case .ref(let ref): if ref.key == "p" { - active_profile = ref.ref_id - profile_open = true + viewModel.active_profile = ref.ref_id + viewModel.profile_open = true } else if ref.key == "e" { - active_event_id = ref.ref_id - thread_open = true + viewModel.active_event_id = ref.ref_id + viewModel.thread_open = true } case .filter(let filt): - active_search = filt - search_open = true + viewModel.active_search = filt + viewModel.search_open = true break // TODO: handle filter searches? } } .onReceive(handle_notify(.boost)) { notif in - guard let privkey = self.privkey else { + guard let privkey = viewModel.privkey else { return } let ev = notif.object as! NostrEvent - let boost = make_boost_event(pubkey: pubkey, privkey: privkey, boosted: ev) - self.damus_state?.pool.send(.event(boost)) + let boost = make_boost_event(pubkey: viewModel.pubkey, privkey: privkey, boosted: ev) + viewModel.state?.pool.send(.event(boost)) } .onReceive(handle_notify(.open_thread)) { obj in //let ev = obj.object as! NostrEvent @@ -222,20 +220,20 @@ struct ContentView: View { } .onReceive(handle_notify(.reply)) { notif in let ev = notif.object as! NostrEvent - self.active_sheet = .reply(ev) + viewModel.active_sheet = .reply(ev) } .onReceive(handle_notify(.like)) { like in } .onReceive(handle_notify(.broadcast_event)) { obj in let ev = obj.object as! NostrEvent - self.damus_state?.pool.send(.event(ev)) + viewModel.state?.pool.send(.event(ev)) } .onReceive(handle_notify(.unfollow)) { notif in - guard let privkey = self.privkey else { + guard let privkey = viewModel.privkey else { return } - guard let damus = self.damus_state else { + guard let damus = viewModel.state else { return } @@ -255,12 +253,12 @@ struct ContentView: View { } } .onReceive(handle_notify(.follow)) { notif in - guard let privkey = self.privkey else { + guard let privkey = viewModel.privkey else { return } let fnotify = notif.object as! FollowTarget - guard let damus = self.damus_state else { + guard let damus = viewModel.state else { return } @@ -271,7 +269,7 @@ struct ContentView: View { follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) { notify(.followed, fnotify.pubkey) - damus_state?.contacts.event = ev + viewModel.state?.contacts.event = ev switch fnotify { case .pubkey(let pk): @@ -282,7 +280,7 @@ struct ContentView: View { } } .onReceive(handle_notify(.post)) { obj in - guard let privkey = self.privkey else { + guard let privkey = viewModel.privkey else { return } @@ -290,22 +288,23 @@ struct ContentView: View { switch post_res { case .post(let post): print("post \(post.content)") - let new_ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey) - self.damus_state?.pool.send(.event(new_ev)) + let new_ev = post_to_event(post: post, privkey: privkey, pubkey: viewModel.pubkey) + viewModel.state?.pool.send(.event(new_ev)) case .cancel: - active_sheet = nil + viewModel.active_sheet = nil print("post cancelled") } } .onReceive(timer) { n in - self.damus_state?.pool.connect_to_disconnected() + viewModel.state?.pool.connect_to_disconnected() } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView(keypair: Keypair(pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", privkey: nil)) + ContentView() + .environmentObject(DamusViewModel(with: Keypair(pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", privkey: nil))) } } diff --git a/damus/DamusViewModel.swift b/damus/DamusViewModel.swift index ea86ffb0..5fa7cb1c 100644 --- a/damus/DamusViewModel.swift +++ b/damus/DamusViewModel.swift @@ -9,12 +9,21 @@ import SwiftUI import Starscream import Kingfisher +/// Default relays to be used when setting up the user's account. +var BOOTSTRAP_RELAYS = [ + "wss://relay.damus.io", + "wss://nostr-relay.wlvs.space", + "wss://nostr.oxtr.dev", +] + class DamusViewModel: ObservableObject { // MARK: Constants and Variables + let sub_id = UUID().description + /// User Keypair object - let keypair: Keypair + var keypair: Keypair var pubkey: String { return keypair.pubkey @@ -24,13 +33,6 @@ class DamusViewModel: ObservableObject { return keypair.privkey } - /// Default relays to be used when setting up the user's account. - var BOOTSTRAP_RELAYS = [ - "wss://relay.damus.io", - "wss://nostr-relay.wlvs.space", - "wss://nostr.oxtr.dev", - ] - @Published var status: String = "Not connected" @Published var state: DamusState? = nil @Published var active_sheet: Sheets? = nil @@ -39,7 +41,7 @@ class DamusViewModel: ObservableObject { @Published var is_profile_open: Bool = false @Published var event: NostrEvent? = nil @Published var active_profile: String? = nil - @Published var active_search: String? = nil + @Published var active_search: NostrFilter? = nil @Published var active_event_id: String? = nil @Published var profile_open: Bool = false @Published var thread_open: Bool = false @@ -47,6 +49,11 @@ class DamusViewModel: ObservableObject { @Published var filter_state: FilterState = .posts_and_replies @Published var home: HomeModel = HomeModel() + // MARK: Initializer + init(with key: Keypair) { + self.keypair = key + } + // MARK: Functionality func switch_timeline(_ timeline: Timeline) { diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift index 9c5d58eb..5a25d971 100644 --- a/damus/Views/TimelineView.swift +++ b/damus/Views/TimelineView.swift @@ -35,7 +35,7 @@ struct InnerTimelineView: View { } .padding(.horizontal) .refreshable { - <#code#> + print("Hello World") } } } diff --git a/damus/damusApp.swift b/damus/damusApp.swift index d23a8602..c284eccb 100644 --- a/damus/damusApp.swift +++ b/damus/damusApp.swift @@ -26,7 +26,8 @@ struct MainView: View { var body: some View { Group { if let kp = keypair, !needs_setup { - ContentView(keypair: kp) + ContentView() + .environmentObject(DamusViewModel(with: kp)) } else { SetupView() .onReceive(handle_notify(.login)) { notif in From 2906fecf5ce04029e71a5d1af17cccb462b1feb7 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sun, 18 Dec 2022 18:35:03 -0700 Subject: [PATCH 06/13] Realized that I didn't actually need to move everything out into a view model. Well oh well im sure it will come in handy eventually --- damus/Views/SearchHomeView.swift | 10 +++++++++- damus/Views/TimelineView.swift | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/damus/Views/SearchHomeView.swift b/damus/Views/SearchHomeView.swift index 617da771..0fe98d40 100644 --- a/damus/Views/SearchHomeView.swift +++ b/damus/Views/SearchHomeView.swift @@ -39,11 +39,19 @@ struct SearchHomeView: View { } var GlobalContent: some View { - 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 { + // Fetch new information by resubscribing to the relay + model.subscribe() + } } var SearchContent: some View { SearchResultsView(damus_state: damus_state, search: $search) + .refreshable { + // Fetch new information by resubscribing to the relay + model.subscribe() + } } var MainContent: some View { diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift index 5a25d971..08f1a5c0 100644 --- a/damus/Views/TimelineView.swift +++ b/damus/Views/TimelineView.swift @@ -34,9 +34,6 @@ struct InnerTimelineView: View { } } .padding(.horizontal) - .refreshable { - print("Hello World") - } } } @@ -60,6 +57,9 @@ struct InnerTimelineRedactedView: View { } struct TimelineView: View { + + @EnvironmentObject var viewModel: DamusViewModel + @Binding var events: [NostrEvent] @Binding var loading: Bool From 614e6a7ee8fc9ff05ba11bb2bf52d3266e4840db Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sun, 18 Dec 2022 21:19:53 -0700 Subject: [PATCH 07/13] Ton of changes to layout and resolving issues fixing the color scheme --- damus/ContentView.swift | 18 ++++--- damus/Views/EventActionBar.swift | 6 +-- damus/Views/EventView.swift | 5 +- damus/Views/FollowButtonView.swift | 25 ++++++++-- damus/Views/NoteContentView.swift | 2 + damus/Views/ProfileName.swift | 80 ++++++++++++++++++++++++++++-- damus/Views/ProfileView.swift | 12 +++-- 7 files changed, 123 insertions(+), 25 deletions(-) diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 912bdbc9..04698c6f 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -151,16 +151,18 @@ struct ContentView: View { } ToolbarItem(placement: .navigationBarTrailing) { - NavigationLink(destination: ConfigView(state: viewModel.state!)) { - if #available(iOS 16.0, *) { - Image(systemName: "chart.bar.fill", variableValue: Double(viewModel.home.signal.signal) / Double(viewModel.home.signal.max_signal)) - .font(.body.weight(.ultraLight)) - .symbolRenderingMode(.hierarchical) - } else { - // Fallback on earlier versions - } + HStack(alignment: .center) { + if viewModel.home.signal.signal != viewModel.home.signal.max_signal { + Text("\(viewModel.home.signal.signal)/\(viewModel.home.signal.max_signal)") + .font(.callout) + .foregroundColor(.gray) + } + + NavigationLink(destination: ConfigView(state: viewModel.state!)) { + Image(systemName: "gear") } .buttonStyle(PlainButtonStyle()) + } } } } diff --git a/damus/Views/EventActionBar.swift b/damus/Views/EventActionBar.swift index 7a4ccf35..dc00b6b8 100644 --- a/damus/Views/EventActionBar.swift +++ b/damus/Views/EventActionBar.swift @@ -42,7 +42,7 @@ struct EventActionBar: View { HStack(alignment: .bottom) { Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")") - .font(.footnote) + .font(.footnote.weight(.medium)) .foregroundColor(bar.boosted ? Color.green : Color.gray) EventActionButton(img: "arrow.2.squarepath", col: bar.boosted ? Color.green : nil) { @@ -56,7 +56,7 @@ struct EventActionBar: View { HStack(alignment: .bottom) { Text("\(bar.likes > 0 ? "\(bar.likes)" : "")") - .font(.footnote) + .font(.footnote.weight(.medium)) .foregroundColor(bar.liked ? Color.red : Color.gray) EventActionButton(img: bar.liked ? "heart.fill" : "heart", col: bar.liked ? Color.red : nil) { @@ -136,7 +136,7 @@ struct EventActionBar: View { func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View { Button(action: action) { Label("", systemImage: img) - .font(.footnote) + .font(.footnote.weight(.medium)) .foregroundColor(col == nil ? Color.gray : col!) } .padding(.trailing, 40) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift index 23392eb8..ea9dd2c1 100644 --- a/damus/Views/EventView.swift +++ b/damus/Views/EventView.swift @@ -119,11 +119,12 @@ struct EventView: View { VStack(alignment: .leading) { HStack(alignment: .center) { - ProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon) + EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon) Text("\(format_relative_time(event.created_at))") + .font(.subheadline) .foregroundColor(.gray) } - + if event.is_reply(damus.keypair.privkey) { Text("\(reply_desc(profiles: damus.profiles, event: event))") .font(.footnote) diff --git a/damus/Views/FollowButtonView.swift b/damus/Views/FollowButtonView.swift index ea8280e5..294d31a0 100644 --- a/damus/Views/FollowButtonView.swift +++ b/damus/Views/FollowButtonView.swift @@ -8,6 +8,9 @@ import SwiftUI struct FollowButtonView: View { + + @Environment(\.colorScheme) var colorScheme + let target: FollowTarget @State var follow_state: FollowState @@ -16,15 +19,15 @@ struct FollowButtonView: View { follow_state = perform_follow_btn_action(follow_state, target: target) } label: { Text(follow_btn_txt(follow_state)) - .padding(.horizontal, 20) - .padding(.vertical, 7) + .padding(.horizontal, 25) + .padding(.vertical, 10) .font(.caption.weight(.bold)) - .foregroundColor(follow_state == .unfollows ? .white : .black) - .background(follow_state == .unfollows ? .black : .white) + .foregroundColor(follow_state == .unfollows ? emptyColor() : fillColor()) + .background(follow_state == .unfollows ? fillColor() : emptyColor()) .cornerRadius(20) .overlay { RoundedRectangle(cornerRadius: 16) - .stroke(follow_state == .unfollows ? .white : .gray, lineWidth: 1) + .stroke(follow_state == .unfollows ? borderColor() : fillColor(), lineWidth: 1) } } .onReceive(handle_notify(.followed)) { notif in @@ -44,6 +47,18 @@ struct FollowButtonView: View { self.follow_state = .unfollows } } + + func fillColor() -> Color { + colorScheme == .light ? .black : .white + } + + func emptyColor() -> Color { + colorScheme == .light ? .white : .black + } + + func borderColor() -> Color { + colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.1) + } } struct FollowButtonPreviews: View { diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift index 5fcdef2d..6d6fc212 100644 --- a/damus/Views/NoteContentView.swift +++ b/damus/Views/NoteContentView.swift @@ -64,8 +64,10 @@ struct NoteContentView: View { return VStack(alignment: .leading) { if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) { Text(txt) + .font(.subheadline) } else { Text(artifacts.content) + .font(.subheadline) } if show_images && artifacts.images.count > 0 { ImageCarousel(urls: artifacts.images) diff --git a/damus/Views/ProfileName.swift b/damus/Views/ProfileName.swift index c1cf17fb..93715a3e 100644 --- a/damus/Views/ProfileName.swift +++ b/damus/Views/ProfileName.swift @@ -23,7 +23,7 @@ struct ProfileFullName: View { .font(.footnote) .foregroundColor(.gray) } else { - ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true) +// ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true) } } } @@ -73,8 +73,9 @@ struct ProfileName: View { var body: some View { HStack { + Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) - //.foregroundColor(hex_to_rgb(pubkey)) + .font(.subheadline) .fontWeight(prefix == "@" ? .none : .bold) if let frend = friend_icon { Label("", systemImage: frend) @@ -92,4 +93,77 @@ struct ProfileName: View { } } - +/// Profile Name used when displaying an event in the timeline +struct EventProfileName: View { + let pubkey: String + let profile: Profile? + let contacts: Contacts + let prefix: String + + let show_friend_confirmed: Bool + + @State var display_name: String? + + init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool) { + self.pubkey = pubkey + self.profile = profile + self.prefix = "" + self.contacts = contacts + self.show_friend_confirmed = show_friend_confirmed + } + + init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool) { + self.pubkey = pubkey + self.profile = profile + self.prefix = prefix + self.contacts = contacts + self.show_friend_confirmed = show_friend_confirmed + } + + var friend_icon: String? { + if !show_friend_confirmed { + return nil + } + + if self.contacts.is_friend(self.pubkey) { + return "person.fill.checkmark" + } + + if self.contacts.is_friend_of_friend(self.pubkey) { + return "person.fill.and.arrow.left.and.arrow.right" + } + + return nil + } + + var body: some View { + HStack { + if let real_name = profile?.display_name { + Text(real_name) + .font(.subheadline.weight(.bold)) + + Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) + .foregroundColor(.gray) + .font(.subheadline) + } else { + Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) + .foregroundColor(.black) + .font(.subheadline) + .fontWeight(.bold) + } + + if let frend = friend_icon { + Label("", systemImage: frend) + .foregroundColor(.gray) + .font(.footnote) + } + } + .onReceive(handle_notify(.profile_updated)) { notif in + let update = notif.object as! ProfileUpdate + if update.pubkey != pubkey { + return + } + display_name = Profile.displayName(profile: update.profile, pubkey: pubkey) + } + } +} diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift index ca6ebd1b..db39b549 100644 --- a/damus/Views/ProfileView.swift +++ b/damus/Views/ProfileView.swift @@ -78,6 +78,9 @@ struct ProfileNameView: View { } struct ProfileView: View { + + @Environment(\.colorScheme) var colorScheme + let damus_state: DamusState @State private var selected_tab: ProfileTab = .posts @@ -94,8 +97,8 @@ struct ProfileView: View { }) { Image(systemName: "bolt.circle") .symbolRenderingMode(.palette) - .foregroundStyle(.black, .gray) - .font(.system(size: 27).weight(.thin)) + .font(.system(size: 34).weight(.thin)) + .foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.1)) } } @@ -106,8 +109,8 @@ struct ProfileView: View { return NavigationLink(destination: dmview) { Image(systemName: "bubble.left.circle") .symbolRenderingMode(.palette) - .font(.system(size: 29).weight(.thin)) - .foregroundStyle(.black, .gray) + .font(.system(size: 34).weight(.thin)) + .foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.1)) } } @@ -133,6 +136,7 @@ struct ProfileView: View { .padding(.bottom) Text(data?.about ?? "") + .font(.subheadline) Divider() From cfe623b58b58a67a6d3124ae3cca7266d35ec797 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sun, 18 Dec 2022 21:46:44 -0700 Subject: [PATCH 08/13] Fixed the boosted font --- damus/Views/EventView.swift | 15 ++++++++++----- damus/Views/ProfileView.swift | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift index ea9dd2c1..5d696c44 100644 --- a/damus/Views/EventView.swift +++ b/damus/Views/EventView.swift @@ -83,17 +83,22 @@ struct EventView: View { NavigationLink(destination: booster_profile) { HStack { - Label("", systemImage: "arrow.2.squarepath") + Image(systemName: "arrow.2.squarepath") + .font(.footnote.weight(.bold)) .foregroundColor(Color.gray) - ProfileName(pubkey: event.pubkey, profile: damus.profiles.lookup(id: event.pubkey), contacts: damus.contacts, show_friend_confirmed: show_friend_icon) - .foregroundColor(Color.gray) - Text(" Boosted") + if let prof = damus.profiles.lookup(id: event.pubkey) { + Text(Profile.displayName(profile: prof, pubkey: event.pubkey)) + .font(.footnote.weight(.bold)) + .foregroundColor(Color.gray) + } + Text("Boosted") + .font(.footnote.weight(.bold)) .foregroundColor(Color.gray) } } .buttonStyle(PlainButtonStyle()) TextEvent(inner_ev, pubkey: inner_ev.pubkey) - .padding([.top], 2) + .padding([.top], 1) } } else { TextEvent(event, pubkey: pubkey) diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift index db39b549..6397db8b 100644 --- a/damus/Views/ProfileView.swift +++ b/damus/Views/ProfileView.swift @@ -98,7 +98,7 @@ struct ProfileView: View { Image(systemName: "bolt.circle") .symbolRenderingMode(.palette) .font(.system(size: 34).weight(.thin)) - .foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.1)) + .foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)) } } @@ -110,7 +110,7 @@ struct ProfileView: View { Image(systemName: "bubble.left.circle") .symbolRenderingMode(.palette) .font(.system(size: 34).weight(.thin)) - .foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.1)) + .foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)) } } From 223908fb3a2987dea652ed69c5ca7432713c1861 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Sun, 18 Dec 2022 21:52:03 -0700 Subject: [PATCH 09/13] font adjustments to the following. Looking good --- damus/Views/ProfileName.swift | 1 - damus/Views/ProfileView.swift | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/damus/Views/ProfileName.swift b/damus/Views/ProfileName.swift index 93715a3e..803107dd 100644 --- a/damus/Views/ProfileName.swift +++ b/damus/Views/ProfileName.swift @@ -147,7 +147,6 @@ struct EventProfileName: View { .font(.subheadline) } else { Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) - .foregroundColor(.black) .font(.subheadline) .fontWeight(.bold) } diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift index 6397db8b..9d67ada1 100644 --- a/damus/Views/ProfileView.swift +++ b/damus/Views/ProfileView.swift @@ -147,7 +147,9 @@ struct ProfileView: View { NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) { HStack { Text("\(profile.following)") + .font(.subheadline.weight(.medium)) Text("Following") + .font(.subheadline) .foregroundColor(.gray) } } @@ -158,7 +160,9 @@ struct ProfileView: View { NavigationLink(destination: fview) { HStack { Text("\(followers.contacts.count)") + .font(.subheadline.weight(.medium)) Text("Followers") + .font(.subheadline) .foregroundColor(.gray) } } From c5761b05d914c93f7ad824d1f2f1dd384ef64d28 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Mon, 19 Dec 2022 13:47:26 -0700 Subject: [PATCH 10/13] Quick fix on the border color of the follow button depending on if you already follow that person. Released my logic path was backwards. fixed! --- damus/Views/FollowButtonView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/damus/Views/FollowButtonView.swift b/damus/Views/FollowButtonView.swift index 294d31a0..d7de02d3 100644 --- a/damus/Views/FollowButtonView.swift +++ b/damus/Views/FollowButtonView.swift @@ -27,7 +27,7 @@ struct FollowButtonView: View { .cornerRadius(20) .overlay { RoundedRectangle(cornerRadius: 16) - .stroke(follow_state == .unfollows ? borderColor() : fillColor(), lineWidth: 1) + .stroke(follow_state == .unfollows ? .clear : borderColor(), lineWidth: 1) } } .onReceive(handle_notify(.followed)) { notif in @@ -57,7 +57,7 @@ struct FollowButtonView: View { } func borderColor() -> Color { - colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.1) + colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2) } } From 3a1d4b1c39fa551b458e373534c6e1395defb9cd Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Tue, 20 Dec 2022 09:20:37 -0700 Subject: [PATCH 11/13] Removed the app viewModel. Need to find a better structure for the future to fix this issue --- damus.xcodeproj/project.pbxproj | 8 +- damus/ContentView.swift | 261 ++++++++++++++++++++++++-------- damus/DamusViewModel.swift | 162 -------------------- damus/Views/TimelineView.swift | 2 - damus/damusApp.swift | 3 +- 5 files changed, 202 insertions(+), 234 deletions(-) delete mode 100644 damus/DamusViewModel.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 0a456065..2d52979c 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -7,10 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; }; 3169CAEB294FCABA00EE4006 /* Shimmer in Frameworks */ = {isa = PBXBuildFile; productRef = 3169CAEA294FCABA00EE4006 /* Shimmer */; }; 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; - 3169CAEF294FD4C400EE4006 /* DamusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEE294FD4C400EE4006 /* DamusViewModel.swift */; }; - 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; }; 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; }; 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; }; 4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; }; @@ -150,9 +149,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; }; - 3169CAEE294FD4C400EE4006 /* DamusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusViewModel.swift; sourceTree = ""; }; 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = ""; }; + 3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; }; 4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = ""; }; 4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = ""; }; 4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = ""; }; @@ -560,7 +558,6 @@ 4C75EFA72804823E0006080F /* Info.plist */, 4C75EFA227FA576C0006080F /* Views */, 4CE6DEE627F7A08100C66700 /* damusApp.swift */, - 3169CAEE294FD4C400EE4006 /* DamusViewModel.swift */, 4CE6DEE827F7A08100C66700 /* ContentView.swift */, 4CE6DEEA27F7A08200C66700 /* Assets.xcassets */, 4CE6DEEC27F7A08200C66700 /* Preview Content */, @@ -750,7 +747,6 @@ files = ( 4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */, 4C363A8A28236B57006E126D /* MentionView.swift in Sources */, - 3169CAEF294FD4C400EE4006 /* DamusViewModel.swift in Sources */, 4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */, 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */, 4C216F34286F5ACD00040376 /* DMView.swift in Sources */, diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 04698c6f..9b05b382 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -9,24 +9,83 @@ import SwiftUI import Starscream import Kingfisher +var BOOTSTRAP_RELAYS = [ + "wss://relay.damus.io", + "wss://nostr-relay.wlvs.space", + "wss://nostr.fmt.wiz.biz", + "wss://nostr.oxtr.dev", +] + +struct TimestampedProfile { + let profile: Profile + let timestamp: Int64 +} + +enum Sheets: Identifiable { + case post + case reply(NostrEvent) + + var id: String { + switch self { + case .post: return "post" + case .reply(let ev): return "reply-" + ev.id + } + } +} + +enum ThreadState { + case event_details + case chatroom +} + +enum FilterState : Int { + case posts_and_replies = 1 + case posts = 0 +} + struct ContentView: View { + let keypair: Keypair - @EnvironmentObject var viewModel: DamusViewModel + var pubkey: String { + return keypair.pubkey + } + + var privkey: String? { + return keypair.privkey + } + + @State var status: String = "Not connected" + @State var active_sheet: Sheets? = nil + @State var damus_state: DamusState? = nil + @State var selected_timeline: Timeline? = .home + @State var is_thread_open: Bool = false + @State var is_profile_open: Bool = false + @State var event: NostrEvent? = nil + @State var active_profile: String? = nil + @State var active_search: NostrFilter? = nil + @State var active_event_id: String? = nil + @State var profile_open: Bool = false + @State var thread_open: Bool = false + @State var search_open: Bool = false + @State var filter_state : FilterState = .posts_and_replies + @StateObject var home: HomeModel = HomeModel() // connect retry timer let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect() + + let sub_id = UUID().description @Environment(\.colorScheme) var colorScheme var PostingTimelineView: some View { VStack{ ZStack { - if let damus = viewModel.state { - TimelineView(events: $viewModel.home.events, loading: $viewModel.home.loading, damus: damus, show_friend_icon: false, filter: filter_event) + if let damus = self.damus_state { + TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter_event) } - if viewModel.privkey != nil { + if privkey != nil { PostButtonContainer { - viewModel.active_sheet = .post + self.active_sheet = .post } } } @@ -45,7 +104,7 @@ struct ContentView: View { var FiltersView: some View { VStack{ - Picker("Filter State", selection: $viewModel.filter_state) { + Picker("Filter State", selection: $filter_state) { Text("Posts").tag(FilterState.posts) Text("Posts & Replies").tag(FilterState.posts_and_replies) } @@ -54,7 +113,7 @@ struct ContentView: View { } func filter_event(_ ev: NostrEvent) -> Bool { - if viewModel.filter_state == .posts { + if self.filter_state == .posts { return !ev.is_reply(nil) } @@ -63,41 +122,41 @@ struct ContentView: View { func MainContent(damus: DamusState) -> some View { VStack { - NavigationLink(destination: MaybeProfileView, isActive: $viewModel.profile_open) { + NavigationLink(destination: MaybeProfileView, isActive: $profile_open) { EmptyView() } - NavigationLink(destination: MaybeThreadView, isActive: $viewModel.thread_open) { + NavigationLink(destination: MaybeThreadView, isActive: $thread_open) { EmptyView() } - NavigationLink(destination: MaybeSearchView, isActive: $viewModel.search_open) { + NavigationLink(destination: MaybeSearchView, isActive: $search_open) { EmptyView() } - switch viewModel.selected_timeline { + switch selected_timeline { case .search: - SearchHomeView(damus_state: viewModel.state!, model: SearchHomeModel(damus_state: viewModel.state!)) + SearchHomeView(damus_state: damus_state!, model: SearchHomeModel(damus_state: damus_state!)) case .home: PostingTimelineView case .notifications: - TimelineView(events: $viewModel.home.notifications, loading: $viewModel.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 }) .navigationTitle("Notifications") case .dms: - DirectMessagesView(damus_state: viewModel.state!) - .environmentObject(viewModel.home.dms) + DirectMessagesView(damus_state: damus_state!) + .environmentObject(home.dms) case .none: EmptyView() } } - .navigationBarTitle(viewModel.selected_timeline == .home ? "Home" : "Global", displayMode: .inline) + .navigationBarTitle(selected_timeline == .home ? "Home" : "Global", displayMode: .inline) } var MaybeSearchView: some View { Group { - if let search = viewModel.active_search { - SearchView(appstate: viewModel.state!, search: SearchModel(pool: viewModel.state!.pool, search: search)) + if let search = self.active_search { + SearchView(appstate: damus_state!, search: SearchModel(pool: damus_state!.pool, search: search)) } else { EmptyView() } @@ -106,9 +165,9 @@ struct ContentView: View { var MaybeThreadView: some View { Group { - if let evid = viewModel.active_event_id { - let thread_model = ThreadModel(evid: evid, damus_state: viewModel.state!) - ThreadView(thread: thread_model, damus: viewModel.state!, is_chatroom: false) + if let evid = self.active_event_id { + let thread_model = ThreadModel(evid: evid, damus_state: damus_state!) + ThreadView(thread: thread_model, damus: damus_state!, is_chatroom: false) } else { EmptyView() } @@ -117,10 +176,10 @@ struct ContentView: View { var MaybeProfileView: some View { Group { - if let pk = viewModel.active_profile { - let profile_model = ProfileModel(pubkey: pk, damus: viewModel.state!) - let followers = FollowersModel(damus_state: viewModel.state!, target: pk) - ProfileView(damus_state: viewModel.state!, profile: profile_model, followers: followers) + if let pk = self.active_profile { + let profile_model = ProfileModel(pubkey: pk, damus: damus_state!) + let followers = FollowersModel(damus_state: damus_state!, target: pk) + ProfileView(damus_state: damus_state!, profile: profile_model, followers: followers) } else { EmptyView() } @@ -129,20 +188,20 @@ struct ContentView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { - if let damus = viewModel.state { + if let damus = self.damus_state { NavigationView { MainContent(damus: damus) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - let profile_model = ProfileModel(pubkey: viewModel.state!.pubkey, damus: viewModel.state!) - let followers_model = FollowersModel(damus_state: viewModel.state!, target: viewModel.state!.pubkey) - let prof_dest = ProfileView(damus_state: viewModel.state!, profile: profile_model, followers: followers_model) + let profile_model = ProfileModel(pubkey: damus_state!.pubkey, damus: damus_state!) + let followers_model = FollowersModel(damus_state: damus_state!, target: damus_state!.pubkey) + let prof_dest = ProfileView(damus_state: damus_state!, profile: profile_model, followers: followers_model) NavigationLink(destination: prof_dest) { /// Verify that the user has a profile picture, if not display a generic SF Symbol /// (Resolves an in-app error where ``Robohash`` pictures are not generated so the button dissapears - if let picture = viewModel.state?.profiles.lookup(id: viewModel.pubkey)?.picture { - ProfilePicView(pubkey: viewModel.state!.pubkey, size: 32, highlight: .none, profiles: viewModel.state!.profiles, picture: picture) + if let picture = damus_state?.profiles.lookup(id: pubkey)?.picture { + ProfilePicView(pubkey: damus_state!.pubkey, size: 32, highlight: .none, profiles: damus_state!.profiles, picture: picture) } else { Image(systemName: "person.fill") } @@ -152,14 +211,14 @@ struct ContentView: View { ToolbarItem(placement: .navigationBarTrailing) { HStack(alignment: .center) { - if viewModel.home.signal.signal != viewModel.home.signal.max_signal { - Text("\(viewModel.home.signal.signal)/\(viewModel.home.signal.max_signal)") + if home.signal.signal != home.signal.max_signal { + Text("\(home.signal.signal)/\(home.signal.max_signal)") .font(.callout) .foregroundColor(.gray) } - NavigationLink(destination: ConfigView(state: viewModel.state!)) { - Image(systemName: "gear") + NavigationLink(destination: ConfigView(state: damus_state!)) { + Label("", systemImage: "gear") } .buttonStyle(PlainButtonStyle()) } @@ -169,19 +228,19 @@ struct ContentView: View { .navigationViewStyle(.stack) } - TabBar(new_events: $viewModel.home.new_events, selected: $viewModel.selected_timeline, action: viewModel.switch_timeline) + TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline) } .onAppear() { - viewModel.connect() + self.connect() //KingfisherManager.shared.cache.clearDiskCache() setup_notifications() } - .sheet(item: $viewModel.active_sheet) { item in + .sheet(item: $active_sheet) { item in switch item { case .post: PostView(replying_to: nil, references: []) case .reply(let event): - ReplyView(replying_to: event, damus: viewModel.state!) + ReplyView(replying_to: event, damus: damus_state!) } } .onOpenURL { url in @@ -192,28 +251,28 @@ struct ContentView: View { switch link { case .ref(let ref): if ref.key == "p" { - viewModel.active_profile = ref.ref_id - viewModel.profile_open = true + active_profile = ref.ref_id + profile_open = true } else if ref.key == "e" { - viewModel.active_event_id = ref.ref_id - viewModel.thread_open = true + active_event_id = ref.ref_id + thread_open = true } case .filter(let filt): - viewModel.active_search = filt - viewModel.search_open = true + active_search = filt + search_open = true break // TODO: handle filter searches? } } .onReceive(handle_notify(.boost)) { notif in - guard let privkey = viewModel.privkey else { + guard let privkey = self.privkey else { return } let ev = notif.object as! NostrEvent - let boost = make_boost_event(pubkey: viewModel.pubkey, privkey: privkey, boosted: ev) - viewModel.state?.pool.send(.event(boost)) + let boost = make_boost_event(pubkey: pubkey, privkey: privkey, boosted: ev) + self.damus_state?.pool.send(.event(boost)) } .onReceive(handle_notify(.open_thread)) { obj in //let ev = obj.object as! NostrEvent @@ -222,20 +281,20 @@ struct ContentView: View { } .onReceive(handle_notify(.reply)) { notif in let ev = notif.object as! NostrEvent - viewModel.active_sheet = .reply(ev) + self.active_sheet = .reply(ev) } .onReceive(handle_notify(.like)) { like in } .onReceive(handle_notify(.broadcast_event)) { obj in let ev = obj.object as! NostrEvent - viewModel.state?.pool.send(.event(ev)) + self.damus_state?.pool.send(.event(ev)) } .onReceive(handle_notify(.unfollow)) { notif in - guard let privkey = viewModel.privkey else { + guard let privkey = self.privkey else { return } - guard let damus = viewModel.state else { + guard let damus = self.damus_state else { return } @@ -255,12 +314,12 @@ struct ContentView: View { } } .onReceive(handle_notify(.follow)) { notif in - guard let privkey = viewModel.privkey else { + guard let privkey = self.privkey else { return } let fnotify = notif.object as! FollowTarget - guard let damus = viewModel.state else { + guard let damus = self.damus_state else { return } @@ -271,7 +330,7 @@ struct ContentView: View { follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) { notify(.followed, fnotify.pubkey) - viewModel.state?.contacts.event = ev + damus_state?.contacts.event = ev switch fnotify { case .pubkey(let pk): @@ -282,7 +341,7 @@ struct ContentView: View { } } .onReceive(handle_notify(.post)) { obj in - guard let privkey = viewModel.privkey else { + guard let privkey = self.privkey else { return } @@ -290,23 +349,70 @@ struct ContentView: View { switch post_res { case .post(let post): print("post \(post.content)") - let new_ev = post_to_event(post: post, privkey: privkey, pubkey: viewModel.pubkey) - viewModel.state?.pool.send(.event(new_ev)) + let new_ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey) + self.damus_state?.pool.send(.event(new_ev)) case .cancel: - viewModel.active_sheet = nil + active_sheet = nil print("post cancelled") } } .onReceive(timer) { n in - viewModel.state?.pool.connect_to_disconnected() + self.damus_state?.pool.connect_to_disconnected() } } + + func switch_timeline(_ timeline: Timeline) { + NotificationCenter.default.post(name: .switched_timeline, object: timeline) + + if timeline == self.selected_timeline { + NotificationCenter.default.post(name: .scroll_to_top, object: nil) + return + } + + self.selected_timeline = timeline + //NotificationCenter.default.post(name: .switched_timeline, object: timeline) + //self.selected_timeline = timeline + } + + func add_relay(_ pool: RelayPool, _ relay: String) { + //add_rw_relay(pool, "wss://nostr-pub.wellorder.net") + add_rw_relay(pool, relay) + /* + let profile = Profile(name: relay, about: nil, picture: nil) + let ts = Int64(Date().timeIntervalSince1970) + let tsprofile = TimestampedProfile(profile: profile, timestamp: ts) + damus!.profiles.add(id: relay, profile: tsprofile) + */ + } + + func connect() { + let pool = RelayPool() + + for relay in BOOTSTRAP_RELAYS { + add_relay(pool, relay) + } + + pool.register_handler(sub_id: sub_id, handler: home.handle_event) + + self.damus_state = DamusState(pool: pool, keypair: keypair, + likes: EventCounter(our_pubkey: pubkey), + boosts: EventCounter(our_pubkey: pubkey), + contacts: Contacts(), + tips: TipCounter(our_pubkey: pubkey), + profiles: Profiles(), + dms: home.dms + ) + home.damus_state = self.damus_state! + + pool.connect() + } + + } struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView() - .environmentObject(DamusViewModel(with: Keypair(pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", privkey: nil))) + ContentView(keypair: Keypair(pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", privkey: nil)) } } @@ -319,6 +425,31 @@ func get_since_time(last_event: NostrEvent?) -> Int64? { return nil } +func ws_nostr_event(relay: String, ev: WebSocketEvent) -> NostrEvent? { + switch ev { + case .binary(let dat): + return NostrEvent(content: "binary data? \(dat.count) bytes", pubkey: relay) + case .cancelled: + return NostrEvent(content: "cancelled", pubkey: relay) + case .connected: + return NostrEvent(content: "connected", pubkey: relay) + case .disconnected: + return NostrEvent(content: "disconnected", pubkey: relay) + case .error(let err): + return NostrEvent(content: "error \(err.debugDescription)", pubkey: relay) + case .text(let txt): + return NostrEvent(content: "text \(txt)", pubkey: relay) + case .pong: + return NostrEvent(content: "pong", pubkey: relay) + case .ping: + return NostrEvent(content: "ping", pubkey: relay) + case .viabilityChanged(let b): + return NostrEvent(content: "viabilityChanged \(b)", pubkey: relay) + case .reconnectSuggested(let b): + return NostrEvent(content: "reconnectSuggested \(b)", pubkey: relay) + } +} + func is_notification(ev: NostrEvent, pubkey: String) -> Bool { if ev.pubkey == pubkey { return false @@ -338,6 +469,11 @@ extension UINavigationController: UIGestureRecognizerDelegate { } } +struct LastNotification { + let id: String + let created_at: Int64 +} + func get_last_event(_ timeline: Timeline) -> LastNotification? { let str = timeline.rawValue let last = UserDefaults.standard.string(forKey: "last_\(str)") @@ -413,3 +549,4 @@ func setup_notifications() { } } } + diff --git a/damus/DamusViewModel.swift b/damus/DamusViewModel.swift deleted file mode 100644 index 5fa7cb1c..00000000 --- a/damus/DamusViewModel.swift +++ /dev/null @@ -1,162 +0,0 @@ -// -// DamusViewModel.swift -// damus -// -// Created by Sam DuBois on 12/18/22. -// - -import SwiftUI -import Starscream -import Kingfisher - -/// Default relays to be used when setting up the user's account. -var BOOTSTRAP_RELAYS = [ - "wss://relay.damus.io", - "wss://nostr-relay.wlvs.space", - "wss://nostr.oxtr.dev", -] - -class DamusViewModel: ObservableObject { - - // MARK: Constants and Variables - - let sub_id = UUID().description - - /// User Keypair object - var keypair: Keypair - - var pubkey: String { - return keypair.pubkey - } - - var privkey: String? { - return keypair.privkey - } - - @Published var status: String = "Not connected" - @Published var state: DamusState? = nil - @Published var active_sheet: Sheets? = nil - @Published var selected_timeline: Timeline? = .home - @Published var is_thread_open: Bool = false - @Published var is_profile_open: Bool = false - @Published var event: NostrEvent? = nil - @Published var active_profile: String? = nil - @Published var active_search: NostrFilter? = nil - @Published var active_event_id: String? = nil - @Published var profile_open: Bool = false - @Published var thread_open: Bool = false - @Published var search_open: Bool = false - @Published var filter_state: FilterState = .posts_and_replies - @Published var home: HomeModel = HomeModel() - - // MARK: Initializer - init(with key: Keypair) { - self.keypair = key - } - - // MARK: Functionality - - func switch_timeline(_ timeline: Timeline) { - NotificationCenter.default.post(name: .switched_timeline, object: timeline) - - if timeline == self.selected_timeline { - NotificationCenter.default.post(name: .scroll_to_top, object: nil) - return - } - - self.selected_timeline = timeline - //NotificationCenter.default.post(name: .switched_timeline, object: timeline) - //self.selected_timeline = timeline - } - - func add_relay(_ pool: RelayPool, _ relay: String) { - //add_rw_relay(pool, "wss://nostr-pub.wellorder.net") - add_rw_relay(pool, relay) - /* - let profile = Profile(name: relay, about: nil, picture: nil) - let ts = Int64(Date().timeIntervalSince1970) - let tsprofile = TimestampedProfile(profile: profile, timestamp: ts) - damus!.profiles.add(id: relay, profile: tsprofile) - */ - } - - func connect() { - let pool = RelayPool() - - for relay in BOOTSTRAP_RELAYS { - add_relay(pool, relay) - } - - pool.register_handler(sub_id: sub_id, handler: home.handle_event) - - self.state = DamusState(pool: pool, keypair: keypair, - likes: EventCounter(our_pubkey: pubkey), - boosts: EventCounter(our_pubkey: pubkey), - contacts: Contacts(), - tips: TipCounter(our_pubkey: pubkey), - profiles: Profiles(), - dms: home.dms - ) - home.damus_state = self.state! - - pool.connect() - } - -} - -struct TimestampedProfile { - let profile: Profile - let timestamp: Int64 -} - -enum Sheets: Identifiable { - case post - case reply(NostrEvent) - - var id: String { - switch self { - case .post: return "post" - case .reply(let ev): return "reply-" + ev.id - } - } -} - -enum ThreadState { - case event_details - case chatroom -} - -enum FilterState : Int { - case posts_and_replies = 1 - case posts = 0 -} - -func ws_nostr_event(relay: String, ev: WebSocketEvent) -> NostrEvent? { - switch ev { - case .binary(let dat): - return NostrEvent(content: "binary data? \(dat.count) bytes", pubkey: relay) - case .cancelled: - return NostrEvent(content: "cancelled", pubkey: relay) - case .connected: - return NostrEvent(content: "connected", pubkey: relay) - case .disconnected: - return NostrEvent(content: "disconnected", pubkey: relay) - case .error(let err): - return NostrEvent(content: "error \(err.debugDescription)", pubkey: relay) - case .text(let txt): - return NostrEvent(content: "text \(txt)", pubkey: relay) - case .pong: - return NostrEvent(content: "pong", pubkey: relay) - case .ping: - return NostrEvent(content: "ping", pubkey: relay) - case .viabilityChanged(let b): - return NostrEvent(content: "viabilityChanged \(b)", pubkey: relay) - case .reconnectSuggested(let b): - return NostrEvent(content: "reconnectSuggested \(b)", pubkey: relay) - } -} - -struct LastNotification { - let id: String - let created_at: Int64 -} diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift index 5a81d40c..289b8317 100644 --- a/damus/Views/TimelineView.swift +++ b/damus/Views/TimelineView.swift @@ -62,8 +62,6 @@ struct InnerTimelineRedactedView: View { struct TimelineView: View { - @EnvironmentObject var viewModel: DamusViewModel - @Binding var events: [NostrEvent] @Binding var loading: Bool diff --git a/damus/damusApp.swift b/damus/damusApp.swift index c284eccb..d23a8602 100644 --- a/damus/damusApp.swift +++ b/damus/damusApp.swift @@ -26,8 +26,7 @@ struct MainView: View { var body: some View { Group { if let kp = keypair, !needs_setup { - ContentView() - .environmentObject(DamusViewModel(with: kp)) + ContentView(keypair: kp) } else { SetupView() .onReceive(handle_notify(.login)) { notif in From 8e4daa79a86c001c5b911b3310bc076511a3c541 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Tue, 20 Dec 2022 09:39:29 -0700 Subject: [PATCH 12/13] Fixed the shimmering bug --- damus.xcodeproj/project.pbxproj | 12 ++--- damus/Components/Shimmer.swift | 77 +++++++++++++++++++++++++++++++++ damus/Views/TimelineView.swift | 1 - 3 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 damus/Components/Shimmer.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 2d52979c..93d7402f 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -8,8 +8,8 @@ /* Begin PBXBuildFile section */ 3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; }; - 3169CAEB294FCABA00EE4006 /* Shimmer in Frameworks */ = {isa = PBXBuildFile; productRef = 3169CAEA294FCABA00EE4006 /* Shimmer */; }; 3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; }; + 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; }; 4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; }; 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; }; 4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; }; @@ -151,6 +151,7 @@ /* Begin PBXFileReference section */ 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = ""; }; 3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; }; + 31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = ""; }; 4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = ""; }; 4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = ""; }; 4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = ""; }; @@ -312,7 +313,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3169CAEB294FCABA00EE4006 /* Shimmer in Frameworks */, 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */, 4CE6DF1227F7A2B300C66700 /* Starscream in Frameworks */, 4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */, @@ -516,6 +516,7 @@ 4CE4F9DF285287A000C00DD9 /* Components */ = { isa = PBXGroup; children = ( + 31D2E846295218AF006D67F8 /* Shimmer.swift */, 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */, 4CD7641A28A1641400B6928F /* EndBlock.swift */, 4C06670528FCB08600038D2A /* ImageCarousel.swift */, @@ -622,7 +623,6 @@ 4CE6DF1127F7A2B300C66700 /* Starscream */, 4C649880286E0EE300EAE2B3 /* secp256k1 */, 4C06670328FC7EC500038D2A /* Kingfisher */, - 3169CAEA294FCABA00EE4006 /* Shimmer */, ); productName = damus; productReference = 4CE6DEE327F7A08100C66700 /* damus.app */; @@ -788,6 +788,7 @@ 4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */, 4C75EFB128049D510006080F /* NostrResponse.swift in Sources */, 4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */, + 31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */, 4C285C8228385570008A31F1 /* CarouselView.swift in Sources */, 4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */, 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */, @@ -1240,11 +1241,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 3169CAEA294FCABA00EE4006 /* Shimmer */ = { - isa = XCSwiftPackageProductDependency; - package = 3169CAE9294FCABA00EE4006 /* XCRemoteSwiftPackageReference "Shimmer" */; - productName = Shimmer; - }; 4C06670328FC7EC500038D2A /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; package = 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */; diff --git a/damus/Components/Shimmer.swift b/damus/Components/Shimmer.swift new file mode 100644 index 00000000..38956fc1 --- /dev/null +++ b/damus/Components/Shimmer.swift @@ -0,0 +1,77 @@ +// +// Shimmer.swift +// +// +// Created by Joshua Homann on 2/20/21. +// + +import SwiftUI + +public struct ShimmerConfiguration { + + @Environment(\.colorScheme) var colorScheme + + public let gradient: Gradient + public let initialLocation: (start: UnitPoint, end: UnitPoint) + public let finalLocation: (start: UnitPoint, end: UnitPoint) + public let duration: TimeInterval + public let opacity: Double + public static let `default` = ShimmerConfiguration( + gradient: Gradient(stops: [ + .init(color: .clear, location: 0), + .init(color: .black, location: 0.3), + .init(color: .black, location: 0.7), + .init(color: .clear, location: 1), + ]), + initialLocation: (start: UnitPoint(x: -1, y: 0.5), end: .leading), + finalLocation: (start: .trailing, end: UnitPoint(x: 2, y: 0.5)), + duration: 2, + opacity: 0.6 + ) +} + +struct ShimmeringView: View { + private let content: () -> Content + private let configuration: ShimmerConfiguration + @State private var startPoint: UnitPoint + @State private var endPoint: UnitPoint + init(configuration: ShimmerConfiguration, @ViewBuilder content: @escaping () -> Content) { + self.configuration = configuration + self.content = content + _startPoint = .init(wrappedValue: configuration.initialLocation.start) + _endPoint = .init(wrappedValue: configuration.initialLocation.end) + } + var body: some View { + ZStack { + content() + LinearGradient( + gradient: configuration.gradient, + startPoint: startPoint, + endPoint: endPoint + ) + .opacity(configuration.opacity) + .blendMode(.overlay) + .onAppear { + withAnimation(Animation.linear(duration: configuration.duration).repeatForever(autoreverses: false)) { + startPoint = configuration.finalLocation.start + endPoint = configuration.finalLocation.end + } + } + } + .edgesIgnoringSafeArea(.all) + } +} + +public struct ShimmerModifier: ViewModifier { + let configuration: ShimmerConfiguration + public func body(content: Content) -> some View { + ShimmeringView(configuration: configuration) { content } + } +} + + +public extension View { + func shimmer(configuration: ShimmerConfiguration = .default) -> some View { + modifier(ShimmerModifier(configuration: configuration)) + } +} diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift index 289b8317..83fa3b76 100644 --- a/damus/Views/TimelineView.swift +++ b/damus/Views/TimelineView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import Shimmer enum TimelineAction { case chillin From cb5593da06db91b8c05fe2645b4789cb8b81e4c4 Mon Sep 17 00:00:00 2001 From: Sam DuBois Date: Tue, 20 Dec 2022 09:44:14 -0700 Subject: [PATCH 13/13] Fixed font formatting --- damus/Views/EventView.swift | 2 +- damus/Views/NoteContentView.swift | 4 ++-- damus/Views/ProfileName.swift | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift index 863eb9da..2138757e 100644 --- a/damus/Views/EventView.swift +++ b/damus/Views/EventView.swift @@ -126,7 +126,7 @@ struct EventView: View { HStack(alignment: .center) { EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon) Text("\(format_relative_time(event.created_at))") - .font(.subheadline) + .font(.body) .foregroundColor(.gray) } diff --git a/damus/Views/NoteContentView.swift b/damus/Views/NoteContentView.swift index 6d6fc212..55c87942 100644 --- a/damus/Views/NoteContentView.swift +++ b/damus/Views/NoteContentView.swift @@ -64,10 +64,10 @@ struct NoteContentView: View { return VStack(alignment: .leading) { if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) { Text(txt) - .font(.subheadline) + .font(.body) } else { Text(artifacts.content) - .font(.subheadline) + .font(.body) } if show_images && artifacts.images.count > 0 { ImageCarousel(urls: artifacts.images) diff --git a/damus/Views/ProfileName.swift b/damus/Views/ProfileName.swift index 803107dd..4b0dce7b 100644 --- a/damus/Views/ProfileName.swift +++ b/damus/Views/ProfileName.swift @@ -75,7 +75,7 @@ struct ProfileName: View { HStack { Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) - .font(.subheadline) + .font(.body) .fontWeight(prefix == "@" ? .none : .bold) if let frend = friend_icon { Label("", systemImage: frend) @@ -140,14 +140,14 @@ struct EventProfileName: View { HStack { if let real_name = profile?.display_name { Text(real_name) - .font(.subheadline.weight(.bold)) + .font(.body.weight(.bold)) Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) .foregroundColor(.gray) - .font(.subheadline) + .font(.body) } else { Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))) - .font(.subheadline) + .font(.body) .fontWeight(.bold) }