damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

OnboardingSuggestionsView.swift (9788B)


      1 //
      2 //  OnboardingSuggestionsView.swift
      3 //  damus
      4 //
      5 //  Created by klabo on 7/17/23.
      6 //
      7 
      8 import SwiftUI
      9 
     10 fileprivate let first_post_example_1: String = NSLocalizedString("Hello everybody!\n\nThis is my first post on Damus, I am happy to meet you all 🤙. What’s up?\n\n#introductions", comment: "First post example given to the user during onboarding, as a suggestion as to what they could post first")
     11 fileprivate let first_post_example_2: String = NSLocalizedString("This is my first post on Nostr 💜. I love drawing and folding Origami!\n\nNice to meet you all! #introductions #plebchain ", comment: "First post example given to the user during onboarding, as a suggestion as to what they could post first")
     12 fileprivate let first_post_example_3: String = NSLocalizedString("For #Introductions! I’m a software developer.\n\nMy side interests include languages and I am striving to be a #polyglot - I am a native English speaker and can speak French, German and Japanese.", comment: "First post example given to the user during onboarding, as a suggestion as to what they could post first")
     13 fileprivate let first_post_example_4: String = NSLocalizedString("Howdy! I’m a graphic designer during the day and coder at night, but I’m also trying to spend more time outdoors.\n\nHope to meet folks who are on their own journeys to a peaceful and free life!", comment: "First post example given to the user during onboarding, as a suggestion as to what they could post first")
     14 
     15 struct OnboardingSuggestionsView: View {
     16 
     17     @StateObject var model: SuggestedUsersViewModel
     18     @State var current_page: Int = 0
     19     let first_post_examples: [String] = [first_post_example_1, first_post_example_2, first_post_example_3, first_post_example_4]
     20     let initial_text_suffix: String = "\n\n#introductions"
     21 
     22     @Environment(\.dismiss) var dismiss
     23     
     24     func next_page() {
     25         withAnimation {
     26             current_page += 1
     27         }
     28     }
     29     
     30     private var canLeaveInterestSelectionPage: Bool {
     31         let count = model.interests.count
     32         return count > 0
     33     }
     34     
     35     /// Save the user's selected interests to NDB
     36     private func saveInterestsToNdb() {
     37         // Convert the selected interests to hashtags for the NIP51 interest list
     38         let interestItems = model.interests.map { interest in
     39             NIP51.InterestList.InterestItem.hashtag(interest.rawValue)
     40         }
     41         
     42         // Create the interest list
     43         let interestList = NIP51.InterestList(interests: Array(interestItems))
     44         
     45         // Convert to a NostrEvent and send to NDB
     46         guard let keypair = model.damus_state.keypair.to_full(),
     47               let event = interestList.toNostrEvent(keypair: keypair, timestamp: nil) else {
     48             return      // Not a big deal, fail silently
     49         }
     50         
     51         // Send the event to NostrDB to allow us to retrieve later
     52         // Did not send this to the network yet because:
     53         // 1. I believe we should add an opt-out/opt-in button.
     54         // 2. If we do, and the user accepts to share it, it will be an awkward situation considering:
     55         //     - We don't show that anywhere else yet
     56         //     - We don't have other mechanisms to allow the user to edit this yet
     57         //
     58         // Therefore, it is better to just save it locally, and retrieve this once we build out https://github.com/damus-io/damus/issues/3042
     59         model.damus_state.nostrNetwork.pool.send_raw_to_local_ndb(.typical(.event(event)))
     60     }
     61 
     62     var body: some View {
     63         NavigationView {
     64             TabView(selection: $current_page) {
     65                 InterestSelectionView(damus_state: model.damus_state, next_page: {
     66                     self.next_page()
     67                 }, selectedInterests: $model.interests, isNextEnabled: canLeaveInterestSelectionPage)
     68                     .navigationTitle(NSLocalizedString("Select your interests", comment: "Title for a screen asking the user for interests"))
     69                     .navigationBarTitleDisplayMode(.inline)
     70                     .tag(0)
     71                 
     72                 if canLeaveInterestSelectionPage {
     73                     
     74                     OnboardingContentSettings(model: model, next_page: self.next_page, settings: model.damus_state.settings, selectedInterests: $model.interests)
     75                         .navigationTitle(NSLocalizedString("Content settings", comment: "Title for an onboarding screen showing user some content settings"))
     76                         .navigationBarTitleDisplayMode(.inline)
     77                         .tag(1)
     78                     
     79                     SuggestedUsersPageView(model: model, next_page: self.next_page)
     80                         .navigationTitle(NSLocalizedString("Who to Follow", comment: "Title for a screen displaying suggestions of who to follow"))
     81                         .navigationBarTitleDisplayMode(.inline)
     82                         .navigationBarItems(leading: Button(action: {
     83                             self.next_page()
     84                         }, label: {
     85                             Text("Skip", comment: "Button to dismiss the suggested users screen")
     86                                 .font(.subheadline.weight(.semibold))
     87                         })
     88                             .accessibilityIdentifier(AppAccessibilityIdentifiers.onboarding_sheet_skip_button.rawValue)
     89                         )
     90                         .tag(2)
     91                     
     92                     PostView(
     93                         action: .posting(.user(model.damus_state.pubkey)),
     94                         damus_state: model.damus_state,
     95                         prompt_view: {
     96                             AnyView(
     97                                 HStack {
     98                                     Image(systemName: "sparkles")
     99                                     Text("Add your first post", comment: "Prompt given to the user during onboarding, suggesting them to write their first post")
    100                                 }
    101                                     .foregroundColor(.secondary)
    102                                     .font(.callout)
    103                                     .padding(.top, 10)
    104                             )
    105                         },
    106                         placeholder_messages: self.first_post_examples,
    107                         initial_text_suffix: self.initial_text_suffix
    108                     )
    109                     .onReceive(handle_notify(.post)) { _ in
    110                         // NOTE: Even though PostView already calls `dismiss`, that is not guaranteed to work under deeply nested views.
    111                         // Thus, we should also call `dismiss` from here (a direct subview of a sheet), which is explicitly supported by Apple.
    112                         // See https://github.com/damus-io/damus/issues/1726 for more context and information
    113                         dismiss()
    114                     }
    115                     .tag(3)
    116                 }
    117             }
    118             .tabViewStyle(.page(indexDisplayMode: .never))
    119             .onChange(of: current_page) { newPage in
    120                 // If the user just swiped from the interests page (0) to the next page (1),
    121                 // save their interests to NDB
    122                 if newPage == 1 && current_page == 1 {
    123                     saveInterestsToNdb()
    124                 }
    125             }
    126         }
    127     }
    128 }
    129 
    130 fileprivate struct SuggestedUsersPageView: View {
    131     var model: SuggestedUsersViewModel
    132     var next_page: (() -> Void)
    133     
    134     var body: some View {
    135         VStack {
    136             if let suggestions = model.suggestions {
    137                 List {
    138                     ForEach(suggestions, id: \.self) { followPack in
    139                         Section {
    140                             ForEach(followPack.publicKeys, id: \.self) { pk in
    141                                 if let usersInterests = model.interestUserMap[pk],
    142                                    !usersInterests.intersection(model.interests).isEmpty && usersInterests.intersection(model.disinterests).isEmpty,
    143                                    let user = model.suggestedUser(pubkey: pk) {
    144                                     SuggestedUserView(user: user, damus_state: model.damus_state)
    145                                 }
    146                             }
    147                         } header: {
    148                             SuggestedUsersSectionHeader(followPack: followPack, model: model)
    149                         }
    150                     }
    151                 }
    152                 .listStyle(.plain)
    153             }
    154             else {
    155                 ProgressView()
    156             }
    157             
    158             Spacer()
    159             
    160             Button(action: {
    161                 self.next_page()
    162             }) {
    163                 Text("Continue", comment: "Button to dismiss suggested users view and continue to the main app")
    164                     .frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
    165             }
    166             .buttonStyle(GradientButtonStyle())
    167             .padding([.leading, .trailing], 24)
    168             .padding(.bottom, 16)
    169         }
    170     }
    171 }
    172 
    173 struct SuggestedUsersSectionHeader: View {
    174     let followPack: FollowPackEvent
    175     let model: SuggestedUsersViewModel
    176     var body: some View {
    177         HStack {
    178             Text(followPack.title ?? NSLocalizedString("Untitled Follow Pack", comment: "Default title for a follow pack if no title is specified"))
    179             Spacer()
    180             Button(NSLocalizedString("Follow All", comment: "Button to follow all users in this section")) {
    181                 model.follow(pubkeys: followPack.publicKeys)
    182             }
    183             .font(.subheadline.weight(.semibold))
    184         }
    185     }
    186 }
    187 
    188 struct SuggestedUsersView_Previews: PreviewProvider {
    189     static var previews: some View {
    190         OnboardingSuggestionsView(model: try! SuggestedUsersViewModel(damus_state: test_damus_state))
    191     }
    192 }