Choose avatar.
This commit is contained in:
parent
9de64db689
commit
537f344cf2
|
@ -266,3 +266,4 @@
|
|||
"editProfile.title.save" = "Save";
|
||||
"editProfile.title.accountSaved" = "Profile has been updated.";
|
||||
"editProfile.error.saveAccountFailed" = "Saving profile failed.";
|
||||
"editProfile.error.loadingAvatarFailed" = "Loading avatar failed.";
|
||||
|
|
|
@ -266,3 +266,4 @@
|
|||
"editProfile.title.save" = "Zapisz";
|
||||
"editProfile.title.accountSaved" = "Profil zaktualizowano.";
|
||||
"editProfile.error.saveAccountFailed" = "Błąd podczas aktualizacji profilu.";
|
||||
"editProfile.error.loadingAvatarFailed" = "Błąd podczas wczytywania zdjęcia.";
|
||||
|
|
|
@ -198,6 +198,7 @@
|
|||
F8CEEDF829ABADDD00DBED66 /* UIImage+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEEDF729ABADDD00DBED66 /* UIImage+Size.swift */; };
|
||||
F8CEEDFA29ABAFD200DBED66 /* ImageFileTranseferable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CEEDF929ABAFD200DBED66 /* ImageFileTranseferable.swift */; };
|
||||
F8E6D03329CDD52500416CCA /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6D03229CDD52500416CCA /* EditProfileView.swift */; };
|
||||
F8E6D03529CE161B00416CCA /* UIImage+Jpeg.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6D03429CE161B00416CCA /* UIImage+Jpeg.swift */; };
|
||||
F8E9391F29C0BCFA002BB3B8 /* ImageContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E9391E29C0BCFA002BB3B8 /* ImageContextMenu.swift */; };
|
||||
F8E9392129C0DA7E002BB3B8 /* LazyImageState+ImageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E9392029C0DA7E002BB3B8 /* LazyImageState+ImageResponse.swift */; };
|
||||
F8F6E44229BC58F20004795E /* Vernissage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */; };
|
||||
|
@ -412,6 +413,7 @@
|
|||
F8CEEDF729ABADDD00DBED66 /* UIImage+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Size.swift"; sourceTree = "<group>"; };
|
||||
F8CEEDF929ABAFD200DBED66 /* ImageFileTranseferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFileTranseferable.swift; sourceTree = "<group>"; };
|
||||
F8E6D03229CDD52500416CCA /* EditProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileView.swift; sourceTree = "<group>"; };
|
||||
F8E6D03429CE161B00416CCA /* UIImage+Jpeg.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Jpeg.swift"; sourceTree = "<group>"; };
|
||||
F8E9391E29C0BCFA002BB3B8 /* ImageContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenu.swift; sourceTree = "<group>"; };
|
||||
F8E9392029C0DA7E002BB3B8 /* LazyImageState+ImageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LazyImageState+ImageResponse.swift"; sourceTree = "<group>"; };
|
||||
F8EF371429C624DA00669F45 /* Vernissage-006.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-006.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
@ -534,6 +536,7 @@
|
|||
F8C14393296AF21B001FE31D /* Double+Round.swift */,
|
||||
F8984E4C296B648000A2610F /* UIImage+Blurhash.swift */,
|
||||
F8CEEDF729ABADDD00DBED66 /* UIImage+Size.swift */,
|
||||
F8E6D03429CE161B00416CCA /* UIImage+Jpeg.swift */,
|
||||
F8996DEA2971D29D0043EEC6 /* View+Transition.swift */,
|
||||
F88E4D43297E82EB0057491A /* Status+MediaAttachmentType.swift */,
|
||||
F8864CEE29ACE90B0020C534 /* UIFont+Font.swift */,
|
||||
|
@ -1237,6 +1240,7 @@
|
|||
F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */,
|
||||
F86167C8297FE781004D1F67 /* AvatarShape.swift in Sources */,
|
||||
F85D4973296406E700751DF7 /* BottomRight.swift in Sources */,
|
||||
F8E6D03529CE161B00416CCA /* UIImage+Jpeg.swift in Sources */,
|
||||
F898DE702972868A004B4A6A /* String+Empty.swift in Sources */,
|
||||
F86B7218296C27C100EE59EC /* ActionButton.swift in Sources */,
|
||||
F8FA9917299F7DBD007AB130 /* Client+Media.swift in Sources */,
|
||||
|
@ -1274,7 +1278,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 80;
|
||||
CURRENT_PROJECT_VERSION = 81;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1302,7 +1306,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 80;
|
||||
CURRENT_PROJECT_VERSION = 81;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1450,7 +1454,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 80;
|
||||
CURRENT_PROJECT_VERSION = 81;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1490,7 +1494,7 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 80;
|
||||
CURRENT_PROJECT_VERSION = 81;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension UIImage {
|
||||
public func getJpegData() -> Data? {
|
||||
#if targetEnvironment(simulator)
|
||||
// For testing purposes.
|
||||
let converted = self.convertToExtendedSRGBJpeg()
|
||||
let filePath = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).jpg")
|
||||
try? converted?.write(to: filePath)
|
||||
print(filePath.string)
|
||||
#endif
|
||||
|
||||
// API don't support images over 5K.
|
||||
if self.size.height > 10_000 || self.size.width > 10_000 {
|
||||
return self
|
||||
.resized(to: .init(width: self.size.width / 4, height: self.size.height / 4))
|
||||
.convertToExtendedSRGBJpeg()
|
||||
} else if self.size.height > 5000 || self.size.width > 5000 {
|
||||
return self
|
||||
.resized(to: .init(width: self.size.width / 2, height: self.size.height / 2))
|
||||
.convertToExtendedSRGBJpeg()
|
||||
} else {
|
||||
return self
|
||||
.convertToExtendedSRGBJpeg()
|
||||
}
|
||||
}
|
||||
|
||||
public func convertToExtendedSRGBJpeg() -> Data? {
|
||||
guard let sourceImage = CIImage(image: self, options: [.applyOrientationProperty: true]) else {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
// We have to store correct image orientation.
|
||||
let orientedImage = sourceImage.oriented(forExifOrientation: self.imageOrientation.exifOrientation)
|
||||
|
||||
// We dont have to convert images which already are in sRGB color space.
|
||||
if orientedImage.colorSpace?.name == CGColorSpace.sRGB || orientedImage.colorSpace?.name == CGColorSpace.extendedSRGB {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
guard let colorSpace = CGColorSpace(name: CGColorSpace.extendedSRGB) else {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
guard let displayP3 = CGColorSpace(name: CGColorSpace.displayP3) else {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
// Create Core Image context (with working color space).
|
||||
let ciContext = CIContext(options: [CIContextOption.workingColorSpace: orientedImage.colorSpace ?? displayP3])
|
||||
|
||||
// Creating image with new color space (and preserving colors).
|
||||
guard let converted = ciContext.jpegRepresentation(of: orientedImage, colorSpace: colorSpace) else {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
// Returning successfully converted image.
|
||||
return converted
|
||||
}
|
||||
}
|
|
@ -52,37 +52,4 @@ extension UIImage {
|
|||
|
||||
return UIImage(cgImage: resizedCGImage)
|
||||
}
|
||||
|
||||
func convertToExtendedSRGBJpeg() -> Data? {
|
||||
guard let sourceImage = CIImage(image: self, options: [.applyOrientationProperty: true]) else {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
// We have to store correct image orientation.
|
||||
let orientedImage = sourceImage.oriented(forExifOrientation: self.imageOrientation.exifOrientation)
|
||||
|
||||
// We dont have to convert images which already are in sRGB color space.
|
||||
if orientedImage.colorSpace?.name == CGColorSpace.sRGB || orientedImage.colorSpace?.name == CGColorSpace.extendedSRGB {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
guard let colorSpace = CGColorSpace(name: CGColorSpace.extendedSRGB) else {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
guard let displayP3 = CGColorSpace(name: CGColorSpace.displayP3) else {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
// Create Core Image context (with working color space).
|
||||
let ciContext = CIContext(options: [CIContextOption.workingColorSpace: orientedImage.colorSpace ?? displayP3])
|
||||
|
||||
// Creating image with new color space (and preserving colors).
|
||||
guard let converted = ciContext.jpegRepresentation(of: orientedImage, colorSpace: colorSpace) else {
|
||||
return self.jpegData(compressionQuality: 0.9)
|
||||
}
|
||||
|
||||
// Returning successfully converted image.
|
||||
return converted
|
||||
}
|
||||
}
|
||||
|
|
|
@ -555,7 +555,7 @@ struct ComposeView: View {
|
|||
return
|
||||
}
|
||||
|
||||
guard let data = self.getJpegData(image: image) else {
|
||||
guard let data = image.getJpegData() else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -570,31 +570,7 @@ struct ComposeView: View {
|
|||
ErrorService.shared.handle(error, message: "compose.error.postingPhotoFailed", showToastr: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func getJpegData(image: UIImage) -> Data? {
|
||||
#if targetEnvironment(simulator)
|
||||
// For testing purposes.
|
||||
let converted = image.convertToExtendedSRGBJpeg()
|
||||
let filePath = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).jpg")
|
||||
try? converted?.write(to: filePath)
|
||||
print(filePath.string)
|
||||
#endif
|
||||
|
||||
// API don't support images over 5K.
|
||||
if image.size.height > 10_000 || image.size.width > 10_000 {
|
||||
return image
|
||||
.resized(to: .init(width: image.size.width / 4, height: image.size.height / 4))
|
||||
.convertToExtendedSRGBJpeg()
|
||||
} else if image.size.height > 5000 || image.size.width > 5000 {
|
||||
return image
|
||||
.resized(to: .init(width: image.size.width / 2, height: image.size.height / 2))
|
||||
.convertToExtendedSRGBJpeg()
|
||||
} else {
|
||||
return image
|
||||
.convertToExtendedSRGBJpeg()
|
||||
}
|
||||
}
|
||||
|
||||
private func publishStatus() async {
|
||||
do {
|
||||
let status = self.createStatus()
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import PhotosUI
|
||||
import SwiftUI
|
||||
import PixelfedKit
|
||||
|
||||
|
@ -12,9 +13,12 @@ struct EditProfileView: View {
|
|||
@EnvironmentObject private var client: Client
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var photosPickerVisible = false
|
||||
@State private var selectedItems: [PhotosPickerItem] = []
|
||||
@State private var saveDisabled = false
|
||||
@State var displayName: String = ""
|
||||
@State var bio: String = ""
|
||||
@State private var displayName: String = ""
|
||||
@State private var bio: String = ""
|
||||
@State private var avatarData: Data?
|
||||
|
||||
private let account: Account
|
||||
|
||||
|
@ -29,10 +33,19 @@ struct EditProfileView: View {
|
|||
Spacer()
|
||||
VStack {
|
||||
ZStack {
|
||||
UserAvatar(accountAvatar: account.avatar, size: .profile)
|
||||
if let avatarData, let uiAvatar = UIImage(data: avatarData) {
|
||||
Image(uiImage: uiAvatar)
|
||||
.resizable()
|
||||
.clipShape(applicationState.avatarShape.shape())
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 96, height: 96)
|
||||
} else {
|
||||
UserAvatar(accountAvatar: account.avatar, size: .profile)
|
||||
}
|
||||
|
||||
BottomRight {
|
||||
Button {
|
||||
|
||||
self.photosPickerVisible = true
|
||||
} label: {
|
||||
Image(systemName: "person.crop.circle.badge.plus")
|
||||
.font(.title)
|
||||
|
@ -61,7 +74,7 @@ struct EditProfileView: View {
|
|||
|
||||
Section("editProfile.title.bio") {
|
||||
TextField("", text: $bio, axis: .vertical)
|
||||
.lineLimit(4, reservesSpace: true)
|
||||
.lineLimit(5, reservesSpace: true)
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
|
@ -84,15 +97,56 @@ struct EditProfileView: View {
|
|||
self.bio = String(attributedString.characters)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.selectedItems) { selectedItem in
|
||||
Task {
|
||||
await self.getAvatar()
|
||||
}
|
||||
}
|
||||
.photosPicker(isPresented: $photosPickerVisible,
|
||||
selection: $selectedItems,
|
||||
maxSelectionCount: 1,
|
||||
matching: .images)
|
||||
}
|
||||
|
||||
private func saveProfile() async {
|
||||
do {
|
||||
_ = try await self.client.accounts?.update(displayName: self.displayName, bio: self.bio, image: nil)
|
||||
_ = try await self.client.accounts?.update(displayName: self.displayName, bio: self.bio, image: self.avatarData)
|
||||
ToastrService.shared.showSuccess("editProfile.title.accountSaved", imageSystemName: "person.crop.circle")
|
||||
dismiss()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "editProfile.error.saveAccountFailed", showToastr: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func getAvatar() async {
|
||||
do {
|
||||
self.saveDisabled = true
|
||||
|
||||
for item in self.selectedItems {
|
||||
if let data = try await item.loadTransferable(type: Data.self) {
|
||||
self.avatarData = data
|
||||
}
|
||||
}
|
||||
|
||||
guard let imageData = self.avatarData else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let image = UIImage(data: imageData) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = image
|
||||
.resized(to: .init(width: 400, height: 400))
|
||||
.getJpegData() else {
|
||||
return
|
||||
}
|
||||
|
||||
self.avatarData = data
|
||||
|
||||
self.saveDisabled = false
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "editProfile.error.loadingAvatarFailed", showToastr: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue