From 5733f782d933a9fbe491ae3f88e7ab77c5a5cc97 Mon Sep 17 00:00:00 2001 From: Ben Weeks Date: Fri, 6 Jan 2023 20:11:19 +0000 Subject: [PATCH] Latest updates to the sidebar. --- damus.xcodeproj/project.pbxproj | 6 +- damus/ContentView.swift | 339 +++++++++++++++++--------------- damus/Views/SideMenuView.swift | 118 +++++++++++ 3 files changed, 302 insertions(+), 161 deletions(-) create mode 100644 damus/Views/SideMenuView.swift diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index cdfe468f..74a4c1ff 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -138,6 +138,7 @@ 4CEE2AF9280B2EAC00AB5EEF /* PowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */; }; 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */; }; 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; }; + 647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; }; 64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; }; 6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; }; BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; }; @@ -331,6 +332,7 @@ 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowView.swift; sourceTree = ""; }; 4CEE2B01280B39E800AB5EEF /* EventActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventActionBar.swift; sourceTree = ""; }; 4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = ""; }; + 647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = ""; }; 64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = ""; }; BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = ""; }; @@ -487,7 +489,6 @@ 4C3AC79E2833115300E1F516 /* FollowButtonView.swift */, 4C3AC79C2833036D00E1F516 /* FollowingView.swift */, 4C90BD17283A9EE5008EE7EF /* LoginView.swift */, - 4C3AC7A42836987600E1F516 /* MainTabView.swift */, 4C363A8928236B57006E126D /* MentionView.swift */, 4C363A8D28236FE4006E126D /* NoteContentView.swift */, 4C75EFAC28049CFB0006080F /* PostButton.swift */, @@ -497,6 +498,7 @@ 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */, 4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */, 4C8682862814DE470026224F /* ProfileView.swift */, + 4C3AC7A42836987600E1F516 /* MainTabView.swift */, 4C363A8B28236B92006E126D /* PubkeyView.swift */, 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */, 4C06670028FC7C5900038D2A /* RelayView.swift */, @@ -512,6 +514,7 @@ 4C0A3F96280F8E02000448DE /* ThreadView.swift */, 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */, 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */, + 647D9A8C2968520300A295DE /* SideMenuView.swift */, ); path = Views; sourceTree = ""; @@ -822,6 +825,7 @@ 4C363A9028247A1D006E126D /* NostrLink.swift in Sources */, 4C0A3F8C280F5FCA000448DE /* ChatroomView.swift in Sources */, 4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */, + 647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */, 4C0A3F91280F6528000448DE /* ChatView.swift in Sources */, 4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */, 4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */, diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 2abd09e4..fe75c0ad 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -80,6 +80,7 @@ struct ContentView: View { @State var thread_open: Bool = false @State var search_open: Bool = false @State var filter_state : FilterState = .posts_and_replies + @State private var isSideBarOpened = false @StateObject var home: HomeModel = HomeModel() @StateObject var user_settings = UserSettingsStore() @@ -210,179 +211,197 @@ struct ContentView: View { } var body: some View { - VStack(alignment: .leading, spacing: 0) { - if let damus = self.damus_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) - - 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) - } else { - Image(systemName: "person.fill") + //ZStack { + VStack(alignment: .leading, spacing: 0) { + if let damus = self.damus_state { + NavigationView { + SideMenuView(damus_state: damus_state!, isSidebarVisible: $isSideBarOpened) + 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) + + Button { + isSideBarOpened.toggle() + } label: { + 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) + + 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") + } } - } - .buttonStyle(PlainButtonStyle()) - } - - 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!).environmentObject(user_settings)) { - Label("", systemImage: "gear") + + 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) + } else { + Image(systemName: "person.fill") + } } .buttonStyle(PlainButtonStyle()) + + } + + 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!).environmentObject(user_settings)) { + Label("", systemImage: "gear") + } + .buttonStyle(PlainButtonStyle()) + } } } - } + } + .navigationViewStyle(.stack) } - .navigationViewStyle(.stack) - } - - TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline) - .padding([.bottom], 8) - } - .onAppear() { - self.connect() - //KingfisherManager.shared.cache.clearDiskCache() - setup_notifications() - } - .sheet(item: $active_sheet) { item in - switch item { - case .post: - PostView(replying_to: nil, references: []) - case .reply(let event): - ReplyView(replying_to: event, damus: damus_state!) - } - } - .onOpenURL { url in - guard let link = decode_nostr_uri(url.absoluteString) else { - return - } - - switch link { - case .ref(let ref): - if ref.key == "p" { - active_profile = ref.ref_id - profile_open = true - } else if ref.key == "e" { - active_event_id = ref.ref_id - thread_open = true - } - case .filter(let filt): - active_search = filt - search_open = true - break - // TODO: handle filter searches? - } - - } - .onReceive(handle_notify(.boost)) { notif in - guard let privkey = self.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)) - } - .onReceive(handle_notify(.open_thread)) { obj in - //let ev = obj.object as! NostrEvent - //thread.set_active_event(ev) - //is_thread_open = true - } - .onReceive(handle_notify(.reply)) { notif in - let ev = notif.object as! NostrEvent - self.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)) - } - .onReceive(handle_notify(.unfollow)) { notif in - guard let privkey = self.privkey else { - return - } - - guard let damus = self.damus_state else { - return - } - - let target = notif.object as! FollowTarget - let pk = target.pubkey - - if let ev = unfollow_user(pool: damus.pool, - our_contacts: damus.contacts.event, - pubkey: damus.pubkey, - privkey: privkey, - unfollow: pk) { - notify(.unfollowed, pk) - damus.contacts.event = ev - damus.contacts.remove_friend(pk) - //friend_events = friend_events.filter { $0.pubkey != pk } + TabBar(new_events: $home.new_events, selected: $selected_timeline, action: switch_timeline) + .padding([.bottom], 8) } - } - .onReceive(handle_notify(.follow)) { notif in - guard let privkey = self.privkey else { - return + .onAppear() { + self.connect() + //KingfisherManager.shared.cache.clearDiskCache() + setup_notifications() } - - let fnotify = notif.object as! FollowTarget - guard let damus = self.damus_state else { - return - } - - if let ev = follow_user(pool: damus.pool, - our_contacts: damus.contacts.event, - pubkey: damus.pubkey, - privkey: privkey, - follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) { - notify(.followed, fnotify.pubkey) - - damus_state?.contacts.event = ev - - switch fnotify { - case .pubkey(let pk): - damus.contacts.add_friend_pubkey(pk) - case .contact(let ev): - damus.contacts.add_friend_contact(ev) + .sheet(item: $active_sheet) { item in + switch item { + case .post: + PostView(replying_to: nil, references: []) + case .reply(let event): + ReplyView(replying_to: event, damus: damus_state!) } } - } - .onReceive(handle_notify(.post)) { obj in - guard let privkey = self.privkey else { - return + .onOpenURL { url in + guard let link = decode_nostr_uri(url.absoluteString) else { + return + } + + switch link { + case .ref(let ref): + if ref.key == "p" { + active_profile = ref.ref_id + profile_open = true + } else if ref.key == "e" { + active_event_id = ref.ref_id + thread_open = true + } + case .filter(let filt): + active_search = filt + search_open = true + break + // TODO: handle filter searches? + } + } - - let post_res = obj.object as! NostrPostResult - 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)) - case .cancel: - active_sheet = nil - print("post cancelled") + .onReceive(handle_notify(.boost)) { notif in + guard let privkey = self.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)) } - } - .onReceive(timer) { n in - self.damus_state?.pool.connect_to_disconnected() - } + .onReceive(handle_notify(.open_thread)) { obj in + //let ev = obj.object as! NostrEvent + //thread.set_active_event(ev) + //is_thread_open = true + } + .onReceive(handle_notify(.reply)) { notif in + let ev = notif.object as! NostrEvent + self.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)) + } + .onReceive(handle_notify(.unfollow)) { notif in + guard let privkey = self.privkey else { + return + } + + guard let damus = self.damus_state else { + return + } + + let target = notif.object as! FollowTarget + let pk = target.pubkey + + if let ev = unfollow_user(pool: damus.pool, + our_contacts: damus.contacts.event, + pubkey: damus.pubkey, + privkey: privkey, + unfollow: pk) { + notify(.unfollowed, pk) + + damus.contacts.event = ev + damus.contacts.remove_friend(pk) + //friend_events = friend_events.filter { $0.pubkey != pk } + } + } + .onReceive(handle_notify(.follow)) { notif in + guard let privkey = self.privkey else { + return + } + + let fnotify = notif.object as! FollowTarget + guard let damus = self.damus_state else { + return + } + + if let ev = follow_user(pool: damus.pool, + our_contacts: damus.contacts.event, + pubkey: damus.pubkey, + privkey: privkey, + follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) { + notify(.followed, fnotify.pubkey) + + damus_state?.contacts.event = ev + + switch fnotify { + case .pubkey(let pk): + damus.contacts.add_friend_pubkey(pk) + case .contact(let ev): + damus.contacts.add_friend_contact(ev) + } + } + } + .onReceive(handle_notify(.post)) { obj in + guard let privkey = self.privkey else { + return + } + + let post_res = obj.object as! NostrPostResult + 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)) + case .cancel: + active_sheet = nil + print("post cancelled") + } + } + .onReceive(timer) { n in + self.damus_state?.pool.connect_to_disconnected() + } + //} } func switch_timeline(_ timeline: Timeline) { diff --git a/damus/Views/SideMenuView.swift b/damus/Views/SideMenuView.swift new file mode 100644 index 00000000..51623889 --- /dev/null +++ b/damus/Views/SideMenuView.swift @@ -0,0 +1,118 @@ +// +// SideMenuView.swift +// damus +// +// Created by Ben Weeks on 1/6/23. +// Ref: https://blog.logrocket.com/create-custom-collapsible-sidebar-swiftui/ + +import SwiftUI + +struct SideMenuView: View { + let damus_state: DamusState + @Binding var isSidebarVisible: Bool + + var sideBarWidth = UIScreen.main.bounds.size.width * 0.7 + var bgColor: Color = + Color(.init( + red: 52 / 255, + green: 70 / 255, + blue: 182 / 255, + alpha: 1)) + + var body: some View { + if isSidebarVisible { + ZStack { + GeometryReader { _ in + EmptyView() + } + .background(.black.opacity(0.6)) + .opacity(isSidebarVisible ? 1 : 0) + .animation(.easeInOut.delay(0.2), value: isSidebarVisible) + .onTapGesture { + isSidebarVisible.toggle() + } + content + } + .edgesIgnoringSafeArea(.all) + } + } + + var content: some View { + HStack(alignment: .top) { + ZStack(alignment: .top) { + Color("DamusBlack") + + VStack(alignment: .leading, spacing: 20) { + let profile = damus_state.profiles.lookup(id: damus_state.pubkey) + + if let picture = damus_state.profiles.lookup(id: damus_state.pubkey)?.picture { + ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, picture: picture) + } else { + Image(systemName: "person.fill") + } + if let display_name = profile?.display_name { + VStack(alignment: .leading) { + Text(display_name) + .foregroundColor(Color("DamusWhite")) + .font(.headline) + ProfileName(pubkey: damus_state.pubkey, profile: profile, prefix: "@", damus: damus_state, show_friend_confirmed: false) + .font(.callout) + .foregroundColor(.gray) + } + } + + Divider() + + //NavigationView { + let followers = FollowersModel(damus_state: damus_state, target: damus_state.pubkey) + let profile_model = ProfileModel(pubkey: damus_state.pubkey, damus: damus_state) + + NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers) + ) { + Label("Profile", systemImage: "person") + .font(.title2) + .foregroundColor(.accentColor) + } + + NavigationLink(destination: EmptyView()) { + Label("Relays", systemImage: "gear") + .font(.title2) + .foregroundColor(.accentColor) + } + + NavigationLink(destination: ConfigView(state: damus_state)) { + Label("App Settings", systemImage: "xserve") + .font(.title2) + .foregroundColor(.accentColor) + } + + Spacer() + + Button(action: { + //ConfigView(state: damus_state) + notify(.logout, ()) + }, label: { + Label("Sign out", systemImage: "exit") + .font(.title3) + .foregroundColor(Color("DamusWhite")) + }) + } + .padding(.top, 50) + .padding(.bottom, 50) + .padding(.horizontal, 20) + } + .frame(width: sideBarWidth) + .offset(x: isSidebarVisible ? 0 : -sideBarWidth) + .animation(.default, value: isSidebarVisible) + + Spacer() + } + } +} + +struct Previews_SideMenuView_Previews: PreviewProvider { + static var previews: some View { + let ds = test_damus_state() + SideMenuView(damus_state: ds, isSidebarVisible: .constant(true)) + } +}