finish first version of World list page
This commit is contained in:
1 parent
844c4d7587
commit
2e4dfa48cd
7 files changed
+178
-5
No files matched your search
@@ -9,11 +9,40 @@ import SwiftUI
|
|||||||
|
|
||||||
struct WorldCardView : View {
|
struct WorldCardView : View {
|
||||||
var world : World
|
var world : World
|
||||||
|
var isSelected = false
|
||||||
|
|
||||||
var body : some View {
|
var body : some View {
|
||||||
VStack {
|
ZStack(alignment: .bottom) {
|
||||||
|
LinearGradient(
|
||||||
|
colors: [.clear, .black.opacity(0.5)],
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
HStack(alignment: .bottom) {
|
||||||
Text(world.name)
|
Text(world.name)
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if isSelected {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.font(.system(size: 28))
|
||||||
|
.foregroundStyle(.white, .indigo)
|
||||||
}
|
}
|
||||||
.background(.red)
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
|
||||||
|
}
|
||||||
|
.frame(height: 220)
|
||||||
|
.background {
|
||||||
|
Image(systemName: "globe.europe.africa")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
}
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 20))
|
||||||
|
.contentShape(RoundedRectangle(cornerRadius: 20))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File renamed without changes.
@@ -23,4 +23,13 @@ class World {
|
|||||||
self.createdAt = Date()
|
self.createdAt = Date()
|
||||||
self.updatedAt = Date()
|
self.updatedAt = Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func duplicate() -> World {
|
||||||
|
let newWorld = World(
|
||||||
|
name: self.name, overview: self.overview
|
||||||
|
)
|
||||||
|
|
||||||
|
return newWorld
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -20,9 +20,6 @@ struct WorldListHeaderView : View {
|
|||||||
)
|
)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(.vertical, 50)
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<World.ID>
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<World.ID>
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,10 @@ struct WorldListView: View {
|
|||||||
private var worlds: [World]
|
private var worlds: [World]
|
||||||
let columns = [GridItem(.flexible()), GridItem(.flexible())]
|
let columns = [GridItem(.flexible()), GridItem(.flexible())]
|
||||||
|
|
||||||
|
@State private var isSelecting = false
|
||||||
|
@State private var selectedWorlds = Set<World.ID>()
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading, spacing: 24){
|
VStack(alignment: .leading, spacing: 24){
|
||||||
@@ -24,16 +28,58 @@ struct WorldListView: View {
|
|||||||
}else{
|
}else{
|
||||||
LazyVGrid(columns: columns){
|
LazyVGrid(columns: columns){
|
||||||
ForEach(worlds) {world in
|
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{
|
NavigationLink{
|
||||||
WorldOverviewView()
|
WorldOverviewView()
|
||||||
} label: {
|
} label: {
|
||||||
WorldCardView(world: world)
|
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)
|
.frame(maxHeight: .infinity, alignment: .topLeading)
|
||||||
|
.modifier(
|
||||||
|
WorldListAreaInsetModifier(
|
||||||
|
isSelecting: isSelecting,
|
||||||
|
selectedWorlds: $selectedWorlds,
|
||||||
|
worlds: worlds
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
.modifier(
|
||||||
|
WorldListToolbarModifier(
|
||||||
|
isSelecting: $isSelecting,
|
||||||
|
selectedWorlds: $selectedWorlds
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user