From 63e0d2e71cd95f9b0a910eef667dabb742297dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Tr=C3=A9guier?= Date: Tue, 1 Oct 2024 16:46:45 +0200 Subject: [PATCH] Support dropping avatar images --- Fyreplace.xcodeproj/project.pbxproj | 4 ++ .../Views/Components/EditableAvatar.swift | 67 +++++++++++++++++++ Fyreplace/Views/Screens/SettingsScreen.swift | 44 +----------- .../Screens/SettingsScreenProtocol.swift | 13 ---- 4 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 Fyreplace/Views/Components/EditableAvatar.swift diff --git a/Fyreplace.xcodeproj/project.pbxproj b/Fyreplace.xcodeproj/project.pbxproj index dea95fa..f9be060 100644 --- a/Fyreplace.xcodeproj/project.pbxproj +++ b/Fyreplace.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 4D54C96D2BF4E9BE001DE071 /* ArchiveScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D54C96C2BF4E9BE001DE071 /* ArchiveScreen.swift */; }; 4D54C96F2BF4E9DF001DE071 /* DraftsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D54C96E2BF4E9DF001DE071 /* DraftsScreen.swift */; }; 4D54C9712BF4EA15001DE071 /* SettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D54C9702BF4EA15001DE071 /* SettingsScreen.swift */; }; + 4D6A24DD2CAC193F001B4435 /* EditableAvatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D6A24DC2CAC192F001B4435 /* EditableAvatar.swift */; }; 4D7F9CC22C259C30007B1CC3 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 4D7F9CC12C259C00007B1CC3 /* PrivacyInfo.xcprivacy */; }; 4D9B3B382C334B3A00A8F7AD /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D9B3B372C334B3A00A8F7AD /* LoginScreen.swift */; }; 4D9B3B3A2C334B6300A8F7AD /* RegisterScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D9B3B392C334B6300A8F7AD /* RegisterScreen.swift */; }; @@ -142,6 +143,7 @@ 4D54C96E2BF4E9DF001DE071 /* DraftsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsScreen.swift; sourceTree = ""; }; 4D54C9702BF4EA15001DE071 /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = ""; }; 4D6641DB2C5B963500BE3D07 /* .ios-test-version */ = {isa = PBXFileReference; lastKnownFileType = text; path = ".ios-test-version"; sourceTree = ""; }; + 4D6A24DC2CAC192F001B4435 /* EditableAvatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableAvatar.swift; sourceTree = ""; }; 4D7F9CC12C259C00007B1CC3 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 4D9B3B372C334B3A00A8F7AD /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; 4D9B3B392C334B6300A8F7AD /* RegisterScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterScreen.swift; sourceTree = ""; }; @@ -222,6 +224,7 @@ 4D30DA5C2C986B6000499450 /* Components */ = { isa = PBXGroup; children = ( + 4D6A24DC2CAC192F001B4435 /* EditableAvatar.swift */, 4D30DA5E2C986B6C00499450 /* Avatar.swift */, ); path = Components; @@ -655,6 +658,7 @@ 4DE785952C8B17AE000EC4E5 /* SubmitOrCancel.swift in Sources */, 4DE785882C88F392000EC4E5 /* HTTPField.swift in Sources */, 4D060BCB2C9438E8008C32D1 /* View.swift in Sources */, + 4D6A24DD2CAC193F001B4435 /* EditableAvatar.swift in Sources */, 4D51F2802C621ADB0018E76E /* ViewProtocol.swift in Sources */, 4DC5B1D92C720B9800B75A07 /* AuthenticationMiddleware.swift in Sources */, 4D9B3B472C36F50300A8F7AD /* UITextContentType.swift in Sources */, diff --git a/Fyreplace/Views/Components/EditableAvatar.swift b/Fyreplace/Views/Components/EditableAvatar.swift new file mode 100644 index 0000000..2e4a24f --- /dev/null +++ b/Fyreplace/Views/Components/EditableAvatar.swift @@ -0,0 +1,67 @@ +import PhotosUI +import SwiftUI + +struct EditableAvatar: View { + let user: Components.Schemas.User? + + let avatarSelected: (Data) async -> Void + + @EnvironmentObject + private var eventBus: EventBus + + @State + private var showEditOverlay = false + + @State + private var avatarItem: PhotosPickerItem? + + var body: some View { + let opacity = showEditOverlay ? 1.0 : 0.0 + let blurred = showEditOverlay + PhotosPicker(selection: $avatarItem) { + Avatar(user: user, blurred: blurred) + .overlay { + Image(systemName: "pencil") + .scaleEffect(2) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding() + .background(.black.opacity(0.5)) + .foregroundStyle(.white) + .clipShape(.circle) + .opacity(opacity) + } + } + .animation(.default.speed(3), value: showEditOverlay) + .buttonStyle(.borderless) + .onHover { showEditOverlay = $0 } + .dropDestination(for: Data.self) { items, _ in + guard let data = items.first else { return false } + + Task { + await avatarSelected(data) + } + + return true + } + .onChange(of: avatarItem) { item in + guard let item else { return } + + Task { + await usePhotoItem(item) + } + } + } + + private func usePhotoItem(_ item: PhotosPickerItem) async { + if let data = try? await item.loadTransferable(type: Data.self) { + await avatarSelected(data) + } else { + eventBus.send( + .failure( + title: "Settings.Error.Image.Title", + text: "Settings.Error.Image.Message" + ) + ) + } + } +} diff --git a/Fyreplace/Views/Screens/SettingsScreen.swift b/Fyreplace/Views/Screens/SettingsScreen.swift index 7c4eb59..f27d661 100644 --- a/Fyreplace/Views/Screens/SettingsScreen.swift +++ b/Fyreplace/Views/Screens/SettingsScreen.swift @@ -36,11 +36,7 @@ struct SettingsScreen: View, SettingsScreenProtocol { } } header: { LogoHeader(namespace: namespace) { - PickableAvatar(user: currentUser) { item in - Task { - await updateAvatar(with: item) - } - } + EditableAvatar(user: currentUser, avatarSelected: updateAvatar) } textContent: { Text("Settings.Header") } @@ -83,41 +79,3 @@ private struct DateText: View { } } } - -private struct PickableAvatar: View { - let user: Components.Schemas.User? - - let avatarSelected: (PhotosPickerItem) -> Void - - @State - private var showEditOverlay = false - - @State - private var avatarItem: PhotosPickerItem? - - var body: some View { - let opacity = showEditOverlay ? 1.0 : 0.0 - let blurred = showEditOverlay - PhotosPicker(selection: $avatarItem) { - Avatar(user: user, blurred: blurred) - .overlay { - Image(systemName: "pencil") - .scaleEffect(2) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding() - .background(.black.opacity(0.5)) - .foregroundStyle(.white) - .clipShape(.circle) - .opacity(opacity) - } - } - .animation(.default.speed(3), value: showEditOverlay) - .buttonStyle(.borderless) - .onHover { showEditOverlay = $0 } - .onChange(of: avatarItem) { - if let item = $0 { - avatarSelected(item) - } - } - } -} diff --git a/Fyreplace/Views/Screens/SettingsScreenProtocol.swift b/Fyreplace/Views/Screens/SettingsScreenProtocol.swift index e86e2cd..9b40bb9 100644 --- a/Fyreplace/Views/Screens/SettingsScreenProtocol.swift +++ b/Fyreplace/Views/Screens/SettingsScreenProtocol.swift @@ -33,19 +33,6 @@ extension SettingsScreenProtocol { } } - func updateAvatar(with item: PhotosPickerItem) async { - if let data = try? await item.loadTransferable(type: Data.self) { - await updateAvatar(with: data) - } else { - eventBus.send( - .failure( - title: "Settings.Error.Image.Title", - text: "Settings.Error.Image.Message" - ) - ) - } - } - func updateAvatar(with data: Data) async { await call { let response = try await api.setCurrentUserAvatar(body: .binary(.init(data)))