From 21614116fcee22a35af49ea8baaaafb3cec426cd Mon Sep 17 00:00:00 2001 From: Giovanni Date: Sat, 5 Sep 2026 13:34:07 +0200 Subject: [PATCH] add illustration image for worlds --- .../xcschemes/xcschememanagement.plist | 8 ++ WorldBuilder/Components/WorldCardView.swift | 15 +++- WorldBuilder/Models/World.swift | 6 +- .../Components/IllustrationSection.swift | 37 +++++++++ .../Components/InformationsSection.swift | 25 ++++++ .../Views/CreateWorld/CreateWorldView.swift | 51 ++++++++---- .../Views/WorldList/WorldListView.swift | 81 ++++++++++--------- 7 files changed, 164 insertions(+), 59 deletions(-) create mode 100644 WorldBuilder/Views/CreateWorld/Components/IllustrationSection.swift create mode 100644 WorldBuilder/Views/CreateWorld/Components/InformationsSection.swift diff --git a/WorldBuilder.xcodeproj/xcuserdata/giovannijosserand.xcuserdatad/xcschemes/xcschememanagement.plist b/WorldBuilder.xcodeproj/xcuserdata/giovannijosserand.xcuserdatad/xcschemes/xcschememanagement.plist index b4799e3..29b7ec6 100644 --- a/WorldBuilder.xcodeproj/xcuserdata/giovannijosserand.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/WorldBuilder.xcodeproj/xcuserdata/giovannijosserand.xcuserdatad/xcschemes/xcschememanagement.plist @@ -10,5 +10,13 @@ 0 + SuppressBuildableAutocreation + + 2C96045430463C2E0003A2A8 + + primary + + + diff --git a/WorldBuilder/Components/WorldCardView.swift b/WorldBuilder/Components/WorldCardView.swift index 387aae6..ff9cd81 100644 --- a/WorldBuilder/Components/WorldCardView.swift +++ b/WorldBuilder/Components/WorldCardView.swift @@ -36,11 +36,18 @@ struct WorldCardView : View { .padding(.horizontal, 4) } - .frame(height: 220) + .frame(height: 250) .background { - Image(systemName: "globe.europe.africa") - .resizable() - .scaledToFill() + if let dataImage = world.illustrationData, + let uiImage = UIImage(data: dataImage) { + Image(uiImage: uiImage) + .resizable() + .scaledToFill() + }else { + Image(systemName: "globe.europe.africa") + .resizable() + .scaledToFill() + } } .clipShape(RoundedRectangle(cornerRadius: 20)) .contentShape(RoundedRectangle(cornerRadius: 20)) diff --git a/WorldBuilder/Models/World.swift b/WorldBuilder/Models/World.swift index 4f28ae8..3cff42f 100644 --- a/WorldBuilder/Models/World.swift +++ b/WorldBuilder/Models/World.swift @@ -15,18 +15,20 @@ class World { var updatedAt : Date var name : String var overview : String + @Attribute(.externalStorage) var illustrationData : Data? - init(name: String, overview: String) { + init(name: String, overview: String, illustrationData: Data?) { self.id = UUID() self.name = name self.overview = overview self.createdAt = Date() self.updatedAt = Date() + self.illustrationData = illustrationData } func duplicate() -> World { let newWorld = World( - name: self.name, overview: self.overview + name: self.name, overview: self.overview, illustrationData: self.illustrationData ) return newWorld diff --git a/WorldBuilder/Views/CreateWorld/Components/IllustrationSection.swift b/WorldBuilder/Views/CreateWorld/Components/IllustrationSection.swift new file mode 100644 index 0000000..5adf1a6 --- /dev/null +++ b/WorldBuilder/Views/CreateWorld/Components/IllustrationSection.swift @@ -0,0 +1,37 @@ +// +// IllustrationSection.swift +// WorldBuilder +// +// Created by Giovanni Josserand on 05/09/2026. +// + +import SwiftUI +import PhotosUI + +struct IllustrationSection : View { + @Binding var selectedItem: PhotosPickerItem? + @Binding var selectedImageData: Data? + + var body: some View { + Section("Illustration") { + if let selectedImageData, let uiImage = UIImage(data: selectedImageData) { + Image(uiImage: uiImage) + .resizable() + .scaledToFill() + .frame(height: 150) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .listRowInsets(EdgeInsets()) + } + + PhotosPicker( + selection: $selectedItem, + matching: .any(of: [.images, .not(.screenshots)]) + ) { + Label( + selectedImageData == nil ? "Sélectionner une photo" : "Modifier la photo", + systemImage: "photo" + ) + } + } + } +} diff --git a/WorldBuilder/Views/CreateWorld/Components/InformationsSection.swift b/WorldBuilder/Views/CreateWorld/Components/InformationsSection.swift new file mode 100644 index 0000000..bedbcd9 --- /dev/null +++ b/WorldBuilder/Views/CreateWorld/Components/InformationsSection.swift @@ -0,0 +1,25 @@ +// +// InformationsSection.swift +// WorldBuilder +// +// Created by Giovanni Josserand on 05/09/2026. +// + +import SwiftUI + +struct InformationsSection : View { + @Binding var worldName : String + @Binding var worldOverview : String + + var body: some View { + Section("Informations") { + TextField("Nom du monde", text: $worldName) + + TextField( + "Description", + text: $worldOverview, + axis: .vertical + ) + } + } +} diff --git a/WorldBuilder/Views/CreateWorld/CreateWorldView.swift b/WorldBuilder/Views/CreateWorld/CreateWorldView.swift index 3e2726c..eeb1d87 100644 --- a/WorldBuilder/Views/CreateWorld/CreateWorldView.swift +++ b/WorldBuilder/Views/CreateWorld/CreateWorldView.swift @@ -5,42 +5,58 @@ // Created by Giovanni Josserand on 01/09/2026. // +// +// CreateWorldView.swift +// WorldBuilder +// + import SwiftUI import SwiftData +import PhotosUI struct CreateWorldView: View { @State private var worldName = "" @State private var worldOverview = "" - + @Environment(\.modelContext) private var context @Environment(\.dismiss) private var dismiss @Environment(AppRouter.self) private var router - + @State private var showSaveError = false @State private var saveErrorMessage = "" - + + @State private var selectedItem: PhotosPickerItem? + @State private var selectedImageData: Data? + var body: some View { Form { - Section("Informations") { - TextField("Nom du monde", text: $worldName) + InformationsSection( + worldName: $worldName, + worldOverview: $worldOverview + ) - TextField( - "Description", - text: $worldOverview, - axis: .vertical - ) - } + IllustrationSection( + selectedItem: $selectedItem, + selectedImageData: $selectedImageData + ) Section { Button("Créer le monde") { - let world = World(name: worldName, overview: worldOverview) + let world = World( + name: worldName, + overview: worldOverview, + illustrationData: selectedImageData + ) + context.insert(world) + do { try context.save() router.selectedTab = .worldsList dismiss() - }catch { - saveErrorMessage = "Impossible de sauvegarder le monde : \(error.localizedDescription)" + } catch { + saveErrorMessage = + "Impossible de sauvegarder le monde : \(error.localizedDescription)" showSaveError = true } } @@ -61,5 +77,12 @@ struct CreateWorldView: View { } message: { Text(saveErrorMessage) } + .onChange(of: selectedItem) { _, newItem in + Task { + if let data = try? await newItem?.loadTransferable(type: Data.self) { + selectedImageData = data + } + } + } } } diff --git a/WorldBuilder/Views/WorldList/WorldListView.swift b/WorldBuilder/Views/WorldList/WorldListView.swift index 249ddf4..10d45b6 100644 --- a/WorldBuilder/Views/WorldList/WorldListView.swift +++ b/WorldBuilder/Views/WorldList/WorldListView.swift @@ -19,52 +19,55 @@ struct WorldListView: View { var body: some View { - ScrollView { VStack(alignment: .leading, spacing: 24){ - WorldListHeaderView(worlds: worlds) if worlds.isEmpty { EmptyWorldView() }else{ - LazyVGrid(columns: columns){ - ForEach(worlds) {world in - if isSelecting { - WorldCardView( - world: world, - isSelected: selectedWorlds.contains(world.id) - ) - .onTapGesture { - if selectedWorlds.contains(world.id) { - selectedWorlds.remove(world.id) - } else { - selectedWorlds.insert(world.id) - } - } - - }else { - NavigationLink{ - WorldOverviewView() - } label: { - WorldCardView(world: world) - } - .contextMenu{ - Button { - context.insert(world.duplicate()) - } label: { - Label("Dupliquer", systemImage: "plus.square.on.square") + ScrollView { + LazyVGrid(columns: columns){ + ForEach(worlds) {world in + if isSelecting { + WorldCardView( + world: world, + isSelected: selectedWorlds.contains(world.id) + ) + .onTapGesture { + if selectedWorlds.contains(world.id) { + selectedWorlds.remove(world.id) + } else { + selectedWorlds.insert(world.id) + } } - Button(role: .destructive){ - context.delete(world) - }label : { - Label("Supprimer", systemImage: "trash") + }else { + NavigationLink{ + WorldOverviewView() + } label: { + WorldCardView(world: world) + } + .contextMenu{ + Button { + context.insert(world.duplicate()) + } label: { + Label("Dupliquer", systemImage: "plus.square.on.square") + } + + Button(role: .destructive){ + context.delete(world) + }label : { + Label("Supprimer", systemImage: "trash") + } } } } } } + .scrollIndicators(.hidden) } } + .navigationTitle("Bibliothèque") + .navigationBarTitleDisplayMode(.inline) .padding(.horizontal, 8) .frame(maxHeight: .infinity, alignment: .topLeading) .modifier( @@ -74,13 +77,13 @@ struct WorldListView: View { worlds: worlds ) ) - } - .modifier( - WorldListToolbarModifier( - isSelecting: $isSelecting, - selectedWorlds: $selectedWorlds, - worldsIsEmpty: worlds.isEmpty + .modifier( + WorldListToolbarModifier( + isSelecting: $isSelecting, + selectedWorlds: $selectedWorlds, + worldsIsEmpty: worlds.isEmpty + ) ) - ) + } }