damus

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

commit c9c51c6d4ad7cb4ed102da8d097d799ff11a9e43
parent b63159a29f55539aad2c84ff543d501005c733dc
Author: Joel Klabo <joelklabo@gmail.com>
Date:   Thu, 30 Mar 2023 20:59:24 -0700

Profile Picture Upload

Changelog-Added: Profile Picture Upload
Closes: #849

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 12++++++++++++
Mdamus/Models/CreateAccountModel.swift | 1+
Mdamus/Nostr/NostrMetadata.swift | 2+-
Mdamus/Views/AttachMediaUtility.swift | 112-------------------------------------------------------------------------------
Mdamus/Views/CreateAccountView.swift | 11++++++++++-
Mdamus/Views/EditMetadataView.swift | 8+++++++-
Adamus/Views/ImagePicker.swift | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/PostView.swift | 4++--
Adamus/Views/Profile/EditProfilePictureControl.swift | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/Profile/ProfilePicView.swift | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Views/Profile/ProfilePictureSelector.swift | 20++++++++++++++++++--
11 files changed, 272 insertions(+), 119 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -253,10 +253,12 @@ DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; }; E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; }; E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; }; + F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757933929D7AECD007DEAC1 /* ImagePicker.swift */; }; F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12C29A1855400E10810 /* BookmarksManager.swift */; }; F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F75BA12E29A18EF500E10810 /* BookmarksView.swift */; }; F7908E92298B0F0700AB113A /* RelayDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E91298B0F0700AB113A /* RelayDetailView.swift */; }; F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; }; + F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */; }; F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; }; F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParicipantsView.swift */; }; /* End PBXBuildFile section */ @@ -642,10 +644,12 @@ DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; }; E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; }; E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; }; + F757933929D7AECD007DEAC1 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; }; F75BA12C29A1855400E10810 /* BookmarksManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksManager.swift; sourceTree = "<group>"; }; F75BA12E29A18EF500E10810 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; }; F7908E91298B0F0700AB113A /* RelayDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayDetailView.swift; sourceTree = "<group>"; }; F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIPURLBuilder.swift; sourceTree = "<group>"; }; + F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfilePictureControl.swift; sourceTree = "<group>"; }; F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismiss.swift; sourceTree = "<group>"; }; F7F0BA262978E54D009531F3 /* ParicipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParicipantsView.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -859,6 +863,7 @@ 4C75EFAC28049CFB0006080F /* PostButton.swift */, 4C75EFA327FA577B0006080F /* PostView.swift */, 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */, + F757933929D7AECD007DEAC1 /* ImagePicker.swift */, 9C83F89229A937B900136C08 /* TextViewWrapper.swift */, 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */, 4C3AC7A42836987600E1F516 /* MainTabView.swift */, @@ -985,6 +990,7 @@ children = ( 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */, 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */, + F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */, 4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */, 4C8682862814DE470026224F /* ProfileView.swift */, 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */, @@ -1428,6 +1434,7 @@ 4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */, 4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */, 4C75EFB728049D990006080F /* RelayPool.swift in Sources */, + F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */, 4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */, 4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */, 4CCEB7A929B29DD50078AA28 /* SearchResultsModel.swift in Sources */, @@ -1542,6 +1549,7 @@ 4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */, 4C363A8828236948006E126D /* BlocksView.swift in Sources */, 4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */, + F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */, 4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */, 4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */, 4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */, @@ -1931,7 +1939,9 @@ INFOPLIST_FILE = damus/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Damus; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + INFOPLIST_KEY_NSCameraUsageDescription = "Damus needs access to your camera if you want to upload photos from it"; INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key"; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Damus needs access to your microphone if you want to upload recorded videos from it"; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1973,7 +1983,9 @@ INFOPLIST_FILE = damus/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Damus; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + INFOPLIST_KEY_NSCameraUsageDescription = "Damus needs access to your camera if you want to upload photos from it"; INFOPLIST_KEY_NSFaceIDUsageDescription = "Local authentication to access private key"; + INFOPLIST_KEY_NSMicrophoneUsageDescription = "Damus needs access to your microphone if you want to upload recorded videos from it"; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Granting Damus access to your photos allows you to save images."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/damus/Models/CreateAccountModel.swift b/damus/Models/CreateAccountModel.swift @@ -14,6 +14,7 @@ class CreateAccountModel: ObservableObject { @Published var about: String = "" @Published var pubkey: String = "" @Published var privkey: String = "" + @Published var profile_image: String? = nil var pubkey_bech32: String { return bech32_pubkey(self.pubkey) ?? "" diff --git a/damus/Nostr/NostrMetadata.swift b/damus/Nostr/NostrMetadata.swift @@ -21,5 +21,5 @@ struct NostrMetadata: Codable { } func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata { - return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: nil, banner: nil, lud06: nil, lud16: nil) + return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: model.profile_image, banner: nil, lud06: nil, lud16: nil) } diff --git a/damus/Views/AttachMediaUtility.swift b/damus/Views/AttachMediaUtility.swift @@ -80,118 +80,6 @@ func create_upload_request(mediaToUpload: MediaUpload, mediaUploader: MediaUploa } } -extension PostView { - struct ImagePicker: UIViewControllerRepresentable { - - @Environment(\.presentationMode) - private var presentationMode - - let sourceType: UIImagePickerController.SourceType - let damusState: DamusState - let onImagePicked: (URL) -> Void - let onVideoPicked: (URL) -> Void - - final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { - @Binding private var presentationMode: PresentationMode - private let sourceType: UIImagePickerController.SourceType - private let onImagePicked: (URL) -> Void - private let onVideoPicked: (URL) -> Void - - init(presentationMode: Binding<PresentationMode>, - sourceType: UIImagePickerController.SourceType, - onImagePicked: @escaping (URL) -> Void, - onVideoPicked: @escaping (URL) -> Void) { - _presentationMode = presentationMode - self.sourceType = sourceType - self.onImagePicked = onImagePicked - self.onVideoPicked = onVideoPicked - } - - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { - if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL { - // Handle the selected video - onVideoPicked(videoURL) - } else if let imageURL = info[UIImagePickerController.InfoKey.imageURL] as? URL { - // Handle the selected image - onImagePicked(imageURL) - } else if let cameraImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { - if let imageURL = saveImageToTemporaryFolder(image: cameraImage, imageType: "jpeg") { - onImagePicked(imageURL) - } - } else if let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { - if let editedImageURL = saveImageToTemporaryFolder(image: editedImage) { - onImagePicked(editedImageURL) - } - } - presentationMode.dismiss() - } - - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - presentationMode.dismiss() - } - - func saveImageToTemporaryFolder(image: UIImage, imageType: String = "png") -> URL? { - // Convert UIImage to Data - let imageData: Data? - if imageType.lowercased() == "jpeg" { - imageData = image.jpegData(compressionQuality: 1.0) - } else { - imageData = image.pngData() - } - - guard let data = imageData else { - print("Failed to convert UIImage to Data.") - return nil - } - - // Generate a temporary URL with a unique filename - let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) - let uniqueImageName = "\(UUID().uuidString).\(imageType)" - let temporaryImageURL = temporaryDirectoryURL.appendingPathComponent(uniqueImageName) - - // Save the image data to the temporary URL - do { - try data.write(to: temporaryImageURL) - return temporaryImageURL - } catch { - print("Error saving image data to temporary URL: \(error.localizedDescription)") - return nil - } - } - } - - func makeCoordinator() -> Coordinator { - return Coordinator(presentationMode: presentationMode, - sourceType: sourceType, - onImagePicked: { url in - // Handle the selected image URL - onImagePicked(url) - }, - onVideoPicked: { videoURL in - // Handle the selected video URL - onVideoPicked(videoURL) - }) - } - - func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController { - let picker = UIImagePickerController() - picker.sourceType = sourceType - let mediaUploader = get_media_uploader(damusState.keypair.pubkey) - picker.mediaTypes = ["public.image", "com.compuserve.gif"] - if mediaUploader.supportsVideo { - picker.mediaTypes.append("public.movie") - } - picker.delegate = context.coordinator - return picker - } - - func updateUIViewController(_ uiViewController: UIImagePickerController, - context: UIViewControllerRepresentableContext<ImagePicker>) { - - } - } -} - extension NSMutableData { func appendString(string: String) { guard let data = string.data(using: String.Encoding.utf8, allowLossyConversion: true) else { diff --git a/damus/Views/CreateAccountView.swift b/damus/Views/CreateAccountView.swift @@ -9,9 +9,12 @@ import SwiftUI struct CreateAccountView: View { @StateObject var account: CreateAccountModel = CreateAccountModel() + @StateObject var profileUploadViewModel = ProfileUploadingViewModel() + @State var is_light: Bool = false @State var is_done: Bool = false @State var reading_eula: Bool = false + @State var profile_image: URL? = nil func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View { return VStack(alignment: .leading, spacing: 10.0, content: content) @@ -32,7 +35,7 @@ struct CreateAccountView: View { .font(.title.bold()) .foregroundColor(.white) - ProfilePictureSelector(pubkey: account.pubkey) + ProfilePictureSelector(pubkey: account.pubkey, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:)) HStack(alignment: .top) { VStack { @@ -81,6 +84,8 @@ struct CreateAccountView: View { self.is_done = true } .padding() + .disabled(profileUploadViewModel.isLoading) + .opacity(profileUploadViewModel.isLoading ? 0.5 : 1) } .padding(.leading, 14.0) .padding(.trailing, 20.0) @@ -91,6 +96,10 @@ struct CreateAccountView: View { .navigationBarBackButtonHidden(true) .navigationBarItems(leading: BackNav()) } + + func uploadedProfilePicture(image_url: URL?) { + account.profile_image = image_url?.absoluteString + } } struct BackNav: View { diff --git a/damus/Views/EditMetadataView.swift b/damus/Views/EditMetadataView.swift @@ -67,6 +67,7 @@ struct EditMetadataView: View { @Environment(\.colorScheme) var colorScheme @State var confirm_ln_address: Bool = false + @StateObject var profileUploadViewModel = ProfileUploadingViewModel() init (damus_state: DamusState) { self.damus_state = damus_state @@ -126,7 +127,7 @@ struct EditMetadataView: View { let pfp_size: CGFloat = 90.0 HStack(alignment: .center) { - ProfilePicView(pubkey: damus_state.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles) + ProfilePictureSelector(pubkey: damus_state.pubkey, damus_state: damus_state, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:)) .offset(y: -(pfp_size/2.0)) // Increase if set a frame Spacer() @@ -214,6 +215,7 @@ struct EditMetadataView: View { dismiss() } } + .disabled(profileUploadViewModel.isLoading) .alert(NSLocalizedString("Invalid Tip Address", comment: "Title of alerting as invalid tip address."), isPresented: $confirm_ln_address) { Button(NSLocalizedString("Ok", comment: "Button to dismiss the alert.")) { } @@ -224,6 +226,10 @@ struct EditMetadataView: View { } .ignoresSafeArea(edges: .top) } + + func uploadedProfilePicture(image_url: URL?) { + picture = image_url?.absoluteString ?? "" + } } struct EditMetadataView_Previews: PreviewProvider { diff --git a/damus/Views/ImagePicker.swift b/damus/Views/ImagePicker.swift @@ -0,0 +1,83 @@ +// +// ImagePicker.swift +// damus +// +// Created by Swift on 3/31/23. +// + +import UIKit +import SwiftUI + +struct ImagePicker: UIViewControllerRepresentable { + + @Environment(\.presentationMode) + private var presentationMode + + let sourceType: UIImagePickerController.SourceType + let pubkey: String + var imagesOnly: Bool = false + let onImagePicked: (URL) -> Void + let onVideoPicked: (URL) -> Void + + final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { + @Binding private var presentationMode: PresentationMode + private let sourceType: UIImagePickerController.SourceType + private let onImagePicked: (URL) -> Void + private let onVideoPicked: (URL) -> Void + + init(presentationMode: Binding<PresentationMode>, + sourceType: UIImagePickerController.SourceType, + onImagePicked: @escaping (URL) -> Void, + onVideoPicked: @escaping (URL) -> Void) { + _presentationMode = presentationMode + self.sourceType = sourceType + self.onImagePicked = onImagePicked + self.onVideoPicked = onVideoPicked + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL { + // Handle the selected video + onVideoPicked(videoURL) + } else if let imageURL = info[UIImagePickerController.InfoKey.imageURL] as? URL { + // Handle the selected image + self.onImagePicked(imageURL) + } + presentationMode.dismiss() + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + presentationMode.dismiss() + } + } + + func makeCoordinator() -> Coordinator { + return Coordinator(presentationMode: presentationMode, + sourceType: sourceType, + onImagePicked: { url in + // Handle the selected image URL + onImagePicked(url) + }, + onVideoPicked: { videoURL in + // Handle the selected video URL + onVideoPicked(videoURL) + }) + } + + func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController { + let picker = UIImagePickerController() + picker.sourceType = sourceType + let mediaUploader = get_media_uploader(pubkey) + picker.mediaTypes = ["public.image", "com.compuserve.gif"] + if mediaUploader.supportsVideo && !imagesOnly { + picker.mediaTypes.append("public.movie") + } + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIImagePickerController, + context: UIViewControllerRepresentableContext<ImagePicker>) { + + } +} diff --git a/damus/Views/PostView.swift b/damus/Views/PostView.swift @@ -246,14 +246,14 @@ struct PostView: View { } .padding() .sheet(isPresented: $attach_media) { - ImagePicker(sourceType: .photoLibrary, damusState: damus_state) { img in + ImagePicker(sourceType: .photoLibrary, pubkey: damus_state.pubkey) { img in handle_upload(media: .image(img)) } onVideoPicked: { url in handle_upload(media: .video(url)) } } .sheet(isPresented: $attach_camera) { - ImagePicker(sourceType: .camera, damusState: damus_state) { img in + ImagePicker(sourceType: .camera, pubkey: damus_state.pubkey) { img in handle_upload(media: .image(img)) } onVideoPicked: { url in handle_upload(media: .video(url)) diff --git a/damus/Views/Profile/EditProfilePictureControl.swift b/damus/Views/Profile/EditProfilePictureControl.swift @@ -0,0 +1,84 @@ +// +// ProfilePictureEditView.swift +// damus +// +// Created by Joel Klabo on 3/30/23. +// + +import SwiftUI + +struct EditProfilePictureControl: View { + + let pubkey: String + @Binding var profile_image: URL? + @ObservedObject var viewModel: ProfileUploadingViewModel + let callback: (URL?) -> Void + + @StateObject var image_upload: ImageUploadModel = ImageUploadModel() + + @State private var show_camera = false + @State private var show_library = false + + var body: some View { + Menu { + Button(action: { + self.show_library = true + }) { + Text("Choose from Library", comment: "Option to select photo from library") + } + + Button(action: { + self.show_camera = true + }) { + Text("Take Photo", comment: "Option to take a photo with the camera") + } + } label: { + if viewModel.isLoading { + ProgressView() + } else { + Image(systemName: "camera") + .resizable() + .scaledToFit() + .frame(width: 25, height: 25) + .foregroundColor(DamusColors.white) + } + } + .sheet(isPresented: $show_camera) { + ImagePicker(sourceType: .camera, pubkey: pubkey, imagesOnly: true) { img in + handle_upload(media: .image(img)) + } onVideoPicked: { url in + print("Cannot upload videos as profile image") + } + } + .sheet(isPresented: $show_library) { + ImagePicker(sourceType: .photoLibrary, pubkey: pubkey, imagesOnly: true) { img in + handle_upload(media: .image(img)) + } onVideoPicked: { url in + print("Cannot upload videos as profile image") + } + } + } + + private func handle_upload(media: MediaUpload) { + viewModel.isLoading = true + let uploader = get_media_uploader(pubkey) + Task { + let res = await image_upload.start(media: media, uploader: uploader) + + switch res { + case .success(let urlString): + let url = URL(string: urlString) + profile_image = url + callback(url) + case .failed(let error): + if let error { + print("Error uploading profile image \(error.localizedDescription)") + } else { + print("Error uploading image :(") + } + callback(nil) + } + viewModel.isLoading = false + } + } +} diff --git a/damus/Views/Profile/ProfilePicView.swift b/damus/Views/Profile/ProfilePicView.swift @@ -32,6 +32,60 @@ func pfp_line_width(_ h: Highlight) -> CGFloat { } } +struct EditProfilePictureView: View { + + @Binding var url: URL? + + let pubkey: String + let size: CGFloat + let highlight: Highlight + + var damus_state: DamusState? + + var PlaceholderColor: Color { + return id_to_color(pubkey) + } + + var Placeholder: some View { + PlaceholderColor + .frame(width: size, height: size) + .clipShape(Circle()) + .overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight))) + .padding(2) + } + + var body: some View { + ZStack { + Color(uiColor: .systemBackground) + + KFAnimatedImage(get_profile_url()) + .imageContext(.pfp) + .cancelOnDisappear(true) + .configure { view in + view.framePreloadCount = 3 + } + .placeholder { _ in + Placeholder + } + .scaledToFill() + .opacity(0.5) + } + .frame(width: size, height: size) + .clipShape(Circle()) + .overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight))) + } + + private func get_profile_url() -> URL? { + if let url { + return url + } else if let state = damus_state, let picture = state.profiles.lookup(id: pubkey)?.picture { + return URL(string: picture) + } else { + return url ?? URL(string: robohash(pubkey)) + } + } +} + struct InnerProfilePicView: View { let url: URL? diff --git a/damus/Views/Profile/ProfilePictureSelector.swift b/damus/Views/Profile/ProfilePictureSelector.swift @@ -7,13 +7,27 @@ import SwiftUI +import Combine + +class ProfileUploadingViewModel: ObservableObject { + @Published var isLoading: Bool = false +} + struct ProfilePictureSelector: View { + let pubkey: String + var size: CGFloat = 80.0 + var damus_state: DamusState? + @ObservedObject var viewModel: ProfileUploadingViewModel + let callback: (URL?) -> Void + + @State var profile_image: URL? = nil var body: some View { let highlight: Highlight = .custom(Color.white, 2.0) ZStack { - ProfilePicView(pubkey: pubkey, size: 80.0, highlight: highlight, profiles: Profiles()) + EditProfilePictureView(url: $profile_image, pubkey: pubkey, size: size, highlight: highlight, damus_state: damus_state) + EditProfilePictureControl(pubkey: pubkey, profile_image: $profile_image, viewModel: viewModel, callback: callback) } } } @@ -21,6 +35,8 @@ struct ProfilePictureSelector: View { struct ProfilePictureSelector_Previews: PreviewProvider { static var previews: some View { let test_pubkey = "ff48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846" - ProfilePictureSelector(pubkey: test_pubkey) + ProfilePictureSelector(pubkey: test_pubkey, viewModel: ProfileUploadingViewModel()) { _ in + // + } } }