damus

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

EditMetadataView.swift (10484B)


      1 //
      2 //  EditMetadataView.swift
      3 //  damus
      4 //
      5 //  Created by Thomas Tastet on 23/12/2022.
      6 //
      7 
      8 import SwiftUI
      9 import Combine
     10 
     11 let PPM_SIZE: CGFloat = 80.0
     12 let BANNER_HEIGHT: CGFloat = 150.0;
     13 
     14 func isHttpsUrl(_ string: String) -> Bool {
     15     let urlRegEx = "^https://.*$"
     16     let urlTest = NSPredicate(format:"SELF MATCHES %@", urlRegEx)
     17     return urlTest.evaluate(with: string)
     18 }
     19 
     20 func isImage(_ urlString: String) -> Bool {
     21     let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"]
     22 
     23     guard let url = URL(string: urlString) else {
     24         return false
     25     }
     26 
     27     var result = false
     28     let semaphore = DispatchSemaphore(value: 0)
     29 
     30     let task = URLSession.shared.dataTask(with: url) { data, response, error in
     31         if let error = error {
     32             print(error)
     33             semaphore.signal()
     34             return
     35         }
     36 
     37         guard let httpResponse = response as? HTTPURLResponse,
     38               let contentType = httpResponse.allHeaderFields["Content-Type"] as? String else {
     39             semaphore.signal()
     40             return
     41         }
     42 
     43         if imageTypes.contains(contentType.lowercased()) {
     44             result = true
     45         }
     46 
     47         semaphore.signal()
     48     }
     49 
     50     task.resume()
     51     semaphore.wait()
     52 
     53     return result
     54 }
     55 
     56 struct EditMetadataView: View {
     57     let damus_state: DamusState
     58     @State var display_name: String
     59     @State var about: String
     60     @State var picture: String
     61     @State var banner: String
     62     @State var nip05: String
     63     @State var name: String
     64     @State var ln: String
     65     @State var website: String
     66     
     67     @Environment(\.dismiss) var dismiss
     68     @Environment(\.colorScheme) var colorScheme
     69 
     70     @State var confirm_ln_address: Bool = false
     71     @StateObject var profileUploadViewModel = ProfileUploadingViewModel()
     72     
     73     init (damus_state: DamusState) {
     74         self.damus_state = damus_state
     75         let data = damus_state.profiles.lookup(id: damus_state.pubkey)
     76         
     77         _name = State(initialValue: data?.name ?? "")
     78         _display_name = State(initialValue: data?.display_name ?? "")
     79         _about = State(initialValue: data?.about ?? "")
     80         _website = State(initialValue: data?.website ?? "")
     81         _picture = State(initialValue: data?.picture ?? "")
     82         _banner = State(initialValue: data?.banner ?? "")
     83         _nip05 = State(initialValue: data?.nip05 ?? "")
     84         _ln = State(initialValue: data?.lud16 ?? data?.lud06 ?? "")
     85     }
     86     
     87     func imageBorderColor() -> Color {
     88             colorScheme == .light ? DamusColors.white : DamusColors.black
     89         }
     90     
     91     func save() {
     92         let metadata = NostrMetadata(
     93             display_name: display_name,
     94             name: name,
     95             about: about,
     96             website: website,
     97             nip05: nip05.isEmpty ? nil : nip05,
     98             picture: picture.isEmpty ? nil : picture,
     99             banner: banner.isEmpty ? nil : banner,
    100             lud06: ln.contains("@") ? nil : ln,
    101             lud16: ln.contains("@") ? ln : nil
    102         );
    103         
    104         let m_metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: metadata)
    105         
    106         if let metadata_ev = m_metadata_ev {
    107             damus_state.postbox.send(metadata_ev)
    108         }
    109     }
    110 
    111     func is_ln_valid(ln: String) -> Bool {
    112         return ln.contains("@") || ln.lowercased().starts(with: "lnurl")
    113     }
    114     
    115     var nip05_parts: NIP05? {
    116         return NIP05.parse(nip05)
    117     }
    118     
    119     var TopSection: some View {
    120         ZStack(alignment: .top) {
    121             GeometryReader { geo in
    122                 BannerImageView(pubkey: damus_state.pubkey, profiles: damus_state.profiles)
    123                     .aspectRatio(contentMode: .fill)
    124                     .frame(width: geo.size.width, height: BANNER_HEIGHT)
    125                     .clipped()
    126             }.frame(height: BANNER_HEIGHT)
    127             VStack(alignment: .leading) {
    128                 let pfp_size: CGFloat = 90.0
    129 
    130                 HStack(alignment: .center) {
    131                     ProfilePictureSelector(pubkey: damus_state.pubkey, damus_state: damus_state, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:))
    132                         .offset(y: -(pfp_size/2.0)) // Increase if set a frame
    133 
    134                    Spacer()
    135                 }.padding(.bottom,-(pfp_size/2.0))
    136             }
    137             .padding(.horizontal,18)
    138             .padding(.top,BANNER_HEIGHT)
    139         }
    140     }
    141     
    142     var body: some View {
    143         VStack(alignment: .leading) {
    144             TopSection
    145             Form {
    146                 Section(NSLocalizedString("Your Name", comment: "Label for Your Name section of user profile form.")) {
    147                     TextField("Satoshi Nakamoto", text: $display_name)
    148                         .autocorrectionDisabled(true)
    149                         .textInputAutocapitalization(.never)
    150                 }
    151                 
    152                 Section(NSLocalizedString("Username", comment: "Label for Username section of user profile form.")) {
    153                     TextField("satoshi", text: $name)
    154                         .autocorrectionDisabled(true)
    155                         .textInputAutocapitalization(.never)
    156 
    157                 }
    158                 
    159                 Section (NSLocalizedString("Profile Picture", comment: "Label for Profile Picture section of user profile form.")) {
    160                     TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $picture)
    161                         .autocorrectionDisabled(true)
    162                         .textInputAutocapitalization(.never)
    163                 }
    164                 
    165                 Section (NSLocalizedString("Banner Image", comment: "Label for Banner Image section of user profile form.")) {
    166                                     TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $banner)
    167                                         .autocorrectionDisabled(true)
    168                                         .textInputAutocapitalization(.never)
    169                                 }
    170                 
    171                 Section(NSLocalizedString("Website", comment: "Label for Website section of user profile form.")) {
    172                     TextField(NSLocalizedString("https://jb55.com", comment: "Placeholder example text for website URL for user profile."), text: $website)
    173                         .autocorrectionDisabled(true)
    174                         .textInputAutocapitalization(.never)
    175                 }
    176                 
    177                 Section(NSLocalizedString("About Me", comment: "Label for About Me section of user profile form.")) {
    178                     let placeholder = NSLocalizedString("Absolute Boss", comment: "Placeholder text for About Me description.")
    179                     ZStack(alignment: .topLeading) {
    180                         TextEditor(text: $about)
    181                             .textInputAutocapitalization(.sentences)
    182                             .frame(minHeight: 20, alignment: .leading)
    183                             .multilineTextAlignment(.leading)
    184                         Text(about.isEmpty ? placeholder : about)
    185                             .padding(.leading, 4)
    186                             .opacity(about.isEmpty ? 1 : 0)
    187                             .foregroundColor(Color(uiColor: .placeholderText))
    188                     }
    189                 }
    190                 
    191                 Section(NSLocalizedString("Bitcoin Lightning Tips", comment: "Label for Bitcoin Lightning Tips section of user profile form.")) {
    192                     TextField(NSLocalizedString("Lightning Address or LNURL", comment: "Placeholder text for entry of Lightning Address or LNURL."), text: $ln)
    193                         .autocorrectionDisabled(true)
    194                         .textInputAutocapitalization(.never)
    195                 }
    196                                 
    197                 Section(content: {
    198                     TextField(NSLocalizedString("jb55@jb55.com", comment: "Placeholder example text for identifier used for NIP-05 verification."), text: $nip05)
    199                         .autocorrectionDisabled(true)
    200                         .textInputAutocapitalization(.never)
    201                         .onReceive(Just(nip05)) { newValue in
    202                             self.nip05 = newValue.trimmingCharacters(in: .whitespaces)
    203                         }
    204                 }, header: {
    205                     Text("NIP-05 Verification", comment: "Label for NIP-05 Verification section of user profile form.")
    206                 }, footer: {
    207                     if let parts = nip05_parts {
    208                         Text("'\(parts.username)' at '\(parts.host)' will be used for verification", comment: "Description of how the nip05 identifier would be used for verification.")
    209                     } else if !nip05.isEmpty {
    210                         Text("'\(nip05)' is an invalid NIP-05 identifier. It should look like an email.", comment: "Description of why the nip05 identifier is invalid.")
    211                     } else {
    212                         Text("")    // without this, the keyboard dismisses unnecessarily when the footer changes state
    213                     }
    214                 })
    215 
    216                 Button(NSLocalizedString("Save", comment: "Button for saving profile.")) {
    217                     if !ln.isEmpty && !is_ln_valid(ln: ln) {
    218                         confirm_ln_address = true
    219                     } else {
    220                         save()
    221                         dismiss()
    222                     }
    223                 }
    224                 .disabled(profileUploadViewModel.isLoading)
    225                 .alert(NSLocalizedString("Invalid Tip Address", comment: "Title of alerting as invalid tip address."), isPresented: $confirm_ln_address) {
    226                     Button(NSLocalizedString("Ok", comment: "Button to dismiss the alert.")) {
    227                     }
    228                 } message: {
    229                     Text("The address should either begin with LNURL or should look like an email address.", comment: "Giving the description of the alert message.")
    230                 }
    231             }
    232         }
    233         .ignoresSafeArea(edges: .top)
    234         .background(Color(.systemGroupedBackground))
    235     }
    236     
    237     func uploadedProfilePicture(image_url: URL?) {
    238         picture = image_url?.absoluteString ?? ""
    239     }
    240 }
    241 
    242 struct EditMetadataView_Previews: PreviewProvider {
    243     static var previews: some View {
    244         EditMetadataView(damus_state: test_damus_state())
    245     }
    246 }