@ -6,16 +6,21 @@
//
import SwiftUI
import LeStorage
import TipKit
struct PlanningView : View {
@ EnvironmentObject var dataStore : DataStore
@ Environment ( Tournament . self ) var tournament : Tournament
@ State private var selectedDay : Date ?
@ Binding var selectedScheduleDestination : ScheduleDestination ?
@ State private var filterOption : PlanningFilterOption = . byDefault
@ State private var showFinishedMatches : Bool = false
@ State private var enableMove : Bool = false
let allMatches : [ Match ]
let timeSlotMoveOptionTip = TimeSlotMoveOptionTip ( )
init ( matches : [ Match ] , selectedScheduleDestination : Binding < ScheduleDestination ? > ) {
self . allMatches = matches
@ -38,22 +43,6 @@ struct PlanningView: View {
timeSlots . keys . sorted ( )
}
enum PlanningFilterOption : Int , CaseIterable , Identifiable {
var id : Int { self . rawValue }
case byDefault
case byCourt
func localizedPlanningLabel ( ) -> String {
switch self {
case . byCourt :
return " Par terrain "
case . byDefault :
return " Par ordre des matchs "
}
}
}
private func _computedTitle ( days : [ Date ] ) -> String {
if let selectedDay {
return selectedDay . formatted ( . dateTime . day ( ) . weekday ( ) . month ( ) )
@ -71,8 +60,13 @@ struct PlanningView: View {
let keys = self . keys ( timeSlots : timeSlots )
let days = self . days ( timeSlots : timeSlots )
let matches = matches
BySlotView ( days : days , keys : keys , timeSlots : timeSlots , matches : matches , selectedDay : selectedDay , filterOption : filterOption , showFinishedMatches : showFinishedMatches )
let notSlots = matches . allSatisfy ( { $0 . startDate = = nil } )
BySlotView ( days : days , keys : keys , timeSlots : timeSlots , matches : matches , selectedDay : selectedDay )
. environment ( \ . filterOption , filterOption )
. environment ( \ . showFinishedMatches , showFinishedMatches )
. environment ( \ . enableMove , enableMove )
. navigationTitle ( Text ( _computedTitle ( days : days ) ) )
. navigationBarBackButtonHidden ( enableMove )
. toolbar ( content : {
if days . count > 1 {
ToolbarTitleMenu {
@ -89,42 +83,79 @@ struct PlanningView: View {
Text ( " Jour " )
}
. pickerStyle ( . automatic )
. disabled ( enableMove )
}
}
ToolbarItemGroup ( placement : . topBarTrailing ) {
Menu {
Picker ( selection : $ showFinishedMatches ) {
Text ( " Afficher tous les matchs " ) . tag ( true )
Text ( " Masquer les matchs terminés " ) . tag ( false )
} label : {
Text ( " Option de filtrage " )
if enableMove {
ToolbarItem ( placement : . topBarLeading ) {
Button ( " Annuler " ) {
enableMove = false
}
. labelsHidden ( )
. pickerStyle ( . inline )
} label : {
Label ( " Filtrer " , systemImage : " clock.badge.checkmark " )
. symbolVariant ( showFinishedMatches ? . fill : . none )
}
Menu {
Picker ( selection : $ filterOption ) {
ForEach ( PlanningFilterOption . allCases ) {
Text ( $0 . localizedPlanningLabel ( ) ) . tag ( $0 )
ToolbarItem ( placement : . topBarTrailing ) {
Button ( " Sauver " ) {
do {
try self . tournament . tournamentStore . matches . addOrUpdate ( contentOfs : allMatches )
} catch {
Logger . error ( error )
}
} label : {
Text ( " Option de triage " )
enableMove = false
}
. labelsHidden ( )
. pickerStyle ( . inline )
} label : {
Label ( " Trier " , systemImage : " line.3.horizontal.decrease.circle " )
. symbolVariant ( filterOption = = . byCourt ? . fill : . none )
}
} else {
ToolbarItemGroup ( placement : . topBarTrailing ) {
if notSlots = = false {
Toggle ( isOn : $ enableMove ) {
Label ( " Déplacer " , systemImage : " rectangle.2.swap " )
}
. popoverTip ( timeSlotMoveOptionTip )
}
Menu {
Section {
Picker ( selection : $ showFinishedMatches ) {
Text ( " Afficher tous les matchs " ) . tag ( true )
Text ( " Masquer les matchs terminés " ) . tag ( false )
} label : {
Text ( " Option de filtrage " )
}
. labelsHidden ( )
. pickerStyle ( . inline )
} header : {
Text ( " Option de filtrage " )
}
Divider ( )
Section {
Picker ( selection : $ filterOption ) {
ForEach ( PlanningFilterOption . allCases ) {
Text ( $0 . localizedPlanningLabel ( ) ) . tag ( $0 )
}
} label : {
Text ( " Option de triage " )
}
. labelsHidden ( )
. pickerStyle ( . inline )
} header : {
Text ( " Option de triage " )
}
} label : {
Label ( " Trier " , systemImage : " line.3.horizontal.decrease.circle " )
. symbolVariant ( filterOption = = . byCourt || showFinishedMatches ? . fill : . none )
}
}
}
} )
. overlay {
if matches . allSatisfy ( { $0 . startDate = = nil } ) {
if notSlots {
ContentUnavailableView {
Label ( " Aucun horaire défini " , systemImage : " clock.badge.questionmark " )
} description : {
@ -140,86 +171,213 @@ struct PlanningView: View {
struct BySlotView : View {
@ Environment ( Tournament . self ) var tournament : Tournament
@ Environment ( \ . filterOption ) private var filterOption
@ Environment ( \ . showFinishedMatches ) private var showFinishedMatches
@ Environment ( \ . enableMove ) private var enableMove
let days : [ Date ]
let keys : [ Date ]
let timeSlots : [ Date : [ Match ] ]
let timeSlots : [ Date : [ Match ] ]
let matches : [ Match ]
let selectedDay : Date ?
let filterOption : PlanningFilterOption
let showFinishedMatches : Bool
let timeSlotMoveTip = TimeSlotMoveTip ( )
var body : some View {
List {
if matches . allSatisfy ( { $0 . startDate = = nil } ) = = false {
if enableMove {
TipView ( timeSlotMoveTip )
. tipStyle ( tint : . logoYellow , asSection : true )
}
if ! matches . allSatisfy ( { $0 . startDate = = nil } ) {
ForEach ( days . filter ( { selectedDay = = nil || selectedDay = = $0 } ) , id : \ . self ) { day in
Section {
ForEach ( keys . filter ( { $0 . dayInt = = day . dayInt } ) , id : \ . self ) { key in
if let _matches = timeSlots [ key ] ? . sorted ( by : filterOption = = . byDefault ? \ . computedOrder : \ . courtIndexForSorting ) {
DisclosureGroup {
ForEach ( _matches ) { match in
NavigationLink {
MatchDetailView ( match : match )
. matchViewStyle ( . sectionedStandardStyle )
} label : {
LabeledContent {
if let courtName = match . courtName ( ) {
Text ( courtName )
}
} label : {
if let groupStage = match . groupStageObject {
Text ( groupStage . groupStageTitle ( . title ) )
} else if let round = match . roundObject {
Text ( round . roundTitle ( ) )
}
Text ( match . matchTitle ( ) )
}
}
}
} label : {
_timeSlotView ( key : key , matches : _matches )
}
}
}
} header : {
HStack {
if day . monthYearFormatted = = Date . distantFuture . monthYearFormatted {
Text ( " Sans horaire " )
} else {
Text ( day . formatted ( . dateTime . day ( ) . weekday ( ) . month ( ) ) )
}
Spacer ( )
let count = _matchesCount ( inDayInt : day . dayInt , timeSlots : timeSlots )
if showFinishedMatches {
Text ( self . _formattedMatchCount ( count ) )
} else {
Text ( self . _formattedMatchCount ( count ) + " restant \( count . pluralSuffix ) " )
}
}
} footer : {
if day . monthYearFormatted = = Date . distantFuture . monthYearFormatted {
Text ( " Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages. " )
}
}
. headerProminence ( . increased )
DaySectionView (
day : day ,
keys : keys . filter ( { $0 . dayInt = = day . dayInt } ) ,
timeSlots : timeSlots ,
selectedDay : selectedDay
)
}
}
}
}
}
struct DaySectionView : View {
@ Environment ( Tournament . self ) var tournament : Tournament
@ Environment ( \ . filterOption ) private var filterOption
@ Environment ( \ . showFinishedMatches ) private var showFinishedMatches
@ Environment ( \ . enableMove ) private var enableMove
let day : Date
let keys : [ Date ]
let timeSlots : [ Date : [ Match ] ]
let selectedDay : Date ?
var body : some View {
Section {
ForEach ( keys , id : \ . self ) { key in
TimeSlotSectionView (
key : key ,
matches : timeSlots [ key ] ? . sorted ( by : filterOption = = . byDefault ? \ . computedOrder : \ . courtIndexForSorting ) ? ? [ ]
)
}
. onMove ( perform : enableMove ? moveSection : nil )
} header : {
HeaderView ( day : day , timeSlots : timeSlots )
} footer : {
if day . monthYearFormatted = = Date . distantFuture . monthYearFormatted {
Text ( " Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages. " )
}
}
}
func moveSection ( from source : IndexSet , to destination : Int ) {
let daySlots = keys . filter { $0 . dayInt = = day . dayInt } . sorted ( )
guard let sourceIdx = source . first ,
sourceIdx < daySlots . count ,
destination <= daySlots . count else {
return
}
// C r e a t e a m u t a b l e c o p y o f t h e t i m e s l o t s f o r t h i s d a y
var slotsToUpdate = daySlots
let updateRange = min ( sourceIdx , destination ) . . . max ( sourceIdx , destination )
// P e r f o r m t h e m o v e i n t h e a r r a y
let sourceTime = slotsToUpdate . remove ( at : sourceIdx )
if sourceIdx < destination {
slotsToUpdate . insert ( sourceTime , at : destination - 1 )
} else {
slotsToUpdate . insert ( sourceTime , at : destination )
}
// U p d a t e m a t c h e s b y s w a p p i n g t h e i r s t a r t D a t e s
for index in updateRange {
// F i n d t h e n e w t i m e s l o t f o r t h e s e m a t c h e s
let oldStartTime = slotsToUpdate [ index ]
let newStartTime = daySlots [ index ]
guard let matchesToUpdate = timeSlots [ oldStartTime ] else { continue }
// U p d a t e e a c h m a t c h w i t h t h e n e w s t a r t t i m e
for match in matchesToUpdate {
match . startDate = newStartTime
}
}
}
}
struct TimeSlotSectionView : View {
@ Environment ( \ . enableMove ) private var enableMove
let key : Date
let matches : [ Match ]
private func _matchesCount ( inDayInt dayInt : Int , timeSlots : [ Date : [ Match ] ] ) -> Int {
var body : some View {
if ! matches . isEmpty {
if enableMove {
TimeSlotHeaderView ( key : key , matches : matches )
} else {
DisclosureGroup {
MatchListView ( matches : matches )
} label : {
TimeSlotHeaderView ( key : key , matches : matches )
}
}
}
}
}
struct MatchListView : View {
let matches : [ Match ]
var body : some View {
ForEach ( matches ) { match in
NavigationLink {
MatchDetailView ( match : match )
. matchViewStyle ( . sectionedStandardStyle )
} label : {
MatchRowView ( match : match )
}
}
}
}
struct MatchRowView : View {
let match : Match
var body : some View {
LabeledContent {
if let courtName = match . courtName ( ) {
Text ( courtName )
}
} label : {
if let groupStage = match . groupStageObject {
Text ( groupStage . groupStageTitle ( . title ) )
} else if let round = match . roundObject {
Text ( round . roundTitle ( ) )
}
Text ( match . matchTitle ( ) )
}
}
}
struct HeaderView : View {
@ Environment ( \ . filterOption ) private var filterOption
@ Environment ( \ . showFinishedMatches ) private var showFinishedMatches
@ Environment ( \ . enableMove ) private var enableMove
let day : Date
let timeSlots : [ Date : [ Match ] ]
var body : some View {
HStack {
if day . monthYearFormatted = = Date . distantFuture . monthYearFormatted {
Text ( " Sans horaire " )
} else {
Text ( day . formatted ( . dateTime . day ( ) . weekday ( ) . month ( ) ) )
}
Spacer ( )
let count = _matchesCount ( inDayInt : day . dayInt , timeSlots : timeSlots )
if showFinishedMatches {
Text ( _formattedMatchCount ( count ) )
} else {
Text ( " \( _formattedMatchCount ( count ) ) restant \( count . pluralSuffix ) " )
}
}
}
private func _matchesCount ( inDayInt dayInt : Int , timeSlots : [ Date : [ Match ] ] ) -> Int {
timeSlots . filter { $0 . key . dayInt = = dayInt } . flatMap ( { $0 . value } ) . count
}
private func _timeSlotView ( key : Date , matches : [ Match ] ) -> some View {
private func _formattedMatchCount ( _ count : Int ) -> String {
return " \( count . formatted ( ) ) match \( count . pluralSuffix ) "
}
}
struct TimeSlotHeaderView : View {
let key : Date
let matches : [ Match ]
@ Environment ( Tournament . self ) var tournament : Tournament
var body : some View {
LabeledContent {
Text ( self . _formattedMatchCount ( matches . count ) )
Text ( " \( matches . count . formatted ( ) ) match \ (matches . count . pluralSuffix ) " )
} label : {
if key . monthYearFormatted = = Date . distantFuture . monthYearFormatted {
Text ( " Aucun horaire " )
} else {
Text ( key . formatted ( date : . omitted , time : . shortened ) ) . font ( . title ) . fontWeight ( . semibold )
Text ( key . formatted ( date : . omitted , time : . shortened ) )
. font ( . title )
. fontWeight ( . semibold )
}
if matches . count <= tournament . courtCount {
let names = matches . sorted ( by : \ . computedOrder )
. compactMap ( { $0 . roundTitle ( ) } )
@ -232,15 +390,203 @@ struct PlanningView: View {
} else {
Text ( matches . count . formatted ( ) . appending ( " matchs " ) )
}
}
}
}
fileprivate func _formattedMatchCount ( _ count : Int ) -> String {
return " \( count . formatted ( ) ) match \( count . pluralSuffix ) "
// s t r u c t B y S l o t V i e w : V i e w {
// @ E n v i r o n m e n t ( T o u r n a m e n t . s e l f ) v a r t o u r n a m e n t : T o u r n a m e n t
// l e t d a y s : [ D a t e ]
// l e t k e y s : [ D a t e ]
// l e t t i m e S l o t s : [ D a t e : [ M a t c h ] ]
// l e t m a t c h e s : [ M a t c h ]
// l e t s e l e c t e d D a y : D a t e ?
// l e t f i l t e r O p t i o n : P l a n n i n g F i l t e r O p t i o n
// l e t s h o w F i n i s h e d M a t c h e s : B o o l
//
// v a r b o d y : s o m e V i e w {
// L i s t {
// i f m a t c h e s . a l l S a t i s f y ( { $ 0 . s t a r t D a t e = = n i l } ) = = f a l s e {
// F o r E a c h ( d a y s . f i l t e r ( { s e l e c t e d D a y = = n i l | | s e l e c t e d D a y = = $ 0 } ) , i d : \ . s e l f ) { d a y i n
// S e c t i o n {
// F o r E a c h ( k e y s . f i l t e r ( { $ 0 . d a y I n t = = d a y . d a y I n t } ) , i d : \ . s e l f ) { k e y i n
// i f l e t _ m a t c h e s = t i m e S l o t s [ k e y ] ? . s o r t e d ( b y : f i l t e r O p t i o n = = . b y D e f a u l t ? \ . c o m p u t e d O r d e r : \ . c o u r t I n d e x F o r S o r t i n g ) {
// D i s c l o s u r e G r o u p {
// F o r E a c h ( _ m a t c h e s ) { m a t c h i n
// N a v i g a t i o n L i n k {
// M a t c h D e t a i l V i e w ( m a t c h : m a t c h )
// . m a t c h V i e w S t y l e ( . s e c t i o n e d S t a n d a r d S t y l e )
//
// } l a b e l : {
// L a b e l e d C o n t e n t {
// i f l e t c o u r t N a m e = m a t c h . c o u r t N a m e ( ) {
// T e x t ( c o u r t N a m e )
// }
// } l a b e l : {
// i f l e t g r o u p S t a g e = m a t c h . g r o u p S t a g e O b j e c t {
// T e x t ( g r o u p S t a g e . g r o u p S t a g e T i t l e ( . t i t l e ) )
// } e l s e i f l e t r o u n d = m a t c h . r o u n d O b j e c t {
// T e x t ( r o u n d . r o u n d T i t l e ( ) )
// }
// T e x t ( m a t c h . m a t c h T i t l e ( ) )
// }
// }
// }
// } l a b e l : {
// _ t i m e S l o t V i e w ( k e y : k e y , m a t c h e s : _ m a t c h e s )
// }
// }
// }
// . o n M o v e ( p e r f o r m : m o v e S e c t i o n )
// } h e a d e r : {
// H S t a c k {
// i f d a y . m o n t h Y e a r F o r m a t t e d = = D a t e . d i s t a n t F u t u r e . m o n t h Y e a r F o r m a t t e d {
// T e x t ( " S a n s h o r a i r e " )
// } e l s e {
// T e x t ( d a y . f o r m a t t e d ( . d a t e T i m e . d a y ( ) . w e e k d a y ( ) . m o n t h ( ) ) )
// }
// S p a c e r ( )
// l e t c o u n t = _ m a t c h e s C o u n t ( i n D a y I n t : d a y . d a y I n t , t i m e S l o t s : t i m e S l o t s )
// i f s h o w F i n i s h e d M a t c h e s {
// T e x t ( s e l f . _ f o r m a t t e d M a t c h C o u n t ( c o u n t ) )
// } e l s e {
// T e x t ( s e l f . _ f o r m a t t e d M a t c h C o u n t ( c o u n t ) + " r e s t a n t \ ( c o u n t . p l u r a l S u f f i x ) " )
// }
// }
// } f o o t e r : {
// i f d a y . m o n t h Y e a r F o r m a t t e d = = D a t e . d i s t a n t F u t u r e . m o n t h Y e a r F o r m a t t e d {
// T e x t ( " I l s ' a g i t d e s m a t c h s q u i n ' o n t p a s r é u s s i à ê t r e p l a c é p a r P a d e l C l u b . P e u t - ê t r e à c a u s e d e c r é n e a u x i n d i s p o n i b l e s , d ' a u t r e s t o u r n o i s o u d e s r é g l a g e s . " )
// }
// }
// . h e a d e r P r o m i n e n c e ( . i n c r e a s e d )
// }
// }
// }
// }
//
// f u n c m o v e S e c t i o n ( f r o m s o u r c e : I n d e x S e t , t o d e s t i n a t i o n : I n t ) {
// l e t d a y S l o t s = k e y s . f i l t e r { s e l e c t e d D a y = = n i l | | $ 0 . d a y I n t = = s e l e c t e d D a y ? . d a y I n t } . s o r t e d ( )
//
// g u a r d l e t s o u r c e I d x = s o u r c e . f i r s t ,
// s o u r c e I d x < d a y S l o t s . c o u n t ,
// d e s t i n a t i o n < = d a y S l o t s . c o u n t e l s e {
// r e t u r n
// }
//
// / / C r e a t e a m u t a b l e c o p y o f t h e t i m e s l o t s f o r t h i s d a y
// v a r s l o t s T o U p d a t e = d a y S l o t s
//
// l e t u p d a t e R a n g e = m i n ( s o u r c e I d x , d e s t i n a t i o n ) . . . m a x ( s o u r c e I d x , d e s t i n a t i o n ) - 1
// p r i n t ( u p d a t e R a n g e )
//
// / / P e r f o r m t h e m o v e i n t h e a r r a y
// l e t s o u r c e T i m e = s l o t s T o U p d a t e . r e m o v e ( a t : s o u r c e I d x )
// i f s o u r c e I d x < d e s t i n a t i o n {
// s l o t s T o U p d a t e . i n s e r t ( s o u r c e T i m e , a t : d e s t i n a t i o n - 1 )
// } e l s e {
// s l o t s T o U p d a t e . i n s e r t ( s o u r c e T i m e , a t : d e s t i n a t i o n )
// }
//
// / / U p d a t e m a t c h e s b y s w a p p i n g t h e i r s t a r t D a t e s
// f o r i n d e x i n u p d a t e R a n g e {
// / / F i n d t h e n e w t i m e s l o t f o r t h e s e m a t c h e s
// l e t o l d S t a r t T i m e = s l o t s T o U p d a t e [ i n d e x ]
// l e t n e w S t a r t T i m e = d a y S l o t s [ i n d e x ]
// g u a r d l e t m a t c h e s T o U p d a t e = t i m e S l o t s [ o l d S t a r t T i m e ] e l s e { c o n t i n u e }
// p r i n t ( " m o v i n g " , o l d S t a r t T i m e , " t o " , n e w S t a r t T i m e )
//
// / / U p d a t e e a c h m a t c h w i t h t h e n e w s t a r t t i m e
// f o r m a t c h i n m a t c h e s T o U p d a t e {
// m a t c h . s t a r t D a t e = n e w S t a r t T i m e
// }
// }
//
// t r y ? s e l f . t o u r n a m e n t . t o u r n a m e n t S t o r e . m a t c h e s . a d d O r U p d a t e ( c o n t e n t O f s : m a t c h e s )
// }
//
//
// p r i v a t e f u n c _ m a t c h e s C o u n t ( i n D a y I n t d a y I n t : I n t , t i m e S l o t s : [ D a t e : [ M a t c h ] ] ) - > I n t {
// t i m e S l o t s . f i l t e r { $ 0 . k e y . d a y I n t = = d a y I n t } . f l a t M a p ( { $ 0 . v a l u e } ) . c o u n t
// }
//
// p r i v a t e f u n c _ t i m e S l o t V i e w ( k e y : D a t e , m a t c h e s : [ M a t c h ] ) - > s o m e V i e w {
// L a b e l e d C o n t e n t {
// T e x t ( s e l f . _ f o r m a t t e d M a t c h C o u n t ( m a t c h e s . c o u n t ) )
// } l a b e l : {
// i f k e y . m o n t h Y e a r F o r m a t t e d = = D a t e . d i s t a n t F u t u r e . m o n t h Y e a r F o r m a t t e d {
// T e x t ( " A u c u n h o r a i r e " )
// } e l s e {
// T e x t ( k e y . f o r m a t t e d ( d a t e : . o m i t t e d , t i m e : . s h o r t e n e d ) ) . f o n t ( . t i t l e ) . f o n t W e i g h t ( . s e m i b o l d )
// }
// i f m a t c h e s . c o u n t < = t o u r n a m e n t . c o u r t C o u n t {
// l e t n a m e s = m a t c h e s . s o r t e d ( b y : \ . c o m p u t e d O r d e r )
// . c o m p a c t M a p ( { $ 0 . r o u n d T i t l e ( ) } )
// . r e d u c e ( i n t o : [ S t r i n g ] ( ) ) { u n i q u e N a m e s , n a m e i n
// i f ! u n i q u e N a m e s . c o n t a i n s ( n a m e ) {
// u n i q u e N a m e s . a p p e n d ( n a m e )
// }
// }
// T e x t ( n a m e s . j o i n e d ( s e p a r a t o r : " , " ) ) . l i n e L i m i t ( 1 ) . t r u n c a t i o n M o d e ( . t a i l )
// } e l s e {
// T e x t ( m a t c h e s . c o u n t . f o r m a t t e d ( ) . a p p e n d i n g ( " m a t c h s " ) )
// }
// }
// }
//
// f i l e p r i v a t e f u n c _ f o r m a t t e d M a t c h C o u n t ( _ c o u n t : I n t ) - > S t r i n g {
// r e t u r n " \ ( c o u n t . f o r m a t t e d ( ) ) m a t c h \ ( c o u n t . p l u r a l S u f f i x ) "
// }
// }
}
enum PlanningFilterOption : Int , CaseIterable , Identifiable {
var id : Int { self . rawValue }
case byDefault
case byCourt
func localizedPlanningLabel ( ) -> String {
switch self {
case . byCourt :
return " Par terrain "
case . byDefault :
return " Par ordre des matchs "
}
}
}
// # P r e v i e w {
// P l a n n i n g V i e w ( m a t c h e s : [ ] , s e l e c t e d S c h e d u l e D e s t i n a t i o n : . c o n s t a n t ( n i l ) )
// }
struct FilterOptionKey : EnvironmentKey {
static let defaultValue : PlanningFilterOption = . byDefault
}
extension EnvironmentValues {
var filterOption : PlanningFilterOption {
get { self [ FilterOptionKey . self ] }
set { self [ FilterOptionKey . self ] = newValue }
}
}
struct ShowFinishedMatchesKey : EnvironmentKey {
static let defaultValue : Bool = false
}
extension EnvironmentValues {
var showFinishedMatches : Bool {
get { self [ ShowFinishedMatchesKey . self ] }
set { self [ ShowFinishedMatchesKey . self ] = newValue }
}
}
struct EnableMoveKey : EnvironmentKey {
static let defaultValue : Bool = false
}
extension EnvironmentValues {
var enableMove : Bool {
get { self [ EnableMoveKey . self ] }
set { self [ EnableMoveKey . self ] = newValue }
}
}