diff --git a/WorldBuilder/Components/WorldCardView.swift b/WorldBuilder/Components/WorldCardView.swift index 43436e7..387aae6 100644 --- a/WorldBuilder/Components/WorldCardView.swift +++ b/WorldBuilder/Components/WorldCardView.swift @@ -9,11 +9,40 @@ import SwiftUI struct WorldCardView : View { var world : World + var isSelected = false var body : some View { - VStack { - Text(world.name) + ZStack(alignment: .bottom) { + LinearGradient( + colors: [.clear, .black.opacity(0.5)], + startPoint: .top, + endPoint: .bottom + ) + + HStack(alignment: .bottom) { + Text(world.name) + .foregroundStyle(.white) + .font(.headline) + + Spacer() + + if isSelected { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 28)) + .foregroundStyle(.white, .indigo) + } + } + .padding(.vertical, 8) + .padding(.horizontal, 4) + } - .background(.red) + .frame(height: 220) + .background { + Image(systemName: "globe.europe.africa") + .resizable() + .scaledToFill() + } + .clipShape(RoundedRectangle(cornerRadius: 20)) + .contentShape(RoundedRectangle(cornerRadius: 20)) } } diff --git a/WorldBuilder/Components/MainTabView.swift b/WorldBuilder/MainTabView.swift similarity index 100% rename from WorldBuilder/Components/MainTabView.swift rename to WorldBuilder/MainTabView.swift diff --git a/WorldBuilder/Models/World.swift b/WorldBuilder/Models/World.swift index 447240f..4f28ae8 100644 --- a/WorldBuilder/Models/World.swift +++ b/WorldBuilder/Models/World.swift @@ -23,4 +23,13 @@ class World { self.createdAt = Date() self.updatedAt = Date() } + + func duplicate() -> World { + let newWorld = World( + name: self.name, overview: self.overview + ) + + return newWorld + } + } diff --git a/WorldBuilder/Views/WorldList/Components/WorldListHeaderView.swift b/WorldBuilder/Views/WorldList/Components/WorldListHeaderView.swift index cce0452..5b7c067 100644 --- a/WorldBuilder/Views/WorldList/Components/WorldListHeaderView.swift +++ b/WorldBuilder/Views/WorldList/Components/WorldListHeaderView.swift @@ -20,9 +20,6 @@ struct WorldListHeaderView : View { ) .font(.subheadline) .foregroundStyle(.secondary) - } - .padding(.vertical, 50) - .padding(.horizontal, 10) } } diff --git a/WorldBuilder/Views/WorldList/Modifiers/WorldListAreaInsetModifier.swift b/WorldBuilder/Views/WorldList/Modifiers/WorldListAreaInsetModifier.swift new file mode 100644 index 0000000..1c56aa2 --- /dev/null +++ b/WorldBuilder/Views/WorldList/Modifiers/WorldListAreaInsetModifier.swift @@ -0,0 +1,51 @@ +// +// WorldListAreaInsetModifier.swift +// WorldBuilder +// +// Created by Giovanni Josserand on 03/09/2026. +// + +import SwiftUI +import SwiftData + +struct WorldListAreaInsetModifier : ViewModifier { + let isSelecting : Bool + @Binding var selectedWorlds : Set + let worlds : [World] + + @Environment(\.modelContext) private var context + + func body(content: Content) -> some View { + content + .safeAreaInset(edge: .bottom){ + if isSelecting && !selectedWorlds.isEmpty { + HStack { + let worldsToAction = worlds.filter { world in + selectedWorlds.contains(world.id) + } + Spacer() + + Button { + for world in worldsToAction { + context.insert(world.duplicate()) + } + } label: { + Label("Dupliquer", systemImage: "plus.square.on.square") + } + + Spacer() + + Button(role: .destructive){ + for world in worldsToAction { + context.delete(world) + } + }label : { + Label("Supprimer", systemImage: "trash") + } + Spacer() + } + } + } + + } +} diff --git a/WorldBuilder/Views/WorldList/Modifiers/WorldListToolbarModifier.swift b/WorldBuilder/Views/WorldList/Modifiers/WorldListToolbarModifier.swift new file mode 100644 index 0000000..e3e9370 --- /dev/null +++ b/WorldBuilder/Views/WorldList/Modifiers/WorldListToolbarModifier.swift @@ -0,0 +1,41 @@ +// +// WorldListToolbarView.swift +// WorldBuilder +// +// Created by Giovanni Josserand on 03/09/2026. +// + +import SwiftUI + +struct WorldListToolbarModifier : ViewModifier { + @Binding var isSelecting : Bool + @Binding var selectedWorlds : Set + + func body(content: Content) -> some View { + content + .toolbar{ + ToolbarItem(placement: .topBarLeading) { + if isSelecting { + Button("Annuler") { + selectedWorlds.removeAll() + isSelecting = false + } + } else { + Button("Sélectionner") { + isSelecting = true + } + } + } + + ToolbarItem(placement: .topBarTrailing) { + if !isSelecting { + NavigationLink { + CreateWorldView() + } label: { + Image(systemName: "plus") + } + } + } + } + } +} diff --git a/WorldBuilder/Views/WorldList/WorldListView.swift b/WorldBuilder/Views/WorldList/WorldListView.swift index a413493..eacf04b 100644 --- a/WorldBuilder/Views/WorldList/WorldListView.swift +++ b/WorldBuilder/Views/WorldList/WorldListView.swift @@ -13,6 +13,10 @@ struct WorldListView: View { @Query(sort: \World.createdAt, order: .reverse) private var worlds: [World] let columns = [GridItem(.flexible()), GridItem(.flexible())] + + @State private var isSelecting = false + @State private var selectedWorlds = Set() + var body: some View { ScrollView { @@ -24,16 +28,58 @@ struct WorldListView: View { }else{ LazyVGrid(columns: columns){ ForEach(worlds) {world in - NavigationLink{ - WorldOverviewView() - } label: { - WorldCardView(world: world) + 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") + } + + Button(role: .destructive){ + context.delete(world) + }label : { + Label("Supprimer", systemImage: "trash") + } + } } } } } } + .padding(.horizontal, 8) .frame(maxHeight: .infinity, alignment: .topLeading) + .modifier( + WorldListAreaInsetModifier( + isSelecting: isSelecting, + selectedWorlds: $selectedWorlds, + worlds: worlds + ) + ) } + .modifier( + WorldListToolbarModifier( + isSelecting: $isSelecting, + selectedWorlds: $selectedWorlds + ) + ) } }