Profile Picture Upload
Changelog-Added: Profile Picture Upload Closes: #849
This commit is contained in:
committed by
William Casarin
parent
b63159a29f
commit
c9c51c6d4a
@@ -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;
|
||||
|
||||
@@ -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) ?? ""
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
83
damus/Views/ImagePicker.swift
Normal file
83
damus/Views/ImagePicker.swift
Normal file
@@ -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>) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
84
damus/Views/Profile/EditProfilePictureControl.swift
Normal file
84
damus/Views/Profile/EditProfilePictureControl.swift
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user