From c37aeea475951cc1baa64d104683fcb57e77203e Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Thu, 19 Oct 2023 14:22:06 -0700 Subject: [PATCH 01/22] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20jira=202286=20sort?= =?UTF-8?q?=20&=20filter=20for=20SwiftUI=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A SwiftUI component for configuration criteria of performing sorting and filter. SortFilterMenu and SortFilterFullCFG are provided. ✅ Closes: 1 --- .../Examples.xcodeproj/project.pbxproj | 34 +- .../FioriSwiftUICore/CoreContentView.swift | 4 + .../SortFilter/SortFilterExample.swift | 80 +++ .../SortFilter/View+Extensions.swift | 35 ++ .../iconfromapp.imageset/Contents.json | 21 + .../iconfromapp.imageset/icon.png | Bin 0 -> 934 bytes Package.swift | 2 +- .../CancellableResettableForm.swift | 109 ++++ .../Components/MultiPropertyComponents.swift | 45 ++ .../Components/SinglePropertyComponents.swift | 2 + .../DataTypes/SoftFilter+DataType.swift | 500 ++++++++++++++++++ .../Models/DefaultViewModels.swift | 16 + .../Models/ModelDefinitions.swift | 80 +++ .../Views/OptionChip+View.swift | 182 +++++++ .../Views/OptionListPicker+View.swift | 61 +++ .../Views/SliderPicker+View.swift | 193 +++++++ .../SortFilter/SortFilter+Environment.swift | 77 +++ .../Views/SortFilter/SortFilterContext.swift | 14 + .../SortFilter/SortFilterDialog+View.swift | 63 +++ .../SortFilter/SortFilterFullCFG+View.swift | 91 ++++ .../SortFilter/SortFilterMenu+View.swift | 45 ++ .../SortFilter/SortFilterMenuItem+Style.swift | 102 ++++ .../SortFilter/SortFilterMenuItem+View.swift | 483 +++++++++++++++++ .../Views/SortFilter/SortFilterStyle.swift | 26 + .../_SortFilterCFGItemContainer.swift | 176 ++++++ .../_SortFilterMenuItemContainer.swift | 136 +++++ .../Views/SwitchPicker+View.swift | 153 ++++++ .../Component+Protocols.generated.swift | 48 +- ...mponentProtocols+Extension.generated.swift | 52 +- .../EnvironmentKey+Styles.generated.swift | 30 +- .../EnvironmentValue+Styles.generated.swift | 72 ++- .../API/OptionChip+API.generated.swift | 63 +++ .../API/OptionListPicker+API.generated.swift | 23 + .../API/SliderPicker+API.generated.swift | 23 + .../API/SortFilterFullCFG+API.generated.swift | 103 ++++ .../API/SortFilterMenu+API.generated.swift | 102 ++++ .../SortFilterMenuItem+API.generated.swift | 82 +++ .../API/SwitchPicker+API.generated.swift | 22 + .../OptionChip+View.generated.swift | 62 +++ .../OptionListPicker+View.generated.swift | 34 ++ .../SliderPicker+View.generated.swift | 34 ++ .../SortFilterFullCFG+View.generated.swift | 70 +++ .../SortFilterMenu+View.generated.swift | 70 +++ .../SortFilterMenuItem+View.generated.swift | 66 +++ .../SwitchPicker+View.generated.swift | 34 ++ .../OptionChip+Init.generated.swift | 16 + .../OptionListPicker+Init.generated.swift | 3 + .../SliderPicker+Init.generated.swift | 3 + .../SortFilterFullCFG+Init.generated.swift | 117 ++++ .../SortFilterMenu+Init.generated.swift | 117 ++++ .../SortFilterMenuItem+Init.generated.swift | 47 ++ .../SwitchPicker+Init.generated.swift | 3 + ...ListPickerModel+Extensions.generated.swift | 9 + ...terFullCFGModel+Extensions.generated.swift | 21 + ...FilterMenuModel+Extensions.generated.swift | 21 + 55 files changed, 4049 insertions(+), 28 deletions(-) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift create mode 100644 Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json create mode 100644 Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png create mode 100644 Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift create mode 100644 Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift create mode 100644 Sources/FioriSwiftUICore/Views/OptionChip+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SliderPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift create mode 100644 Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 7f18def4a..22c0a0904 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -125,6 +125,8 @@ B8D4376F25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */; }; B8D4377125F983730024EE7D /* ObjectCell_Rules_Alignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */; }; B8D437732609479E0024EE7D /* SingleActionFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */; }; + C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A0FDB22AD893FA0001738E /* View+Extensions.swift */; }; + C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -307,6 +309,8 @@ B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Spec_Jan2018.swift; sourceTree = ""; }; B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Rules_Alignment.swift; sourceTree = ""; }; B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleActionFollowButton.swift; sourceTree = ""; }; + C1A0FDB22AD893FA0001738E /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; + C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortFilterExample.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -501,6 +505,7 @@ 8A5579C824C1293C0098003A /* FioriSwiftUICore */ = { isa = PBXGroup; children = ( + C1C764862A818BD600BCB0F7 /* SortFilter */, B100639129C0623300AF0CA2 /* StepProgressIndicator */, 108E43D3292DAB3E006532F3 /* EmptyStateView */, B1D41B1E291A2D2E004E64A5 /* Picker */, @@ -700,6 +705,15 @@ path = ObjectItem; sourceTree = ""; }; + C1C764862A818BD600BCB0F7 /* SortFilter */ = { + isa = PBXGroup; + children = ( + C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */, + C1A0FDB22AD893FA0001738E /* View+Extensions.swift */, + ); + path = SortFilter; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -889,6 +903,7 @@ 8A557A2424C12F380098003A /* ChartDetailView.swift in Sources */, 8A5579D024C1293C0098003A /* SettingsLine.swift in Sources */, 1FC30412270540FB004BEE00 /* 72-Fonts.swift in Sources */, + C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */, B84D24ED2652F343007F2373 /* HeaderChartExample.swift in Sources */, B100639329C0624D00AF0CA2 /* StepProgressIndicatorExample.swift in Sources */, B846F94626815CC90085044B /* ContactItemExample.swift in Sources */, @@ -937,6 +952,7 @@ 8A5579D524C1293C0098003A /* SettingsSeries.swift in Sources */, 8A557A2224C12C9B0098003A /* CoreContentView.swift in Sources */, 8A5579D224C1293C0098003A /* Color+Extensions.swift in Sources */, + C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */, B84D24EF2652F343007F2373 /* ObjectHeaderTestApp.swift in Sources */, B84D24EC2652F343007F2373 /* ObjectHeaderSpecCompact.swift in Sources */, 8A5579CD24C1293C0098003A /* SettingsLabel.swift in Sources */, @@ -1116,7 +1132,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1171,7 +1187,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -1188,10 +1204,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"Examples/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = H8QB58T8ZU; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Examples/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1210,10 +1226,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"Examples/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = H8QB58T8ZU; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Examples/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1232,7 +1248,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = ExamplesTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1253,7 +1269,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = ExamplesTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift index fcf70c39e..91545232c 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift @@ -107,6 +107,10 @@ struct CoreContentView: View { destination: EmptyStateViewExample()) { Text("EmptyStateViewExample") } + + NavigationLink(destination: SortFilterExample()) { + Text("SortFilterExample") + } } }.navigationBarTitle("FioriSwiftUICore") } diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift new file mode 100644 index 000000000..213ad14ec --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -0,0 +1,80 @@ +import FioriSwiftUICore +import SwiftUI + +struct SortFilterExample: View { + @State private var items: [[SortFilterItem]] = [[ + .picker(item: .init(value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], name: "JIRA Status", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), isShownOnMenu: true), + .picker(item: .init(value: [0], valueOptions: ["High", "Medium", "Low"], name: "Priority", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), isShownOnMenu: true), + .filterfeedback(item: .init(value: [0], valueOptions: ["Ascending", "Descending"], name: "Sort Order", allowsMultipleSelection: false, allowsEmptySelection: false, icon: "checkmark")), + .slider(item: .init(value: 10, minimumValue: 0, maximumValue: 100, name: "Number of User Stories", icon: "number"), isShownOnMenu: true), + .slider(item: .init(value: nil, minimumValue: 0, maximumValue: 100, name: "Number of Tasks"), isShownOnMenu: true), + .datetime(item: .init(value: Date(), name: "Start Date", icon: "calendar"), isShownOnMenu: true), + .datetime(item: .init(value: nil, name: "Completion Date"), isShownOnMenu: true), + .switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), isShownOnMenu: true), + .switch(item: .init(name: "Tagged", value: nil, icon: "tag"), isShownOnMenu: false) + ]] + + @State private var isShowingFullCFG: Bool = false + @State private var sortFilterList: [String] = [] + @State private var sortFilterButtonLabel: String = "Sort & Filter" + + var body: some View { + VStack { + SortFilterMenu(items: $items, onUpdate: performSortAndFilter) +// .trailingFullConfigurationMenuItem(icon: "command") +// .leadingFullConfigurationMenuItem(icon: "command") +// .leadingFullConfigurationMenuItem(name: "All") +// .sortFilterMenuItemStyle(font: .subheadline, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + + List { + ForEach(sortFilterList, id: \.self) { line in + Text(line) + } + } + .listStyle(PlainListStyle()) + + Button("Print") { + for line in sortFilterList { + print(line) + } + } + } + .navigationTitle("Sort & Filter") + .toolbar { + Button(sortFilterButtonLabel) { + isShowingFullCFG.toggle() + } + } + .sheet(isPresented: $isShowingFullCFG) { + SortFilterFullCFG( + items: $items, + onUpdate: performSortAndFilter + ) + } + .onAppear { + performSortAndFilter() + } + } + + func numberOfItems() -> Int { + // randomly padding result to mimic impact of filterring + for i in 0 ... Int.random(in: 0 ... 5) { + self.sortFilterList.append("Non-SortFilterCFG: element \(i + 1)") + } + return self.sortFilterList.count + } + + func performSortAndFilter() { + self.sortFilterList = self.items.joined().map { value(of: $0) } + self.sortFilterButtonLabel = "Sort & Filter (\(self.numberOfItems()))" + } +} + +#if DEBUG + @available(iOS 16.0, *) + struct SortFilterExample_Previews: PreviewProvider { + static var previews: some View { + SortFilterExample() + } + } +#endif diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift new file mode 100644 index 000000000..10686c555 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift @@ -0,0 +1,35 @@ +import FioriSwiftUICore +import SwiftUI + +extension View { + func value(of item: SortFilterItem) -> String { + switch item { + case .picker(let v, _): + return self.json(item: v) + case .filterfeedback(let v): + return self.json(item: v) + case .slider(let v, _): + return self.json(item: v) + case .datetime(let v, _): + return self.json(item: v) + case .switch(let v, _): + return self.json(item: v) + } + } + + func json(item: PickerItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(item.value)}" + } + + func json(item: SliderItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } + + func json(item: DateTimeItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } + + func json(item: SwitchItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } +} diff --git a/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json new file mode 100644 index 000000000..2945b36b9 --- /dev/null +++ b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d91dd8e60c39253ab9753e4f1c0d137b4e06cbdb GIT binary patch literal 934 zcmV;X16lluP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR916rckD1ONa40RR916aWAK0J7ci{Qv+15J^NqR5%fpR7q=8K@k3`Ur#1# zP;(em)aY$okQ~I5H_?+4m-xnrzd%9sqT&YQilPTWe}v{G2K5p*@ZwD`t{6dF2^w)t z$YkE@F003;K0+Enn5^>MDW|pX^!4#1{#?F+J;N3Pft7AI1RK?4cu<-%$vqhUJOcTYeE!SPz4x-MpalaBV?OT2%N_V%~|lHDf<#OMsmekqr?EP$(w zd!|lVE4<@zm024s!|+I37O25D1C}Dsr?)Rb)pnxA-ayV2y)`>c>-S?O|KoT*FuRaz zkphwE@+;C}iBAJe_hvwqxI~Kuj8pYnoII3*5XK5Yx>O!l9;!|Z|MnGirpj{al`5B_ z7kqB>S9zP=vrX=m?6+X62R*ZvauB)PDtNaTEy*?7H}ENan=ds|jV6uq5p84g?w-)$ z=r)`kN|^3?m^*OQp;Siv95V}}4Up{KoMBxvXRJalpn}}d8V^^r(`@?GeI>UwJ`u{} zrgnTwn!Bd%J1HQQqftKFbysbS%Rj~hcOr4dX*yZ+j;d2OYXHl66k)q|c@GeplM2eVo?S6!>H~AIb7zEPL8C!x}yO*Kl~YoME5N zW0-$jCS+1&~()d5RhhJY&n zhtdI?g6V+WYo?d4z9%;8ka!v65>N;26qnkjQ{K5g#UR1@1r+>sK#p6*NdN!<07*qo IM6N<$g1&*0mH+?% literal 0 HcmV?d00001 diff --git a/Package.swift b/Package.swift index 0b4374da2..48162e04b 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "FioriSwiftUI", defaultLocalization: "en", - platforms: [.iOS(.v15), .watchOS(.v7)], + platforms: [.iOS(.v16), .watchOS(.v7)], products: [ .library( name: "FioriSwiftUI", diff --git a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift new file mode 100644 index 000000000..aceb1c9da --- /dev/null +++ b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift @@ -0,0 +1,109 @@ +import Foundation +import SwiftUI + +struct CancellableResettableDialogForm: View { + let title: Title + + let components: Components + + var cancelAction: CancelAction + var resetAction: ResetAction + var applyAction: ApplyAction + + public init(@ViewBuilder title: () -> Title, + @ViewBuilder cancelAction: () -> CancelAction, + @ViewBuilder resetAction: () -> ResetAction, + @ViewBuilder applyAction: () -> ApplyAction, + @ViewBuilder components: () -> Components) + { + self.title = title() + self.cancelAction = cancelAction() + self.resetAction = resetAction() + self.applyAction = applyAction() + self.components = components() + } + + var body: some View { + VStack(spacing: UIDevice.current.userInterfaceIdiom == .pad ? 8 : 16) { + HStack { + cancelAction + Spacer() + title + Spacer() + resetAction + } + components + applyAction + } + .frame(minWidth: 375) + .padding(UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) + } +} + +// fileprivate extension View { +// func readHeight() -> some View { +// self.modifier(ReadHeightModifier()) +// } +// } +// +// private struct ReadHeightModifier: ViewModifier { +// private var sizeView: some View { +// GeometryReader { geometry in +// Color.clear.preference(key: HeightPreferenceKey.self, value: geometry.size.height) +// } +// } +// +// func body(content: Content) -> some View { +// content.background(sizeView) +// } +// } +// +// + +struct ApplyButtonStyle: PrimitiveButtonStyle { + func makeBody(configuration: Configuration) -> some View { +// GeometryReader { geometry in + // HStack { + // Spacer() + configuration.label + .frame(minWidth: 375, maxWidth: .infinity) + // Spacer() + // } + .onTapGesture { + configuration.trigger() + } + .padding(15) + .font(.system(size: 16, weight: .bold)) + .background(RoundedRectangle(cornerRadius: 5).fill(Color.preferredColor(.tintColor))) + .foregroundColor(.white) + } + +// } +} + +#Preview { + VStack { + Spacer() + CancellableResettableDialogForm { + Text("Date of Completion") + } cancelAction: { + Action(actionText: "Cancel", didSelectAction: nil) + } resetAction: { + Action(actionText: "Reset", didSelectAction: nil) + } applyAction: { + Button {} label: { + Text("Apply") + .frame(width: 375) + } +// Action(actionText: "Apply", didSelectAction: nil) +// .buttonStyle(ApplyButtonStyle()) + } components: { + DatePicker( + "date", + selection: Binding(get: { Date() }, set: { print($0) }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + } +} diff --git a/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift b/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift index 14215991d..e522c4b21 100644 --- a/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift +++ b/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift @@ -53,3 +53,48 @@ internal protocol _DurationPicker: _ComponentMultiPropGenerating, AnyObject { // sourcery: default.value = MeasurementFormatter() var measurementFormatter: MeasurementFormatter { get set } } + +internal protocol _SliderPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Int? { get set } + + // sourcery: no_view + // sourcery: default.value = 0.0 + var minimumValue: Int { get } + + // sourcery: no_view + // sourcery: default.value = 100.0 + var maximumValue: Int { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} + +internal protocol _SwitchPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Bool? { get set } + + // sourcery: no_view + // sourcery: default.value = nil + var name: String? { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} + +internal protocol _OptionListPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: [Int] { get set } + + // sourcery: no_view + var valueOptions: [String] { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} diff --git a/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift b/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift index e9731f208..2020a4b4b 100644 --- a/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift +++ b/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift @@ -51,4 +51,6 @@ internal struct _Component: _ComponentGenerating { // sourcery: backingComponent=FootnoteIconStack // sourcery: customFunctionBuilder=FootnoteIconsBuilder let footnoteIcons_: [TextOrIcon]? + let leftIcon_: Image? + let rightIcon_: Image? } diff --git a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift new file mode 100644 index 000000000..a75e2987a --- /dev/null +++ b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift @@ -0,0 +1,500 @@ +import SwiftUI +import UIKit + +public enum SortFilterItem: Identifiable, Hashable { + public var id: String { + switch self { + case .picker(let item, _): + return item.id + case .filterfeedback(let item): + return item.id + case .switch(let item, _): + return item.id + case .slider(let item, _): + return item.id + case .datetime(let item, _): + return item.id + } + } + + case picker(item: PickerItem, isShownOnMenu: Bool) + case filterfeedback(item: PickerItem) + case `switch`(item: SwitchItem, isShownOnMenu: Bool) + case slider(item: SliderItem, isShownOnMenu: Bool) + case datetime(item: DateTimeItem, isShownOnMenu: Bool) + + public var isShownOnMenu: Bool { + switch self { + case .picker(_, let isShownOnMenu): + return isShownOnMenu + case .filterfeedback: + return true + case .switch(_, let isShownOnMenu): + return isShownOnMenu + case .slider(_, let isShownOnMenu): + return isShownOnMenu + case .datetime(_, let isShownOnMenu): + return isShownOnMenu + } + } + + public func hash(into hasher: inout Hasher) { + switch self { + case .picker(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .filterfeedback(let item): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .switch(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .slider(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .datetime(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + } + } +} + +/// (value: [Int], valueOptions: [String], keyName: String?, allowsMultipleSelection: Bool, allowsEmptySelection: Bool) +public struct PickerItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + + public var value: [Int] + public var workingValue: [Int] + let originalValue: [Int] + + var valueOptions: [String] + public let allowsMultipleSelection: Bool + public let allowsEmptySelection: Bool + public let icon: String? + + public init(value: [Int], valueOptions: [String], name: String, allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.valueOptions = valueOptions + self.name = name + self.allowsMultipleSelection = allowsMultipleSelection + self.allowsEmptySelection = allowsEmptySelection + self.icon = icon + } + + mutating func onTap(option: String) { + guard let index = valueOptions.firstIndex(of: option) else { return } + if self.workingValue.contains(index) { + if self.workingValue.count > 1 { + self.workingValue = self.workingValue.filter { $0 != index } + } else { + if self.allowsEmptySelection { + self.workingValue = [] + } else { + self.workingValue = index == 1 ? [0] : [1] + } + } + } else { + if self.allowsMultipleSelection { + self.workingValue.append(index) + } else { + self.workingValue = [index] + } + } + } + + mutating func optionOnTap(_ index: Int) { + if self.workingValue.contains(index) { + if self.workingValue.count > 1 { + self.workingValue = self.workingValue.filter { $0 != index } + } else { + if self.allowsEmptySelection { + self.workingValue = [] + } else { + self.workingValue = index == 1 ? [0] : [1] + } + } + } else { + if self.allowsMultipleSelection { + self.workingValue.append(index) + } else { + self.workingValue = [index] + } + } + } + + mutating func cancel() { + self.workingValue = self.value.map { $0 } + } + + mutating func reset() { + self.workingValue = self.originalValue.map { $0 } + } + + mutating func apply() { + self.value = self.workingValue.map { $0 } + } + + func isOptionSelected(_ option: String) -> Bool { + guard let idx = valueOptions.firstIndex(of: option) else { return false } + return self.workingValue.contains(idx) + } + + func isOptionSelected(index: Int) -> Bool { + self.workingValue.contains(index) + } + + var isChecked: Bool { + !self.value.isEmpty + } + + var label: String { + self.value.count >= 1 ? "\(self.name) (\(self.value.count))" : "\(self.name)" + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +/// (value: Bool, keyName: String) +public struct SwitchItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + public var value: Bool? + var workingValue: Bool? + let originalValue: Bool? + public let icon: String? + public let hint: String? + + public init(id: String = UUID().uuidString, name: String, value: Bool?, icon: String? = nil, hint: String? = nil) { + self.id = id + self.name = name + self.value = value + self.workingValue = value + self.originalValue = value + self.icon = icon + self.hint = hint + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + mutating func apply() { + self.value = self.workingValue + } + + var isChecked: Bool { + self.value ?? false + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +/// (value: Float, minimumValue: Float, maximumValue: Float, keyName: String?) +public struct SliderItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + + public var value: Int? + var workingValue: Int? + let originalValue: Int? + public let minimumValue: Int + public let maximumValue: Int + public let icon: String? + public let hint: String? + + public init(value: Int? = nil, minimumValue: Int, maximumValue: Int, name: String, icon: String? = nil, hint: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.minimumValue = minimumValue + self.maximumValue = maximumValue + self.name = name + self.icon = icon + self.hint = hint + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + mutating func apply() { + self.value = self.workingValue + } + + var isChecked: Bool { + self.value != nil + } + + var label: String { + name + } + + mutating func setValue(newValue: SliderItem) { + self.value = newValue.value + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +public struct DateTimeItem: Equatable, Hashable { + public let id = UUID().uuidString + + public var name: String + public var value: Date? + var workingValue: Date? + let originalValue: Date? + public var icon: String? + + public var datePickerMode: UIDatePicker.Mode? + public var dateFormatter: DateFormatter? + + public init(value: Date?, name: String, datePickerMode: UIDatePicker.Mode? = nil, dateFormatter: DateFormatter? = nil, icon: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.name = name + self.datePickerMode = datePickerMode + self.dateFormatter = dateFormatter + self.icon = icon + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func apply() { + self.value = self.workingValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + var isChecked: Bool { + self.value != nil + } + + var label: String { + self.name + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +extension SortFilterItem { + var picker: PickerItem { + get { + switch self { + case .picker(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .picker(_, let isShownOnMenu): + self = .picker(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var filterfeedback: PickerItem { + get { + switch self { + case .filterfeedback(let item): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .filterfeedback: + self = .filterfeedback(item: newValue) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var slider: SliderItem { + get { + switch self { + case .slider(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .slider(_, let isShownOnMenu): + self = .slider(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var datetime: DateTimeItem { + get { + switch self { + case .datetime(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .datetime(_, let isShownOnMenu): + self = .datetime(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var `switch`: SwitchItem { + get { + switch self { + case .switch(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .switch(_, let isShownOnMenu): + self = .switch(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + public var isChanged: Bool { + switch self { + case .picker(let item, _): + return item.isChanged + case .filterfeedback(let item): + return item.isChanged + case .switch(let item, _): + return item.isChanged + case .datetime(let item, _): + return item.isChanged + case .slider(let item, _): + return item.isChanged + } + } + + public mutating func cancel() { + switch self { + case .picker(var item, _): + item.cancel() + self.picker = item + case .filterfeedback(var item): + item.cancel() + self.filterfeedback = item + case .switch(var item, _): + item.cancel() + self.switch = item + case .datetime(var item, _): + item.cancel() + self.datetime = item + case .slider(var item, _): + item.cancel() + self.slider = item + } + } + + public mutating func reset() { + switch self { + case .picker(var item, _): + item.reset() + self.picker = item + case .filterfeedback(var item): + item.reset() + self.filterfeedback = item + case .switch(var item, _): + item.reset() + self.switch = item + case .datetime(var item, _): + item.reset() + self.datetime = item + case .slider(var item, _): + item.reset() + self.slider = item + } + } + + public mutating func apply() { + switch self { + case .picker(var item, _): + item.apply() + self.picker = item + case .filterfeedback(var item): + item.apply() + self.filterfeedback = item + case .switch(var item, _): + item.apply() + self.switch = item + case .datetime(var item, _): + item.apply() + self.datetime = item + case .slider(var item, _): + item.apply() + self.slider = item + } + } +} + +/* + Notes: + a. add hint text to slider, stepper, etc. + b. support range on slider + c. to resolve: keyName should not be nillable for menu item, but it can be nil for sheet + d. fix reset func (bug on original value) + e. make filter feedback configuraion a separate item type, instead of sharing FilterItem. + */ diff --git a/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift b/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift index 420d1ad11..7c615eecb 100644 --- a/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift +++ b/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift @@ -24,6 +24,22 @@ public struct _CancelActionDefault: ActionModel { public init() {} } +public struct _ResetActionDefault: ActionModel { + public var actionText: String? { + NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") + } + + public init() {} +} + +public struct _ApplyActionDefault: ActionModel { + public var actionText: String? { + NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") + } + + public init() {} +} + public struct _AgreeActionDefault: ActionModel { public var actionText: String? { NSLocalizedString("Agree", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index 41941e396..2cd13e6e5 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -433,3 +433,83 @@ public protocol StepProgressIndicatorModel: AnyObject { // sourcery: default.value = _CancelActionDefault() var cancelAction: ActionModel? { get } } + +// sourcery: generated_component_composite +public protocol SortFilterMenuModel: AnyObject { + // sourcery: bindingProperty + // sourcery: backingComponent=_SortFilterMenuItemContainer + var items: [[SortFilterItem]] { get set } + + // sourcery: genericParameter.name = CancelActionView + // sourcery: default.value = _CancelActionDefault() + var cancelAction: ActionModel? { get } + + // sourcery: genericParameter.name = ResetActionView + // sourcery: default.value = _ResetActionDefault() + var resetAction: ActionModel? { get } + + // sourcery: genericParameter.name = ApplyActionView + // sourcery: default.value = _ApplyActionDefault() + var applyAction: ActionModel? { get } + + // sourcery: default.value = nil + // sourcery: no_view + var onUpdate: (() -> Void)? { get set } +} + +// sourcery: virtualPropActionHelper = "@State var context: SortFilterContext = SortFilterContext()" +// sourcery: add_env_props = "dismiss" +// sourcery: generated_component_composite +public protocol SortFilterFullCFGModel: AnyObject { + // sourcery: bindingProperty + // sourcery: backingComponent=_SortFilterCFGItemContainer + var items: [[SortFilterItem]] { get set } + + // sourcery: genericParameter.name = CancelActionView + // sourcery: default.value = _CancelActionDefault() + var cancelAction: ActionModel? { get } + + // sourcery: genericParameter.name = ResetActionView + // sourcery: default.value = _ResetActionDefault() + var resetAction: ActionModel? { get } + + // sourcery: genericParameter.name = ApplyActionView + // sourcery: default.value = _ApplyActionDefault() + var applyAction: ActionModel? { get } + + // sourcery: default.value = nil + // sourcery: no_view + var onUpdate: (() -> Void)? { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: virtualPropActionHelper = "@State var context: SortFilterContext = SortFilterContext()" +// sourcery: generated_component_composite +public protocol SortFilterMenuItemModel: LeftIconComponent, TitleComponent, RightIconComponent { + // sourcery: no_view + var isSelected: Bool { get } +} + +// sourcery: add_env_props = "optionChipStyle" +// sourcery: generated_component_composite +public protocol OptionChipModel: LeftIconComponent, TitleComponent { + // sourcery: no_view + var isSelected: Bool { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +public protocol OptionListPickerModel: OptionListPickerComponent { + // sourcery: default.value = nil + // sourcery: no_view + var onTap: ((_ index: Int) -> Void)? { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +// sourcery: add_env_props = "fioriToggleStyle" +public protocol SwitchPickerModel: SwitchPickerComponent {} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +public protocol SliderPickerModel: SliderPickerComponent {} diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift new file mode 100644 index 000000000..82bcc02e9 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -0,0 +1,182 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum OptionChip { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + } +} + +extension OptionChip: View { + public var body: some View { + optionChipStyle.makeBody(configuration: OptionChipConfiguration(leftIcon: AnyView(leftIcon), title: AnyView(title), isSelected: _isSelected)) + } +} + +/* + // FIXME: - Implement OptionChip specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct OptionChipLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionChip(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +public struct OptionChipConfiguration { + let leftIcon: AnyView + let title: AnyView + let isSelected: Bool + + public init(leftIcon: AnyView, title: AnyView, isSelected: Bool) { + self.leftIcon = leftIcon + self.title = title + self.isSelected = isSelected + } +} + +public protocol OptionChipStyle { + associatedtype Body = View + + typealias Configuration = OptionChipConfiguration + + func makeBody(configuration: Self.Configuration) -> AnyView // Self.Body +} + +public struct DefaultOptionChipStyle: OptionChipStyle { + let font: Font + let foregroundColorSelected: Color + let foregroundColorUnselected: Color + let fillColorSelected: Color + let fillColorUnselected: Color + let strokeColorSelected: Color + let strokeColorUnselected: Color + let cornerRadius: CGFloat + let spacing: CGFloat + let padding: CGFloat + let borderWidth: CGFloat + let minHeight: CGFloat + + public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) { + self.font = font + self.foregroundColorSelected = foregroundColorSelected + self.foregroundColorUnselected = foregroundColorUnselected + self.fillColorSelected = fillColorSelected + self.fillColorUnselected = fillColorUnselected + self.strokeColorSelected = strokeColorSelected + self.strokeColorUnselected = strokeColorUnselected + self.cornerRadius = cornerRadius + self.spacing = spacing + self.padding = padding + self.borderWidth = borderWidth + self.minHeight = minHeight + } + + public func makeBody(configuration: Configuration) -> AnyView { + AnyView( + HStack(spacing: self.spacing) { + configuration.leftIcon + configuration.title + } + .font(self.font) + .foregroundColor(configuration.isSelected ? self.foregroundColorSelected : self.foregroundColorUnselected) + .padding(self.padding) + .frame(maxWidth: .infinity) + .background( + ZStack { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(configuration.isSelected ? fillColorSelected : fillColorUnselected) + RoundedRectangle(cornerRadius: cornerRadius) + .stroke(configuration.isSelected ? strokeColorSelected : strokeColorUnselected, lineWidth: borderWidth) + } + ) + ) + } +} + +struct OptionChipStyleKey: EnvironmentKey { + static var defaultValue: any OptionChipStyle = DefaultOptionChipStyle() +} + +extension EnvironmentValues { + var optionChipStyle: any OptionChipStyle { + get { + self[OptionChipStyleKey.self] + } + set { + self[OptionChipStyleKey.self] = newValue + } + } +} + +public extension View { + func optionChipStyle(_ style: S) -> some View where S: OptionChipStyle { + self.environment(\.optionChipStyle, style) + } + + func optionChipStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { + self.environment(\.optionChipStyle, + DefaultOptionChipStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) + } +} + +#Preview { + VStack { + Spacer() + + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: true) + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: false) + OptionChip(title: "Ship", isSelected: true) + OptionChip(title: "Ship", isSelected: false) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) + + Spacer() + + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: true) + .optionChipStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: false) + .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + + .optionChipStyle(cornerRadius: 16) + OptionChip(title: "Ship", isSelected: true) + .optionChipStyle(fillColorSelected: .yellow) + OptionChip(title: "Ship", isSelected: false) + .optionChipStyle(fillColorUnselected: .gray) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) + .optionChipStyle(cornerRadius: 20) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) + .optionChipStyle(cornerRadius: 20) + + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift new file mode 100644 index 000000000..f46c06e72 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift @@ -0,0 +1,61 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +import SwiftUI + +extension OptionListPicker: View { + public var body: some View { + Grid { + ForEach(0 ..< Int(ceil(Double(_valueOptions.count) / 2.0))) { rowIndex in + GridRow { + OptionChip( + leftIcon: _value.wrappedValue.contains(rowIndex * 2) ? Image(systemName: "checkmark") : nil, + title: _valueOptions[rowIndex * 2], + isSelected: _value.wrappedValue.contains(rowIndex * 2) + ) + .onTapGesture { + _onTap?(rowIndex * 2) + } + if rowIndex * 2 + 1 < _valueOptions.count { + OptionChip( + leftIcon: _value.wrappedValue.contains(rowIndex * 2 + 1) ? Image(systemName: "checkmark") : nil, + title: _valueOptions[rowIndex * 2 + 1], + isSelected: _value.wrappedValue.contains(rowIndex * 2 + 1) + ) + .onTapGesture { + _onTap?(rowIndex * 2 + 1) + } + } + } + } + } + } +} + +/* + // FIXME: - Implement OptionListPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct OptionListPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionListPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +#Preview { + VStack { + Spacer() + OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + .frame(width: 375) + Spacer() + OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + .optionChipStyle(font: .title, foregroundColorSelected: Color.red, strokeColorSelected: Color.red, cornerRadius: 25) + .frame(width: 375) + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift new file mode 100644 index 000000000..9e7d43c82 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift @@ -0,0 +1,193 @@ +import SwiftUI + +extension SliderPicker: View { + public var body: some View { + VStack { + HStack { + Text("Range: \(_minimumValue) - \(_maximumValue)") + Spacer() + } + HStack { + Slider(value: .convert(from: _value, ifNilUse: _minimumValue), in: Float(_minimumValue) ... Float(_maximumValue), step: 1.0) + TextField("", value: Binding(get: { _value.wrappedValue ?? _minimumValue }, set: { _value.wrappedValue = $0 }), format: .number) + .frame(width: calcWidth(font: .body)) + .keyboardType(.numberPad) + .textFieldStyle(.roundedBorder) +// TextField("", value: _value.wrappedValue, format: .number) +// Text("\(_value.wrappedValue ?? _minimumValue)") +// .padding(10) +// .foregroundColor(_value.wrappedValue != nil ? Color.preferredColor(.tintColor) : Color.preferredColor(.separator)) +// .background( +// RoundedRectangle(cornerRadius: 10) +// .stroke(_value.wrappedValue != nil ? Color.preferredColor(.tintColor) : Color.preferredColor(.separator), lineWidth: 1) +// ) + } + if let hint = _hint { + HStack { + Text(hint) + Spacer() + } + } + } + } +} + +private extension SliderPicker { + func calcWidth(font: Font) -> CGFloat { + var width: CGFloat = 0 + for i in 0 ... 9 { + let fontAttributes = [NSAttributedString.Key.font: self.preferredFont(from: font)] + let size = String(i).size(withAttributes: fontAttributes) + width = max(size.width, width) + } + return floor(log10(CGFloat(_maximumValue)) + 1) * width + 2 * 12 + } + + func preferredFont(from font: Font) -> UIFont { + let uiFont: UIFont + + switch font { + case .largeTitle: + uiFont = UIFont.preferredFont(forTextStyle: .largeTitle) + case .title: + uiFont = UIFont.preferredFont(forTextStyle: .title1) + case .title2: + uiFont = UIFont.preferredFont(forTextStyle: .title2) + case .title3: + uiFont = UIFont.preferredFont(forTextStyle: .title3) + case .headline: + uiFont = UIFont.preferredFont(forTextStyle: .headline) + case .subheadline: + uiFont = UIFont.preferredFont(forTextStyle: .subheadline) + case .callout: + uiFont = UIFont.preferredFont(forTextStyle: .callout) + case .caption: + uiFont = UIFont.preferredFont(forTextStyle: .caption1) + case .caption2: + uiFont = UIFont.preferredFont(forTextStyle: .caption2) + case .footnote: + uiFont = UIFont.preferredFont(forTextStyle: .footnote) + case .body: + fallthrough + default: + uiFont = UIFont.preferredFont(forTextStyle: .body) + } + + return uiFont + } +} + +struct RangeIntegerStyle: ParseableFormatStyle { + var parseStrategy: RangeIntegerStrategy = .init() + let range: ClosedRange + + func format(_ value: Int) -> String { + let constrainedValue = min(max(value, range.lowerBound), self.range.upperBound) + return "\(constrainedValue)" + } +} + +struct RangeIntegerStrategy: ParseStrategy { + func parse(_ value: String) throws -> Int { + Int(value) ?? 1 + } +} + +extension FormatStyle where Self == RangeIntegerStyle { + static func ranged(_ range: ClosedRange) -> RangeIntegerStyle { + RangeIntegerStyle(range: range) + } +} + +extension Binding { + static func convert(from intBinding: Binding, ifNilUse defaultValue: TInt) -> Binding where TInt: BinaryInteger, TFloat: BinaryFloatingPoint { + Binding( + get: { + TFloat(intBinding.wrappedValue ?? defaultValue) + }, + set: { + intBinding.wrappedValue = TInt($0) + } + ) + } +} + +/* + // FIXME: - Implement SliderPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SliderPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SliderPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private struct SliderPickeTestView: View { + @State var value1: Int? = 10 + @State var value2: Int? = 20 + @State var value3: Int? = nil + + var body: some View { + VStack { + Spacer() + HStack { + Text("Value 1: \($value1.wrappedValue ?? 0)") + .font(.largeTitle) + .foregroundColor(value1 != nil ? .blue : .gray) + Spacer() + } + SliderPicker(value: Binding( + get: { + value1 + }, + set: { + value1 = $0 + } + ), minimumValue: 0, maximumValue: 1000, hint: nil) + Spacer() + HStack { + Text("Value 2: \(value2 ?? 0)") + .font(.largeTitle) + .foregroundColor(value2 != nil ? .blue : .gray) + + Spacer() + } + + SliderPicker(value: Binding( + get: { + value2 + }, + set: { + value2 = $0 + } + ), minimumValue: 0, maximumValue: 100, hint: "Pick an integer value") + Spacer() + HStack { + Text("Value 3: \(value3 ?? 0)") + .font(.largeTitle) + .foregroundColor(value3 != nil ? .blue : .gray) + + Spacer() + } + SliderPicker(value: Binding( + get: { + value3 + }, + set: { + value3 = $0 + } + ), minimumValue: 0, maximumValue: 100, hint: "Pick an integer value") + Spacer() + } + .frame(width: 375) + } +} + +struct SliderPickeTestView_Previews: PreviewProvider { + static var previews: some View { + SliderPickeTestView() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift new file mode 100644 index 000000000..cc6cd5f26 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift @@ -0,0 +1,77 @@ +import SwiftUI + +struct SortFilterOnModelUpdateAppCallbackKey: EnvironmentKey { + static let defaultValue: () -> Void = { print("default empty callback") } +} + +struct SortFilterMenuCancelActionKey: EnvironmentKey { + static var defaultValue: any View = Button("TO BE REPLACED") {} +} + +struct SortFilterMenuResetActionKey: EnvironmentKey { + static var defaultValue: any View = Button("TO BE REPLACED") {} +} + +struct SortFilterMenuApplyActionKey: EnvironmentKey { + static var defaultValue: any View = Button("TO BE REPLACED") {} +} + +extension EnvironmentValues { + var onModelUpdateAppCallback: () -> Void { + get { + self[SortFilterOnModelUpdateAppCallbackKey.self] + } + + set { + self[SortFilterOnModelUpdateAppCallbackKey.self] = newValue + } + } + + var cancelActionView: any View { + get { + self[SortFilterMenuCancelActionKey.self] + } + + set { + self[SortFilterMenuCancelActionKey.self] = newValue + } + } + + var resetActionView: any View { + get { + self[SortFilterMenuResetActionKey.self] + } + + set { + self[SortFilterMenuResetActionKey.self] = newValue + } + } + + var applyActionView: any View { + get { + self[SortFilterMenuApplyActionKey.self] + } + + set { + self[SortFilterMenuApplyActionKey.self] = newValue + } + } +} + +extension View { + func onModelUpdateAppCallback(_ closure: @escaping () -> Void) -> some View { + self.environment(\.onModelUpdateAppCallback, closure) + } + + func cancelActionView(_ view: any View) -> some View { + self.environment(\.cancelActionView, view) + } + + func resetActionView(_ view: any View) -> some View { + self.environment(\.resetActionView, view) + } + + func applyActionView(_ view: any View) -> some View { + self.environment(\.applyActionView, view) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift new file mode 100644 index 000000000..c87a7c066 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift @@ -0,0 +1,14 @@ +import SwiftUI + +class SortFilterContext: ObservableObject { + @Published public var isResetButtonEnabled: Bool = false + @Published public var isApplyButtonEnabled: Bool = false + + @Published public var handleCancel: (() -> Void)? + @Published public var handleReset: (() -> Void)? + @Published public var handleApply: (() -> Void)? + + @Published public var handleDismiss: (() -> Void)? + + public init() {} +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift new file mode 100644 index 000000000..cc4939d47 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift @@ -0,0 +1,63 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* + import SwiftUI + + // FIXME: - Implement Fiori style definitions + + extension Fiori { + enum SortFilterDialog { + typealias Item = EmptyModifier + typealias ItemCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let item = Item() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let itemCumulative = ItemCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } + } + + // FIXME: - Implement SortFilterDialog View body + + extension SortFilterDialog: View { + public var body: some View { + <# View body #> + } + } + + // FIXME: - Implement SortFilterDialog specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterDialogLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterDialog(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift new file mode 100644 index 000000000..414b9b127 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift @@ -0,0 +1,91 @@ +import SwiftUI + +extension Fiori { + enum SortFilterFullCFG { + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + static let items = Items() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let itemsCumulative = ItemsCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } +} + +extension SortFilterFullCFG: View { + public var body: some View { + CancellableResettableDialogForm { + Text("Sort & Filter") + } cancelAction: { + cancelAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Cancel...") + context.handleCancel?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + dismiss() + } + ) + } resetAction: { + resetAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Reset...") + context.handleReset?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + dismiss() + } + ) + + } applyAction: { + applyAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Apply...") + context.handleApply?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + _onUpdate?() + dismiss() + } + ) + + .buttonStyle(ApplyButtonStyle()) + } components: { + _items + .environmentObject(context) +// .onModelUpdateAppCallback(_onUpdate!) +// .cancelActionView(cancelAction) +// .resetActionView(resetAction) +// .applyActionView(applyAction) + } + } +} + +/* + // FIXME: - Implement SortFilterFullCFG specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterFullCFGLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterFullCFG(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift new file mode 100644 index 000000000..d21966993 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift @@ -0,0 +1,45 @@ +import SwiftUI + +extension Fiori { + enum SortFilterMenu { + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + static let items = Items() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let itemsCumulative = ItemsCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } +} + +extension SortFilterMenu: View { + public var body: some View { + items + .onModelUpdateAppCallback(_onUpdate!) +// .cancelActionView(_cancelAction) +// .resetActionView(_resetAction) +// .applyActionView(_applyAction) + } +} + +/* + // FIXME: - Implement SortFilterMenu specific LibraryContentProvider + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterMenuLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenu(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift new file mode 100644 index 000000000..6ae4230e3 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift @@ -0,0 +1,102 @@ +import FioriThemeManager +import SwiftUI + +public struct SortFilterMenuItemConfiguration { + let leftIcon: AnyView + let title: AnyView + let isSelected: Bool + let rightIcon: AnyView + + public init(leftIcon: AnyView, title: AnyView, isSelected: Bool, rightIcon: AnyView) { + self.leftIcon = leftIcon + self.title = title + self.isSelected = isSelected + self.rightIcon = rightIcon + } +} + +public protocol SortFilterMenuItemStyle { + associatedtype Body = View + + typealias Configuration = SortFilterMenuItemConfiguration + + func makeBody(configuration: Self.Configuration) -> AnyView // Self.Body +} + +public struct DefaultSortFilterMenuItemStyle: SortFilterMenuItemStyle { + let font: Font + let foregroundColorSelected: Color + let foregroundColorUnselected: Color + let fillColorSelected: Color + let fillColorUnselected: Color + let strokeColorSelected: Color + let strokeColorUnselected: Color + let cornerRadius: CGFloat + let spacing: CGFloat + let padding: CGFloat + let borderWidth: CGFloat + let minHeight: CGFloat + + public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) { + self.font = font + self.foregroundColorSelected = foregroundColorSelected + self.foregroundColorUnselected = foregroundColorUnselected + self.fillColorSelected = fillColorSelected + self.fillColorUnselected = fillColorUnselected + self.strokeColorSelected = strokeColorSelected + self.strokeColorUnselected = strokeColorUnselected + self.cornerRadius = cornerRadius + self.spacing = spacing + self.padding = padding + self.borderWidth = borderWidth + self.minHeight = minHeight + } + + public func makeBody(configuration: Configuration) -> AnyView { + AnyView( + HStack(spacing: self.spacing) { + configuration.leftIcon + configuration.title + configuration.rightIcon + } + .font(self.font) + .foregroundColor(configuration.isSelected ? self.foregroundColorSelected : self.foregroundColorUnselected) + .padding(self.padding) + .frame(minHeight: self.minHeight) + .background( + ZStack { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(configuration.isSelected ? fillColorSelected : fillColorUnselected) + RoundedRectangle(cornerRadius: cornerRadius) + .stroke(configuration.isSelected ? strokeColorSelected : strokeColorUnselected, lineWidth: borderWidth) + } + ) + ) + } +} + +struct SortFilterMenuItemStyleKey: EnvironmentKey { + static var defaultValue: any SortFilterMenuItemStyle = DefaultSortFilterMenuItemStyle() +} + +extension EnvironmentValues { + var sortFilterMenuItemStyle: any SortFilterMenuItemStyle { + get { + self[SortFilterMenuItemStyleKey.self] + } + set { + self[SortFilterMenuItemStyleKey.self] = newValue + } + } +} + +public extension View { + func sortFilterMenuItemStyle(_ style: S) -> some View where S: SortFilterMenuItemStyle { + self.environment(\.sortFilterMenuItemStyle, style) + } + + func sortFilterMenuItemStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { + self.environment(\.sortFilterMenuItemStyle, + DefaultSortFilterMenuItemStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift new file mode 100644 index 000000000..6fb3240d0 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -0,0 +1,483 @@ +import SwiftUI + +extension Fiori { + enum SortFilterMenuItem { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + typealias RightIcon = EmptyModifier + typealias RightIconCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let rightIcon = RightIcon() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + static let rightIconCumulative = RightIconCumulative() + } +} + +extension SortFilterMenuItem: View { + public var body: some View { + sortFilterMenuItemStyle.makeBody(configuration: SortFilterMenuItemConfiguration(leftIcon: AnyView(_leftIcon), title: AnyView(_title), isSelected: _isSelected, rightIcon: AnyView(_rightIcon))).typeErased + } +} + +/* + // FIXME: - Implement SortFilterMenuItem specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterMenuItemLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenuItem(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private extension View { + func icon(name: String?, isVisible: Bool) -> Image? { + if isVisible { + if let name = name { + return Image(systemName: name) + } + } + return nil + } +} + +struct FilterFeedbackMenuItem: View { + @Binding var item: PickerItem + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + Group { + ForEach($item.valueOptions.wrappedValue, id: \.self) { opt in + SortFilterMenuItem(leftIcon: item.isOptionSelected(opt) ? icon(name: item.icon, isVisible: true) : nil, title: opt, isSelected: item.isOptionSelected(opt)) + .onTapGesture { + item.onTap(option: opt) + item.apply() + onUpdate() + } + } + } + } +} + +struct SliderMenuItem: View { + @Environment(\.cancelActionView) var cancelActionView + @Environment(\.resetActionView) var resetActionView + @Environment(\.applyActionView) var applyActionView + + @Binding var item: SliderItem + + @State var isSheetVisible = false + + @State var detentHeight: CGFloat = 0 + + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + Text(item.name) + } cancelAction: { +// _CancelActionDefault() +// .simultaneousGesture( +// TapGesture() +// .onEnded { _ in +// print("...Cancel...") +// item.cancel() +// isSheetVisible.toggle() +// } +// ) + Action(actionText: "Cancel", didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + } resetAction: { + Action(actionText: "Reset", didSelectAction: { + item.reset() + }) + } applyAction: { + Action(actionText: "Apply", didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + + } components: { + SliderPicker(value: Binding(get: { item.workingValue }, set: { item.workingValue = $0 }), minimumValue: item.minimumValue, maximumValue: item.maximumValue) + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } +} + +struct PickerMenuItem: View { + @Binding var item: PickerItem + var onUpdate: () -> Void + + @State var isSheetVisible = false + + @State var detentHeight: CGFloat = 0 + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + if item.valueOptions.count > 4 { + button + } else { + menu + } + } + + @ViewBuilder + var button: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + Text(item.name) + } cancelAction: { + Action(actionText: "Cancel", didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + } resetAction: { + Action(actionText: "Reset", didSelectAction: { + item.reset() + }) + } applyAction: { + Action(actionText: "Apply", didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + } components: { + OptionListPicker(value: $item.workingValue, valueOptions: item.valueOptions, hint: nil) { index in + item.onTap(option: item.valueOptions[index]) + } + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } + + @ViewBuilder + var menu: some View { + HStack { + Menu { + ForEach(item.valueOptions.indices) { idx in + if item.isOptionSelected(index: idx) { + Button { + item.onTap(option: item.valueOptions[idx]) + item.apply() + onUpdate() + } label: { + Label { Text(item.valueOptions[idx]) } icon: { Image(fioriName: "fiori.accept") } + } + } else { + Button(item.valueOptions[idx]) { + item.onTap(option: item.valueOptions[idx]) + item.apply() + onUpdate() + } + } + } + } label: { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, isSelected: item.isChecked) + } + } + } +} + +struct HeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat? + + static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) { + guard let nextValue = nextValue() else { return } + value = nextValue + } +} + +private extension View { + func readHeight() -> some View { + self.modifier(ReadHeightModifier()) + } +} + +private struct ReadHeightModifier: ViewModifier { + private var sizeView: some View { + GeometryReader { geometry in + Color.clear.preference(key: HeightPreferenceKey.self, value: geometry.size.height) + } + } + + func body(content: Content) -> some View { + content.background(self.sizeView) + } +} + +struct DateTimeMenuItem: View { + @Environment(\.cancelActionView) var cancelActionView + @Environment(\.resetActionView) var resetActionView + @Environment(\.applyActionView) var applyActionView + + @Binding private var item: DateTimeItem + + @State private var isSheetVisible: Bool = false + + @State var detentHeight: CGFloat = 0 + + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + Text(item.name) + } cancelAction: { + Action(actionText: "Cancel", didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + } resetAction: { + Action(actionText: "Reset", didSelectAction: { + item.reset() + }) + } applyAction: { + Action(actionText: "Apply", didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + } components: { + VStack { + HStack { + Text("Time") + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + DatePicker( + "", + selection: Binding(get: { item.workingValue ?? Date() }, set: { item.workingValue = $0 }), + displayedComponents: [.hourAndMinute] + ) + .labelsHidden() + } + + DatePicker( + item.label, + selection: Binding(get: { item.workingValue ?? Date() }, set: { item.workingValue = $0 }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } +} + +struct SwitchMenuItem: View { + @Environment(\.cancelActionView) var cancelActionView + @Environment(\.resetActionView) var resetActionView + @Environment(\.applyActionView) var applyActionView + + @Binding private var item: SwitchItem + +// @State var detentHeight: CGFloat = 0 + +// @State private var isSheetVisible: Bool = false + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, isSelected: item.isChecked) + .onTapGesture { + if item.value != nil { + item.workingValue?.toggle() + item.apply() + onUpdate() + } else { + item.workingValue = true + item.apply() + onUpdate() + } +// isSheetVisible.toggle() + } +// .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { +// CancellableResettableDialogForm { +// Text(item.name) +// } cancelAction: { +// Action(actionText: "Cancel", didSelectAction: { +// item.cancel() +// isSheetVisible.toggle() +// }) +// } resetAction: { +// Action(actionText: "Reset", didSelectAction: { +// item.reset() +// }) +// } applyAction: { +// Action(actionText: "Apply", didSelectAction: { +// item.apply() +// onUpdate() +// isSheetVisible.toggle() +// }) +// .buttonStyle(ApplyButtonStyle()) +// } components: { +// SwitchPicker(value: $item.workingValue) +// } +// } + } +} + +struct FullCFGMenuItem: View { + @Environment(\.cancelActionView) var cancelActionView + @Environment(\.resetActionView) var resetActionView + @Environment(\.applyActionView) var applyActionView + @Environment(\.sortFilterMenuItemFullConfigurationButton) var fullCFGButton + + @Binding var items: [[SortFilterItem]] + + @State var isSheetVisible = false + + var onUpdate: () -> Void + + public init(items: Binding<[[SortFilterItem]]>, onUpdate: @escaping () -> Void) { + self._items = items + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: fullCFGButton.icon, isVisible: true), title: fullCFGButton.name ?? "", isSelected: true) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + SortFilterFullCFG( + items: { + _SortFilterCFGItemContainer(items: $items) + }, + cancelAction: { + Action(actionText: "Apply", didSelectAction: { + // item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + }, + resetAction: { + Action(actionText: "Cancel", didSelectAction: { + // item.cancel() + isSheetVisible.toggle() + }) + }, + applyAction: { + Action(actionText: "Reset", didSelectAction: { + //// item.reset() + }) + }, + onUpdate: {} + ) + } + } +} + +#Preview { + VStack { + Spacer() + + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) + + Spacer() + + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + .sortFilterMenuItemStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + .sortFilterMenuItemStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + + .sortFilterMenuItemStyle(cornerRadius: 16) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + .sortFilterMenuItemStyle(fillColorSelected: .yellow) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + .sortFilterMenuItemStyle(fillColorUnselected: .gray) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) + .sortFilterMenuItemStyle(cornerRadius: 20) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) + .sortFilterMenuItemStyle(cornerRadius: 20) + + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift new file mode 100644 index 000000000..e4ca740a5 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift @@ -0,0 +1,26 @@ +import FioriThemeManager +import SwiftUI + +public final class SortFilterStyle { + public static var instance = SortFilterStyle(iconForCheckedItem: Image(fioriName: "fiori.accept")) + + let iconForCheckedItem: Image? + let iconForMenuItem: Image + let foregroundColorForSelectedItem: Color + let backgroundColorForSelectedColor: Color + let foregroundColorForUnselectedItem: Color + let backgroundColorForUnselectedColor: Color + + public static var shared: SortFilterStyle { + instance + } + + public init(iconForCheckedItem: Image? = nil, iconForMenuItem: Image? = nil, foregroundColorForSelectedItem: Color? = nil, backgroundColorForSelectedColor: Color? = nil, foregroundColorForUnselectedItem: Color? = nil, backgroundColorForUnselectedColor: Color? = nil) { + self.iconForCheckedItem = iconForCheckedItem + self.iconForMenuItem = iconForMenuItem ?? Image(fioriName: "fiori.navigation.down.arrow")! + self.foregroundColorForSelectedItem = foregroundColorForSelectedItem ?? .preferredColor(.tintColor) + self.backgroundColorForSelectedColor = backgroundColorForSelectedColor ?? .preferredColor(.primaryBackground) + self.foregroundColorForUnselectedItem = foregroundColorForUnselectedItem ?? .preferredColor(.separator) + self.backgroundColorForUnselectedColor = foregroundColorForUnselectedItem ?? .preferredColor(.tertiaryFill) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift new file mode 100644 index 000000000..58abee538 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -0,0 +1,176 @@ +// +import FioriThemeManager +// _SortFilterMenuItemContainer.swift +// +// +// Created by Xu, Charles on 9/25/23. +// +import SwiftUI + +public struct _SortFilterCFGItemContainer { +// @Environment(\.onModelUpdateAppCallback) var onUpdate: () -> Void +// @Environment(\.cancelActionView) var cancelActionView: any View +// @Environment(\.resetActionView) var resetActionView: any View +// @Environment(\.applyActionView) var applyActionView: any View + @EnvironmentObject var context: SortFilterContext + + @Binding var _items: [[SortFilterItem]] + + public init(items: Binding<[[SortFilterItem]]>) { + self.__items = items + } +} + +extension _SortFilterCFGItemContainer: View { + public var body: some View { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 20) { + ForEach(0 ..< _items.count) { r in + ForEach(0 ..< _items[r].count) { c in + switch _items[r][c] { + case .picker: + VStack { + HStack { + Text(_items[r][c].picker.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + OptionListPicker( + value: Binding<[Int]>(get: { _items[r][c].picker.workingValue }, set: { _items[r][c].picker.workingValue = $0 }), + valueOptions: _items[r][c].picker.valueOptions, + onTap: { index in + _items[r][c].picker.onTap(option: _items[r][c].picker.valueOptions[index]) + } + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .filterfeedback: + VStack { + HStack { + Text(_items[r][c].filterfeedback.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + OptionListPicker( + value: Binding<[Int]>(get: { _items[r][c].filterfeedback.workingValue }, set: { _items[r][c].filterfeedback.workingValue = $0 }), + valueOptions: _items[r][c].filterfeedback.valueOptions, + onTap: { index in + _items[r][c].filterfeedback.onTap(option: _items[r][c].filterfeedback.valueOptions[index]) + } + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + + case .switch: + VStack { +// Text(_items[r][c].switch.name) + SwitchPicker(value: Binding(get: { _items[r][c].switch.workingValue }, set: { _items[r][c].switch.workingValue = $0 }), name: _items[r][c].switch.name, hint: nil) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .slider: + VStack { + HStack { + Text(_items[r][c].slider.name) + .font(.headline) + Spacer() + } + SliderPicker( + value: Binding(get: { _items[r][c].slider.workingValue }, set: { _items[r][c].slider.workingValue = $0 }), + minimumValue: _items[r][c].slider.minimumValue, + maximumValue: _items[r][c].slider.maximumValue + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .datetime: + VStack { + HStack { + Text(_items[r][c].datetime.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + DatePicker( + "Time", + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.hourAndMinute] + ) + .labelsHidden() + } + + DatePicker( + _items[r][c].datetime.label, + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + } + } + } + } + } + .onChange(of: _items) { _ in + for item in _items.joined() { + if item.isChanged { + context.isResetButtonEnabled = true + context.isApplyButtonEnabled = true + return + } + } + context.isResetButtonEnabled = true + } + .onAppear { + context.handleCancel = { + print("....cancel in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].cancel() + } + } + } + + context.handleReset = { + print("....reset in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].reset() + } + } + } + + context.handleApply = { + print("....apply in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].apply() + } + } + } + + context.isResetButtonEnabled = false + context.isApplyButtonEnabled = false + } + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift new file mode 100644 index 000000000..93941c514 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift @@ -0,0 +1,136 @@ +// +// _SortFilterMenuItemContainer.swift +// +// +// Created by Xu, Charles on 9/25/23. +// +import SwiftUI + +public struct _SortFilterMenuItemContainer { + @Environment(\.onModelUpdateAppCallback) var onUpdate: () -> Void +// @Environment(\.cancelActionView) var _cancelAction + @Environment(\.sortFilterMenuItemFullConfigurationButton) var fullCFGButton + @Binding var _items: [[SortFilterItem]] + + public init(items: Binding<[[SortFilterItem]]>) { + self.__items = items + } +} + +extension _SortFilterMenuItemContainer: View { + public var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 10) { + if fullCFGButton.positon == .leading { + FullCFGMenuItem(items: $_items, onUpdate: onUpdate) + } + ForEach(0 ..< _items.count) { r in + ForEach(0 ..< _items[r].count) { c in + if _items[r][c].isShownOnMenu { + switch _items[r][c] { + case .picker: + PickerMenuItem(item: Binding(get: { _items[r][c].picker }, set: { _items[r][c].picker = $0 }), onUpdate: onUpdate) + case .filterfeedback: + FilterFeedbackMenuItem(item: Binding(get: { _items[r][c].filterfeedback }, set: { _items[r][c].filterfeedback = $0 }), onUpdate: onUpdate) + case .switch: + SwitchMenuItem(item: Binding(get: { _items[r][c].switch }, set: { _items[r][c].switch = $0 }), onUpdate: onUpdate) + case .slider: + SliderMenuItem(item: Binding(get: { _items[r][c].slider }, set: { _items[r][c].slider = $0 }), onUpdate: onUpdate) + case .datetime: + DateTimeMenuItem(item: Binding(get: { _items[r][c].datetime }, set: { _items[r][c].datetime = $0 }), onUpdate: onUpdate) + } + } + } + } + if fullCFGButton.positon == .trailing { + FullCFGMenuItem(items: $_items, onUpdate: onUpdate) + } + } + } + .frame(minHeight: 44) + .padding(.leading, 5) + } +} + +public struct SortFilterMenuItemFullConfigurationButtonKey: EnvironmentKey { + public static var defaultValue: SortFilterMenuItemFullConfigurationButton = .none +} + +public struct SortFilterMenuItemFullConfigurationButton { + public let name: String? + public let icon: String? + public let positon: Position + + public enum Position { + case leading, trailing, none + } + + private init(name: String? = nil, icon: String? = nil, positon: Position) { + self.name = name + self.icon = icon + self.positon = positon + } + + public static func leading(name: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, positon: .leading) + } + + public static func leading(icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(icon: icon, positon: .leading) + } + + public static func leading(name: String, icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, icon: icon, positon: .leading) + } + + public static func trailing(name: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, positon: .trailing) + } + + public static func trailing(icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(icon: icon, positon: .trailing) + } + + public static func trailing(name: String, icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, icon: icon, positon: .trailing) + } + + static var none = SortFilterMenuItemFullConfigurationButton(positon: Position.none) +} + +public extension EnvironmentValues { + var sortFilterMenuItemFullConfigurationButton: SortFilterMenuItemFullConfigurationButton { + get { + self[SortFilterMenuItemFullConfigurationButtonKey.self] + } + set { + self[SortFilterMenuItemFullConfigurationButtonKey.self] = newValue + } + } +} + +public extension View { + func leadingFullConfigurationMenuItem(name: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name)) + } + + func leadingFullConfigurationMenuItem(icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(icon: icon)) + } + + func leadingFullConfigurationMenuItem(name: String, icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name, icon: icon)) + } + + func trailingFullConfigurationMenuItem(name: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(name: name)) + } + + func trailingFullConfigurationMenuItem(icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(icon: icon)) + } + + func trailingFullConfigurationMenuItem(name: String, icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(name: name, icon: icon)) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift new file mode 100644 index 000000000..1993ca0df --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift @@ -0,0 +1,153 @@ +import FioriThemeManager +import SwiftUI + +extension SwitchPicker: View { + public var body: some View { + AnyView( + Toggle(_name ?? "", isOn: .convert(from: _value, ifNilUse: false)) + .toggleStyle(fioriToggleStyle) + ).typeErased + } +} + +private extension Binding { + static func convert(from value: Binding, ifNilUse defaultValue: Bool) -> Binding { + Binding( + get: { + value.wrappedValue ?? defaultValue + }, + set: { + value.wrappedValue = $0 + } + ) + } +} + +public struct FioriToggleStyle: ToggleStyle { + @ScaledMetric var scale: CGFloat = 1 + + let labelColor: Color + + let onColor: Color + let offColor: Color + + let onThumbColor: Color + let offThumbColor: Color + + let onBorderColor: Color + let offBorderColor: Color + + public init( + labelColor: Color = Color.preferredColor(.primaryLabel), + onColor: Color = Color.preferredColor(.tintColor), + offColor: Color = Color.preferredColor(.grey3), + onThumbColor: Color = Color.preferredColor(.baseWhite), + offThumbColor: Color = Color.preferredColor(.baseWhite), + onBorderColor: Color = Color.preferredColor(.separator), + offBorderColor: Color = Color.preferredColor(.separator) + ) { + self.labelColor = labelColor + + self.onColor = onColor + self.offColor = offColor + + self.onThumbColor = onThumbColor + self.offThumbColor = offColor + + self.onBorderColor = onBorderColor + self.offBorderColor = offBorderColor + } + + public func makeBody(configuration: Self.Configuration) -> some View { + HStack { + configuration.label + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(labelColor) + + Spacer() + ZStack { + RoundedRectangle(cornerRadius: 16 * scale, style: .circular) + .stroke(configuration.isOn ? onBorderColor : offBorderColor, lineWidth: 0.5 * scale) + .frame(width: 51 * scale, height: 30 * scale) + + RoundedRectangle(cornerRadius: 16 * scale, style: .circular) + .fill(configuration.isOn ? onColor : offColor) + .frame(width: 51 * scale, height: 30 * scale) + .overlay( + Circle() + .fill(configuration.isOn ? onThumbColor : offThumbColor) + .shadow(radius: 1 * scale, x: 0, y: 1 * scale) + .padding(1.5 * scale) + .offset(x: configuration.isOn ? 10 * scale : -10 * scale)) + .animation(Animation.easeInOut(duration: 0.2), value: configuration.isOn) + .frame(minHeight: 44) + .onTapGesture { configuration.isOn.toggle() } + } + } + .padding(.horizontal) + } +} + +// public struct DefaultToggleStyle: ToggleStyle { +// public func makeBody(configuration: Configuration) -> some View { +// VStack { +// Toggle(configuration) +// .labelsHidden() +// .foregroundColor(Color.tintColor) +// configuration.label +// .font(.system(size: 22, weight: .semibold)).lineLimit(2) +// .padding() +// .overlay( +// RoundedRectangle(cornerRadius: 10) +// .stroke(configuration.isOn ? Color.green: Color.gray, lineWidth: 1) +// ) +// } +// } +// } + +public struct FioriToggleStyleKey: EnvironmentKey { + public static var defaultValue: any ToggleStyle = FioriToggleStyle() +} + +public extension EnvironmentValues { + var fioriToggleStyle: any ToggleStyle { + get { + self[FioriToggleStyleKey.self] + } + set { + self[FioriToggleStyleKey.self] = newValue + } + } +} + +/* + // FIXME: - Implement SwitchPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SwitchPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SwitchPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private struct TestSwitchPicker: View { + @State var v1: Bool? = true + @State var v2: Bool? = false + @State var v3: Bool? = nil + + var body: some View { + VStack { + SwitchPicker(value: $v1, hint: nil) + SwitchPicker(value: $v2, hint: nil) + SwitchPicker(value: $v3, hint: nil) + } + } +} + +#Preview { + TestSwitchPicker() + .frame(width: 375) +} diff --git a/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift index 9ffbfc4de..84685e2f2 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -158,6 +158,14 @@ public protocol FootnoteIconsComponent { var footnoteIcons: [TextOrIcon]? { get } } +public protocol LeftIconComponent { + var leftIcon: Image? { get } +} + +public protocol RightIconComponent { + var rightIcon: Image? { get } +} + public protocol ActionComponent { var actionText: String? { get } @@ -194,10 +202,48 @@ public protocol KpiProgressComponent : KpiComponent { var fraction: Double? { get } } +public protocol OptionListPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: [Int] { get set } + // sourcery: no_view + var valueOptions: [String] { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + public protocol ProgressIndicatorComponent { var progressIndicatorText: String? { get } } +public protocol SliderPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Int? { get set } + // sourcery: default.value=0 + // sourcery: no_view + var minimumValue: Int { get } + // sourcery: default.value=100 + // sourcery: no_view + var maximumValue: Int { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + +public protocol SwitchPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Bool? { get set } + // sourcery: default.value=nil + // sourcery: no_view + var name: String? { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + public protocol TextInputComponent : AnyObject { // sourcery: bindingPropertyOptional=.constant("") var textInputValue: String { get set } diff --git a/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift index 97eae186c..0a8948612 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -142,12 +142,24 @@ public extension KpiProgressComponent { } } +public extension LeftIconComponent { + var leftIcon: Image? { + return nil + } +} + public extension LowerBoundTitleComponent { var lowerBoundTitle: String? { return nil } } +public extension OptionListPickerComponent { + var hint: String? { + return nil + } +} + public extension PlaceholderComponent { var placeholder: String? { return nil @@ -160,6 +172,12 @@ public extension ProgressIndicatorComponent { } } +public extension RightIconComponent { + var rightIcon: Image? { + return nil + } +} + public extension SecondActionTitleComponent { var secondActionTitle: String? { return nil @@ -184,6 +202,24 @@ public extension SecondaryValuesAxisTitleComponent { } } +public extension SliderPickerComponent { + var minimumValue: Int { + return 0 + } + + var maximumValue: Int { + return 100 + } + + var hint: String? { + return nil + } + + var value: Int? { + return nil + } +} + public extension StatusComponent { var status: TextOrIcon? { return nil @@ -202,6 +238,20 @@ public extension SubtitleComponent { } } +public extension SwitchPickerComponent { + var name: String? { + return nil + } + + var hint: String? { + return nil + } + + var value: Bool? { + return nil + } +} + public extension TagsComponent { var tags: [String]? { return nil diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift index 43459f573..98937a7c0 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -146,19 +146,23 @@ struct FootnoteIconsModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct ActionTextModifierKey: EnvironmentKey { +struct LeftIconModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct ActionItemsModifierKey: EnvironmentKey { +struct RightIconModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct ProgressIndicatorTextModifierKey: EnvironmentKey { +struct ActionTextModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct NodeModifierKey: EnvironmentKey { +struct ActionItemsModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ProgressIndicatorTextModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } @@ -206,6 +210,22 @@ struct SaveActionModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } +struct NodeModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ItemsModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ResetActionModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ApplyActionModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + struct NextActionModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift index 016d1dda8..98e973869 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -184,6 +184,16 @@ extension EnvironmentValues { set { self[FootnoteIconsModifierKey.self] = newValue } } + public var leftIconModifier: AnyViewModifier { + get { return self[LeftIconModifierKey.self] } + set { self[LeftIconModifierKey.self] = newValue } + } + + public var rightIconModifier: AnyViewModifier { + get { return self[RightIconModifierKey.self] } + set { self[RightIconModifierKey.self] = newValue } + } + public var actionTextModifier: AnyViewModifier { get { return self[ActionTextModifierKey.self] } set { self[ActionTextModifierKey.self] = newValue } @@ -199,11 +209,6 @@ extension EnvironmentValues { set { self[ProgressIndicatorTextModifierKey.self] = newValue } } - public var nodeModifier: AnyViewModifier { - get { return self[NodeModifierKey.self] } - set { self[NodeModifierKey.self] = newValue } - } - public var textInputValueModifier: AnyViewModifier { get { return self[TextInputValueModifierKey.self] } set { self[TextInputValueModifierKey.self] = newValue } @@ -259,6 +264,26 @@ extension EnvironmentValues { set { self[SaveActionModifierKey.self] = newValue } } + public var nodeModifier: AnyViewModifier { + get { return self[NodeModifierKey.self] } + set { self[NodeModifierKey.self] = newValue } + } + + public var itemsModifier: AnyViewModifier { + get { return self[ItemsModifierKey.self] } + set { self[ItemsModifierKey.self] = newValue } + } + + public var resetActionModifier: AnyViewModifier { + get { return self[ResetActionModifierKey.self] } + set { self[ResetActionModifierKey.self] = newValue } + } + + public var applyActionModifier: AnyViewModifier { + get { return self[ApplyActionModifierKey.self] } + set { self[ApplyActionModifierKey.self] = newValue } + } + public var nextActionModifier: AnyViewModifier { get { return self[NextActionModifierKey.self] } set { self[NextActionModifierKey.self] = newValue } @@ -463,6 +488,16 @@ public extension View { self.environment(\.footnoteIconsModifier, AnyViewModifier(transform)) } + @ViewBuilder + func leftIconModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.leftIconModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func rightIconModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.rightIconModifier, AnyViewModifier(transform)) + } + @ViewBuilder func actionTextModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.actionTextModifier, AnyViewModifier(transform)) @@ -478,11 +513,6 @@ public extension View { self.environment(\.progressIndicatorTextModifier, AnyViewModifier(transform)) } - @ViewBuilder - func nodeModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { - self.environment(\.nodeModifier, AnyViewModifier(transform)) - } - @ViewBuilder func textInputValueModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.textInputValueModifier, AnyViewModifier(transform)) @@ -538,6 +568,26 @@ public extension View { self.environment(\.saveActionModifier, AnyViewModifier(transform)) } + @ViewBuilder + func nodeModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.nodeModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func itemsModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.itemsModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func resetActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.resetActionModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func applyActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.applyActionModifier, AnyViewModifier(transform)) + } + @ViewBuilder func nextActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.nextActionModifier, AnyViewModifier(transform)) diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift new file mode 100644 index 000000000..b7e8c3e77 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct OptionChip { + @Environment(\.leftIconModifier) private var leftIconModifier + @Environment(\.titleModifier) private var titleModifier + @Environment(\.optionChipStyle) var optionChipStyle + + let _leftIcon: LeftIcon + let _title: Title + let _isSelected: Bool + + + private var isModelInit: Bool = false + private var isLeftIconNil: Bool = false + + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self._leftIcon = leftIcon() + self._title = title() + self._isSelected = isSelected + } + + @ViewBuilder var leftIcon: some View { + if isModelInit { + _leftIcon.modifier(leftIconModifier.concat(Fiori.OptionChip.leftIcon).concat(Fiori.OptionChip.leftIconCumulative)) + } else { + _leftIcon.modifier(leftIconModifier.concat(Fiori.OptionChip.leftIcon)) + } + } + @ViewBuilder var title: some View { + if isModelInit { + _title.modifier(titleModifier.concat(Fiori.OptionChip.title).concat(Fiori.OptionChip.titleCumulative)) + } else { + _title.modifier(titleModifier.concat(Fiori.OptionChip.title)) + } + } + + var isLeftIconEmptyView: Bool { + ((isModelInit && isLeftIconNil) || LeftIcon.self == EmptyView.self) ? true : false + } +} + +extension OptionChip where LeftIcon == _ConditionalContent, + Title == Text { + + public init(model: OptionChipModel) { + self.init(leftIcon: model.leftIcon, title: model.title, isSelected: model.isSelected) + } + + public init(leftIcon: Image? = nil, title: String, isSelected: Bool) { + self._leftIcon = leftIcon != nil ? ViewBuilder.buildEither(first: leftIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._title = Text(title) + self._isSelected = isSelected + + isModelInit = true + isLeftIconNil = leftIcon == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift new file mode 100644 index 000000000..ef062bf3f --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift @@ -0,0 +1,23 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct OptionListPicker { + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding<[Int]> + var _valueOptions: [String] + var _hint: String? = nil + var _onTap: ((_ index: Int) -> Void)? = nil + + public init(model: OptionListPickerModel) { + self.init(value: Binding<[Int]>(get: { model.value }, set: { model.value = $0 }), valueOptions: model.valueOptions, hint: model.hint, onTap: model.onTap) + } + + public init(value: Binding<[Int]>, valueOptions: [String] = [], hint: String? = nil, onTap: ((_ index: Int) -> Void)? = nil) { + self._value = value + self._valueOptions = valueOptions + self._hint = hint + self._onTap = onTap + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift new file mode 100644 index 000000000..753adc175 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift @@ -0,0 +1,23 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SliderPicker { + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding + var _minimumValue: Int + var _maximumValue: Int + var _hint: String? = nil + + public init(model: SliderPickerModel) { + self.init(value: Binding(get: { model.value }, set: { model.value = $0 }), minimumValue: model.minimumValue, maximumValue: model.maximumValue, hint: model.hint) + } + + public init(value: Binding, minimumValue: Int = 0, maximumValue: Int = 100, hint: String? = nil) { + self._value = value + self._minimumValue = minimumValue + self._maximumValue = maximumValue + self._hint = hint + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift new file mode 100644 index 000000000..03419e968 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift @@ -0,0 +1,103 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterFullCFG { + @Environment(\.itemsModifier) private var itemsModifier + @Environment(\.cancelActionModifier) private var cancelActionModifier + @Environment(\.resetActionModifier) private var resetActionModifier + @Environment(\.applyActionModifier) private var applyActionModifier + @Environment(\.dismiss) var dismiss + + var _items: Items + let _cancelAction: CancelActionView + let _resetAction: ResetActionView + let _applyAction: ApplyActionView + let _onUpdate: (() -> Void)? + @State var context: SortFilterContext = SortFilterContext() + + private var isModelInit: Bool = false + private var isCancelActionNil: Bool = false + private var isResetActionNil: Bool = false + private var isApplyActionNil: Bool = false + private var isOnUpdateNil: Bool = false + + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder resetAction: () -> ResetActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self._items = items() + self._cancelAction = cancelAction() + self._resetAction = resetAction() + self._applyAction = applyAction() + self._onUpdate = onUpdate + } + + @ViewBuilder var items: some View { + if isModelInit { + _items.modifier(itemsModifier.concat(Fiori.SortFilterFullCFG.items).concat(Fiori.SortFilterFullCFG.itemsCumulative)) + } else { + _items.modifier(itemsModifier.concat(Fiori.SortFilterFullCFG.items)) + } + } + @ViewBuilder var cancelAction: some View { + if isModelInit { + _cancelAction.modifier(cancelActionModifier.concat(Fiori.SortFilterFullCFG.cancelAction).concat(Fiori.SortFilterFullCFG.cancelActionCumulative)) + } else { + _cancelAction.modifier(cancelActionModifier.concat(Fiori.SortFilterFullCFG.cancelAction)) + } + } + @ViewBuilder var resetAction: some View { + if isModelInit { + _resetAction.modifier(resetActionModifier.concat(Fiori.SortFilterFullCFG.resetAction).concat(Fiori.SortFilterFullCFG.resetActionCumulative)) + } else { + _resetAction.modifier(resetActionModifier.concat(Fiori.SortFilterFullCFG.resetAction)) + } + } + @ViewBuilder var applyAction: some View { + if isModelInit { + _applyAction.modifier(applyActionModifier.concat(Fiori.SortFilterFullCFG.applyAction).concat(Fiori.SortFilterFullCFG.applyActionCumulative)) + } else { + _applyAction.modifier(applyActionModifier.concat(Fiori.SortFilterFullCFG.applyAction)) + } + } + + var isCancelActionEmptyView: Bool { + ((isModelInit && isCancelActionNil) || CancelActionView.self == EmptyView.self) ? true : false + } + + var isResetActionEmptyView: Bool { + ((isModelInit && isResetActionNil) || ResetActionView.self == EmptyView.self) ? true : false + } + + var isApplyActionEmptyView: Bool { + ((isModelInit && isApplyActionNil) || ApplyActionView.self == EmptyView.self) ? true : false + } +} + +extension SortFilterFullCFG where Items == _SortFilterCFGItemContainer, + CancelActionView == _ConditionalContent, + ResetActionView == _ConditionalContent, + ApplyActionView == _ConditionalContent { + + public init(model: SortFilterFullCFGModel) { + self.init(items: Binding<[[SortFilterItem]]>(get: { model.items }, set: { model.items = $0 }), cancelAction: model.cancelAction != nil ? Action(model: model.cancelAction!) : nil, resetAction: model.resetAction != nil ? Action(model: model.resetAction!) : nil, applyAction: model.applyAction != nil ? Action(model: model.applyAction!) : nil, onUpdate: model.onUpdate) + } + + public init(items: Binding<[[SortFilterItem]]>, cancelAction: Action? = Action(model: _CancelActionDefault()), resetAction: Action? = Action(model: _ResetActionDefault()), applyAction: Action? = Action(model: _ApplyActionDefault()), onUpdate: (() -> Void)? = nil) { + self._items = _SortFilterCFGItemContainer(items: items) + self._cancelAction = cancelAction != nil ? ViewBuilder.buildEither(first: cancelAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._resetAction = resetAction != nil ? ViewBuilder.buildEither(first: resetAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._applyAction = applyAction != nil ? ViewBuilder.buildEither(first: applyAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._onUpdate = onUpdate + + isModelInit = true + isCancelActionNil = cancelAction == nil ? true : false + isResetActionNil = resetAction == nil ? true : false + isApplyActionNil = applyAction == nil ? true : false + isOnUpdateNil = onUpdate == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift new file mode 100644 index 000000000..50dba7c16 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift @@ -0,0 +1,102 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterMenu { + @Environment(\.itemsModifier) private var itemsModifier + @Environment(\.cancelActionModifier) private var cancelActionModifier + @Environment(\.resetActionModifier) private var resetActionModifier + @Environment(\.applyActionModifier) private var applyActionModifier + + var _items: Items + let _cancelAction: CancelActionView + let _resetAction: ResetActionView + let _applyAction: ApplyActionView + var _onUpdate: (() -> Void)? + + + private var isModelInit: Bool = false + private var isCancelActionNil: Bool = false + private var isResetActionNil: Bool = false + private var isApplyActionNil: Bool = false + private var isOnUpdateNil: Bool = false + + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder resetAction: () -> ResetActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self._items = items() + self._cancelAction = cancelAction() + self._resetAction = resetAction() + self._applyAction = applyAction() + self._onUpdate = onUpdate + } + + @ViewBuilder var items: some View { + if isModelInit { + _items.modifier(itemsModifier.concat(Fiori.SortFilterMenu.items).concat(Fiori.SortFilterMenu.itemsCumulative)) + } else { + _items.modifier(itemsModifier.concat(Fiori.SortFilterMenu.items)) + } + } + @ViewBuilder var cancelAction: some View { + if isModelInit { + _cancelAction.modifier(cancelActionModifier.concat(Fiori.SortFilterMenu.cancelAction).concat(Fiori.SortFilterMenu.cancelActionCumulative)) + } else { + _cancelAction.modifier(cancelActionModifier.concat(Fiori.SortFilterMenu.cancelAction)) + } + } + @ViewBuilder var resetAction: some View { + if isModelInit { + _resetAction.modifier(resetActionModifier.concat(Fiori.SortFilterMenu.resetAction).concat(Fiori.SortFilterMenu.resetActionCumulative)) + } else { + _resetAction.modifier(resetActionModifier.concat(Fiori.SortFilterMenu.resetAction)) + } + } + @ViewBuilder var applyAction: some View { + if isModelInit { + _applyAction.modifier(applyActionModifier.concat(Fiori.SortFilterMenu.applyAction).concat(Fiori.SortFilterMenu.applyActionCumulative)) + } else { + _applyAction.modifier(applyActionModifier.concat(Fiori.SortFilterMenu.applyAction)) + } + } + + var isCancelActionEmptyView: Bool { + ((isModelInit && isCancelActionNil) || CancelActionView.self == EmptyView.self) ? true : false + } + + var isResetActionEmptyView: Bool { + ((isModelInit && isResetActionNil) || ResetActionView.self == EmptyView.self) ? true : false + } + + var isApplyActionEmptyView: Bool { + ((isModelInit && isApplyActionNil) || ApplyActionView.self == EmptyView.self) ? true : false + } +} + +extension SortFilterMenu where Items == _SortFilterMenuItemContainer, + CancelActionView == _ConditionalContent, + ResetActionView == _ConditionalContent, + ApplyActionView == _ConditionalContent { + + public init(model: SortFilterMenuModel) { + self.init(items: Binding<[[SortFilterItem]]>(get: { model.items }, set: { model.items = $0 }), cancelAction: model.cancelAction != nil ? Action(model: model.cancelAction!) : nil, resetAction: model.resetAction != nil ? Action(model: model.resetAction!) : nil, applyAction: model.applyAction != nil ? Action(model: model.applyAction!) : nil, onUpdate: model.onUpdate) + } + + public init(items: Binding<[[SortFilterItem]]>, cancelAction: Action? = Action(model: _CancelActionDefault()), resetAction: Action? = Action(model: _ResetActionDefault()), applyAction: Action? = Action(model: _ApplyActionDefault()), onUpdate: (() -> Void)? = nil) { + self._items = _SortFilterMenuItemContainer(items: items) + self._cancelAction = cancelAction != nil ? ViewBuilder.buildEither(first: cancelAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._resetAction = resetAction != nil ? ViewBuilder.buildEither(first: resetAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._applyAction = applyAction != nil ? ViewBuilder.buildEither(first: applyAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._onUpdate = onUpdate + + isModelInit = true + isCancelActionNil = cancelAction == nil ? true : false + isResetActionNil = resetAction == nil ? true : false + isApplyActionNil = applyAction == nil ? true : false + isOnUpdateNil = onUpdate == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift new file mode 100644 index 000000000..944c78803 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift @@ -0,0 +1,82 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterMenuItem { + @Environment(\.leftIconModifier) private var leftIconModifier + @Environment(\.titleModifier) private var titleModifier + @Environment(\.rightIconModifier) private var rightIconModifier + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + let _leftIcon: LeftIcon + let _title: Title + let _rightIcon: RightIcon + let _isSelected: Bool + + + private var isModelInit: Bool = false + private var isLeftIconNil: Bool = false + private var isRightIconNil: Bool = false + + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + @ViewBuilder rightIcon: () -> RightIcon, + isSelected: Bool + ) { + self._leftIcon = leftIcon() + self._title = title() + self._rightIcon = rightIcon() + self._isSelected = isSelected + } + + @ViewBuilder var leftIcon: some View { + if isModelInit { + _leftIcon.modifier(leftIconModifier.concat(Fiori.SortFilterMenuItem.leftIcon).concat(Fiori.SortFilterMenuItem.leftIconCumulative)) + } else { + _leftIcon.modifier(leftIconModifier.concat(Fiori.SortFilterMenuItem.leftIcon)) + } + } + @ViewBuilder var title: some View { + if isModelInit { + _title.modifier(titleModifier.concat(Fiori.SortFilterMenuItem.title).concat(Fiori.SortFilterMenuItem.titleCumulative)) + } else { + _title.modifier(titleModifier.concat(Fiori.SortFilterMenuItem.title)) + } + } + @ViewBuilder var rightIcon: some View { + if isModelInit { + _rightIcon.modifier(rightIconModifier.concat(Fiori.SortFilterMenuItem.rightIcon).concat(Fiori.SortFilterMenuItem.rightIconCumulative)) + } else { + _rightIcon.modifier(rightIconModifier.concat(Fiori.SortFilterMenuItem.rightIcon)) + } + } + + var isLeftIconEmptyView: Bool { + ((isModelInit && isLeftIconNil) || LeftIcon.self == EmptyView.self) ? true : false + } + + var isRightIconEmptyView: Bool { + ((isModelInit && isRightIconNil) || RightIcon.self == EmptyView.self) ? true : false + } +} + +extension SortFilterMenuItem where LeftIcon == _ConditionalContent, + Title == Text, + RightIcon == _ConditionalContent { + + public init(model: SortFilterMenuItemModel) { + self.init(leftIcon: model.leftIcon, title: model.title, rightIcon: model.rightIcon, isSelected: model.isSelected) + } + + public init(leftIcon: Image? = nil, title: String, rightIcon: Image? = nil, isSelected: Bool) { + self._leftIcon = leftIcon != nil ? ViewBuilder.buildEither(first: leftIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._title = Text(title) + self._rightIcon = rightIcon != nil ? ViewBuilder.buildEither(first: rightIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._isSelected = isSelected + + isModelInit = true + isLeftIconNil = leftIcon == nil ? true : false + isRightIconNil = rightIcon == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift new file mode 100644 index 000000000..916d3cd29 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift @@ -0,0 +1,22 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SwitchPicker { + @Environment(\.fioriToggleStyle) var fioriToggleStyle + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding + var _name: String? = nil + var _hint: String? = nil + + public init(model: SwitchPickerModel) { + self.init(value: Binding(get: { model.value }, set: { model.value = $0 }), name: model.name, hint: model.hint) + } + + public init(value: Binding, name: String? = nil, hint: String? = nil) { + self._value = value + self._name = name + self._hint = hint + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift new file mode 100644 index 000000000..237897746 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift @@ -0,0 +1,62 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/OptionChip+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement OptionChip `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum OptionChip { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + } +} + +// FIXME: - Implement OptionChip View body + +extension OptionChip: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement OptionChip specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct OptionChipLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionChip(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift new file mode 100644 index 000000000..37e1d46ef --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/OptionListPicker+View.swift` +//TODO: Implement OptionListPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement OptionListPicker View body + +extension OptionListPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement OptionListPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct OptionListPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionListPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift new file mode 100644 index 000000000..2e6706c9b --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SliderPicker+View.swift` +//TODO: Implement SliderPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement SliderPicker View body + +extension SliderPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SliderPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SliderPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SliderPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift new file mode 100644 index 000000000..a8f99ee43 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift @@ -0,0 +1,70 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterFullCFG+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterFullCFG `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterFullCFG { + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let items = Items() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let itemsCumulative = ItemsCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } +} + +// FIXME: - Implement SortFilterFullCFG View body + +extension SortFilterFullCFG: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterFullCFG specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterFullCFGLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterFullCFG(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift new file mode 100644 index 000000000..7ac89b854 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift @@ -0,0 +1,70 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterMenu+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterMenu `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterMenu { + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let items = Items() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let itemsCumulative = ItemsCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } +} + +// FIXME: - Implement SortFilterMenu View body + +extension SortFilterMenu: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterMenu specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterMenuLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenu(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift new file mode 100644 index 000000000..7e85ea3f1 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift @@ -0,0 +1,66 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterMenuItem+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterMenuItem `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterMenuItem { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + typealias RightIcon = EmptyModifier + typealias RightIconCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let rightIcon = RightIcon() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + static let rightIconCumulative = RightIconCumulative() + } +} + +// FIXME: - Implement SortFilterMenuItem View body + +extension SortFilterMenuItem: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterMenuItem specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterMenuItemLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenuItem(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift new file mode 100644 index 000000000..4ac15b331 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SwitchPicker+View.swift` +//TODO: Implement SwitchPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement SwitchPicker View body + +extension SwitchPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SwitchPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SwitchPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SwitchPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift new file mode 100644 index 000000000..b50093bb2 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift @@ -0,0 +1,16 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension OptionChip where LeftIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + isSelected: isSelected + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift new file mode 100644 index 000000000..e62ecfece --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift @@ -0,0 +1,117 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension SortFilterFullCFG where CancelActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder resetAction: () -> ResetActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: resetAction, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ResetActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: cancelAction, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ApplyActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder resetAction: () -> ResetActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: cancelAction, + resetAction: resetAction, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder resetAction: () -> ResetActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: resetAction, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ResetActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: cancelAction, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder items: () -> Items, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift new file mode 100644 index 000000000..7aaf37540 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift @@ -0,0 +1,117 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension SortFilterMenu where CancelActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder resetAction: () -> ResetActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: resetAction, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterMenu where ResetActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: cancelAction, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterMenu where ApplyActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder resetAction: () -> ResetActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: cancelAction, + resetAction: resetAction, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterMenu where CancelActionView == Action, ResetActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterMenu where CancelActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder resetAction: () -> ResetActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: resetAction, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterMenu where ResetActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: cancelAction, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterMenu where CancelActionView == Action, ResetActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder items: () -> Items, + onUpdate: (() -> Void)? = nil + ) { + self.init( + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift new file mode 100644 index 000000000..1f3fcd0e5 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift @@ -0,0 +1,47 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension SortFilterMenuItem where LeftIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder rightIcon: () -> RightIcon, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + rightIcon: rightIcon, + isSelected: isSelected + ) + } +} + +extension SortFilterMenuItem where RightIcon == EmptyView { + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: leftIcon, + title: title, + rightIcon: { EmptyView() }, + isSelected: isSelected + ) + } +} + +extension SortFilterMenuItem where LeftIcon == EmptyView, RightIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + rightIcon: { EmptyView() }, + isSelected: isSelected + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift new file mode 100644 index 000000000..bd22eec19 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift @@ -0,0 +1,9 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension OptionListPickerModel { + var onTap: ((_ index: Int) -> Void)? { + return nil + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift new file mode 100644 index 000000000..2a7904961 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift @@ -0,0 +1,21 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension SortFilterFullCFGModel { + var cancelAction: ActionModel? { + return _CancelActionDefault() + } + + var resetAction: ActionModel? { + return _ResetActionDefault() + } + + var applyAction: ActionModel? { + return _ApplyActionDefault() + } + + var onUpdate: (() -> Void)? { + return nil + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift new file mode 100644 index 000000000..d9054e7d8 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift @@ -0,0 +1,21 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension SortFilterMenuModel { + var cancelAction: ActionModel? { + return _CancelActionDefault() + } + + var resetAction: ActionModel? { + return _ResetActionDefault() + } + + var applyAction: ActionModel? { + return _ApplyActionDefault() + } + + var onUpdate: (() -> Void)? { + return nil + } +} From 84cf5501e42702dfdb6f54ef09a1562341ae1f51 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Thu, 19 Oct 2023 14:22:06 -0700 Subject: [PATCH 02/22] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20jira=202286=20sort?= =?UTF-8?q?=20&=20filter=20for=20SwiftUI=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A SwiftUI component for configuration criteria of performing sorting and filter. SortFilterMenu and SortFilterFullCFG are provided. ✅ Closes: 1 --- .../Examples.xcodeproj/project.pbxproj | 30 +- .../FioriSwiftUICore/CoreContentView.swift | 4 + .../SortFilter/SortFilterExample.swift | 101 ++++ .../SortFilter/View+Extensions.swift | 35 ++ .../iconfromapp.imageset/Contents.json | 21 + .../iconfromapp.imageset/icon.png | Bin 0 -> 934 bytes Package.swift | 2 +- .../CancellableResettableForm.swift | 92 ++++ .../Components/MultiPropertyComponents.swift | 49 ++ .../Components/SinglePropertyComponents.swift | 2 + .../DataTypes/SoftFilter+DataType.swift | 521 ++++++++++++++++++ .../Models/DefaultViewModels.swift | 16 + .../Models/ModelDefinitions.swift | 68 +++ .../Views/OptionChip+View.swift | 182 ++++++ .../Views/OptionListPicker+View.swift | 61 ++ .../Views/SliderPicker+View.swift | 191 +++++++ .../SortFilter/SortFilter+Environment.swift | 77 +++ .../Views/SortFilter/SortFilterContext.swift | 14 + .../SortFilter/SortFilterDialog+View.swift | 63 +++ .../SortFilter/SortFilterFullCFG+View.swift | 102 ++++ .../SortFilter/SortFilterItemTitle.swift | 29 + .../SortFilter/SortFilterMenu+View.swift | 30 + .../SortFilter/SortFilterMenuItem+Style.swift | 102 ++++ .../SortFilter/SortFilterMenuItem+View.swift | 464 ++++++++++++++++ .../Views/SortFilter/SortFilterStyle.swift | 26 + .../_SortFilterCFGItemContainer.swift | 173 ++++++ .../_SortFilterMenuItemContainer.swift | 136 +++++ .../Views/SwitchPicker+View.swift | 158 ++++++ .../Component+Protocols.generated.swift | 51 +- ...mponentProtocols+Extension.generated.swift | 56 +- .../EnvironmentKey+Styles.generated.swift | 30 +- .../EnvironmentValue+Styles.generated.swift | 72 ++- .../API/OptionChip+API.generated.swift | 63 +++ .../API/OptionListPicker+API.generated.swift | 23 + .../API/SliderPicker+API.generated.swift | 25 + .../API/SortFilterFullCFG+API.generated.swift | 116 ++++ .../API/SortFilterMenu+API.generated.swift | 47 ++ .../SortFilterMenuItem+API.generated.swift | 82 +++ .../API/SwitchPicker+API.generated.swift | 22 + .../OptionChip+View.generated.swift | 62 +++ .../OptionListPicker+View.generated.swift | 34 ++ .../SliderPicker+View.generated.swift | 34 ++ .../SortFilterFullCFG+View.generated.swift | 74 +++ .../SortFilterMenu+View.generated.swift | 58 ++ .../SortFilterMenuItem+View.generated.swift | 66 +++ .../SwitchPicker+View.generated.swift | 34 ++ .../OptionChip+Init.generated.swift | 16 + .../OptionListPicker+Init.generated.swift | 3 + .../SliderPicker+Init.generated.swift | 3 + .../SortFilterFullCFG+Init.generated.swift | 131 +++++ .../SortFilterMenu+Init.generated.swift | 3 + .../SortFilterMenuItem+Init.generated.swift | 47 ++ .../SwitchPicker+Init.generated.swift | 3 + ...ListPickerModel+Extensions.generated.swift | 9 + ...terFullCFGModel+Extensions.generated.swift | 21 + ...FilterMenuModel+Extensions.generated.swift | 9 + .../en.lproj/FioriSwiftUICore.strings | 3 + 57 files changed, 3920 insertions(+), 26 deletions(-) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift create mode 100644 Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json create mode 100644 Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png create mode 100644 Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift create mode 100644 Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift create mode 100644 Sources/FioriSwiftUICore/Views/OptionChip+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SliderPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift create mode 100644 Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 7f18def4a..64f224ac3 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -125,6 +125,8 @@ B8D4376F25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */; }; B8D4377125F983730024EE7D /* ObjectCell_Rules_Alignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */; }; B8D437732609479E0024EE7D /* SingleActionFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */; }; + C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A0FDB22AD893FA0001738E /* View+Extensions.swift */; }; + C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -307,6 +309,8 @@ B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Spec_Jan2018.swift; sourceTree = ""; }; B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Rules_Alignment.swift; sourceTree = ""; }; B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleActionFollowButton.swift; sourceTree = ""; }; + C1A0FDB22AD893FA0001738E /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; + C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortFilterExample.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -501,6 +505,7 @@ 8A5579C824C1293C0098003A /* FioriSwiftUICore */ = { isa = PBXGroup; children = ( + C1C764862A818BD600BCB0F7 /* SortFilter */, B100639129C0623300AF0CA2 /* StepProgressIndicator */, 108E43D3292DAB3E006532F3 /* EmptyStateView */, B1D41B1E291A2D2E004E64A5 /* Picker */, @@ -700,6 +705,15 @@ path = ObjectItem; sourceTree = ""; }; + C1C764862A818BD600BCB0F7 /* SortFilter */ = { + isa = PBXGroup; + children = ( + C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */, + C1A0FDB22AD893FA0001738E /* View+Extensions.swift */, + ); + path = SortFilter; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -889,6 +903,7 @@ 8A557A2424C12F380098003A /* ChartDetailView.swift in Sources */, 8A5579D024C1293C0098003A /* SettingsLine.swift in Sources */, 1FC30412270540FB004BEE00 /* 72-Fonts.swift in Sources */, + C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */, B84D24ED2652F343007F2373 /* HeaderChartExample.swift in Sources */, B100639329C0624D00AF0CA2 /* StepProgressIndicatorExample.swift in Sources */, B846F94626815CC90085044B /* ContactItemExample.swift in Sources */, @@ -937,6 +952,7 @@ 8A5579D524C1293C0098003A /* SettingsSeries.swift in Sources */, 8A557A2224C12C9B0098003A /* CoreContentView.swift in Sources */, 8A5579D224C1293C0098003A /* Color+Extensions.swift in Sources */, + C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */, B84D24EF2652F343007F2373 /* ObjectHeaderTestApp.swift in Sources */, B84D24EC2652F343007F2373 /* ObjectHeaderSpecCompact.swift in Sources */, 8A5579CD24C1293C0098003A /* SettingsLabel.swift in Sources */, @@ -1116,7 +1132,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1171,7 +1187,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -1191,7 +1207,7 @@ DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Examples/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1213,7 +1229,7 @@ DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Examples/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1232,7 +1248,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = ExamplesTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1253,7 +1269,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = ExamplesTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift index fcf70c39e..91545232c 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift @@ -107,6 +107,10 @@ struct CoreContentView: View { destination: EmptyStateViewExample()) { Text("EmptyStateViewExample") } + + NavigationLink(destination: SortFilterExample()) { + Text("SortFilterExample") + } } }.navigationBarTitle("FioriSwiftUICore") } diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift new file mode 100644 index 000000000..2ba825e21 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -0,0 +1,101 @@ +import FioriSwiftUICore +import SwiftUI + +struct SortFilterExample: View { + @State private var items: [[SortFilterItem]] = [[ + .picker(item: .init(value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], name: "JIRA Status", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), isShownOnMenu: true), + .picker(item: .init(value: [0], valueOptions: ["High", "Medium", "Low"], name: "Priority", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), isShownOnMenu: true), + .filterfeedback(item: .init(value: [0], valueOptions: ["Ascending", "Descending"], name: "Sort Order", allowsMultipleSelection: false, allowsEmptySelection: false, icon: "checkmark")), + .slider(item: .init(value: 10, minimumValue: 0, maximumValue: 100, name: "User Stories", formatter: "%2d Stories", icon: "number"), isShownOnMenu: true), + .slider(item: .init(value: nil, minimumValue: 0, maximumValue: 100, name: "Number of Tasks"), isShownOnMenu: true), + .datetime(item: .init(value: Date(), name: "Start Date", formatter: "yyyy-MM-dd HH:mm",icon: "calendar"), isShownOnMenu: true), + .datetime(item: .init(value: nil, name: "Completion Date"), isShownOnMenu: true), + .switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), isShownOnMenu: true), + .switch(item: .init(name: "Tagged", value: nil, icon: "tag"), isShownOnMenu: false) + ]] + + @State private var isShowingFullCFG: Bool = false + @State private var isCustomStyle: Bool = false + @State private var sortFilterList: [String] = [] + @State private var sortFilterButtonLabel: String = "Sort & Filter" + + var body: some View { + VStack { + if isCustomStyle { + SortFilterMenu(items: $items, onUpdate: performSortAndFilter) + .sortFilterMenuItemStyle(font: .subheadline, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + // .trailingFullConfigurationMenuItem(icon: "command") + // .leadingFullConfigurationMenuItem(icon: "command") + // .leadingFullConfigurationMenuItem(name: "All") + } else { + SortFilterMenu(items: $items, onUpdate: performSortAndFilter) + } + + List { + ForEach(sortFilterList, id: \.self) { line in + Text(line) + } + } + .listStyle(PlainListStyle()) + + VStack { + Toggle("Custom Style", isOn: $isCustomStyle) + .toggleStyle(FioriToggleStyle()) + + Button("Print") { + for line in sortFilterList { + print(line) + } + } + } + } + .navigationTitle("Sort & Filter") + .toolbar { + Button(sortFilterButtonLabel) { + isShowingFullCFG.toggle() + } + .popover(isPresented: $isShowingFullCFG, arrowEdge: .leading) { + if isCustomStyle { + SortFilterFullCFG( + title: "Configuration", + items: $items, + onUpdate: performSortAndFilter + ) + .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + } else { + SortFilterFullCFG( + title: "Configuration", + items: $items, + onUpdate: performSortAndFilter + ) + } + } + } + .onAppear { + performSortAndFilter() + } + } + + func numberOfItems() -> Int { + // randomly padding result to mimic impact of filterring + for i in 0 ... Int.random(in: 0 ... 5) { + self.sortFilterList.append("Non-SortFilterCFG: element \(i + 1)") + } + return self.sortFilterList.count + } + + func performSortAndFilter() { + self.sortFilterList = self.items.joined().map { value(of: $0) } + self.sortFilterButtonLabel = "Sort & Filter (\(self.numberOfItems()))" + } +} + +#if DEBUG + @available(iOS 16.0, *) + struct SortFilterExample_Previews: PreviewProvider { + static var previews: some View { + SortFilterExample() + } + } +#endif diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift new file mode 100644 index 000000000..10686c555 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift @@ -0,0 +1,35 @@ +import FioriSwiftUICore +import SwiftUI + +extension View { + func value(of item: SortFilterItem) -> String { + switch item { + case .picker(let v, _): + return self.json(item: v) + case .filterfeedback(let v): + return self.json(item: v) + case .slider(let v, _): + return self.json(item: v) + case .datetime(let v, _): + return self.json(item: v) + case .switch(let v, _): + return self.json(item: v) + } + } + + func json(item: PickerItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(item.value)}" + } + + func json(item: SliderItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } + + func json(item: DateTimeItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } + + func json(item: SwitchItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } +} diff --git a/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json new file mode 100644 index 000000000..2945b36b9 --- /dev/null +++ b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d91dd8e60c39253ab9753e4f1c0d137b4e06cbdb GIT binary patch literal 934 zcmV;X16lluP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR916rckD1ONa40RR916aWAK0J7ci{Qv+15J^NqR5%fpR7q=8K@k3`Ur#1# zP;(em)aY$okQ~I5H_?+4m-xnrzd%9sqT&YQilPTWe}v{G2K5p*@ZwD`t{6dF2^w)t z$YkE@F003;K0+Enn5^>MDW|pX^!4#1{#?F+J;N3Pft7AI1RK?4cu<-%$vqhUJOcTYeE!SPz4x-MpalaBV?OT2%N_V%~|lHDf<#OMsmekqr?EP$(w zd!|lVE4<@zm024s!|+I37O25D1C}Dsr?)Rb)pnxA-ayV2y)`>c>-S?O|KoT*FuRaz zkphwE@+;C}iBAJe_hvwqxI~Kuj8pYnoII3*5XK5Yx>O!l9;!|Z|MnGirpj{al`5B_ z7kqB>S9zP=vrX=m?6+X62R*ZvauB)PDtNaTEy*?7H}ENan=ds|jV6uq5p84g?w-)$ z=r)`kN|^3?m^*OQp;Siv95V}}4Up{KoMBxvXRJalpn}}d8V^^r(`@?GeI>UwJ`u{} zrgnTwn!Bd%J1HQQqftKFbysbS%Rj~hcOr4dX*yZ+j;d2OYXHl66k)q|c@GeplM2eVo?S6!>H~AIb7zEPL8C!x}yO*Kl~YoME5N zW0-$jCS+1&~()d5RhhJY&n zhtdI?g6V+WYo?d4z9%;8ka!v65>N;26qnkjQ{K5g#UR1@1r+>sK#p6*NdN!<07*qo IM6N<$g1&*0mH+?% literal 0 HcmV?d00001 diff --git a/Package.swift b/Package.swift index 0b4374da2..48162e04b 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "FioriSwiftUI", defaultLocalization: "en", - platforms: [.iOS(.v15), .watchOS(.v7)], + platforms: [.iOS(.v16), .watchOS(.v7)], products: [ .library( name: "FioriSwiftUI", diff --git a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift new file mode 100644 index 000000000..114413063 --- /dev/null +++ b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift @@ -0,0 +1,92 @@ +import Foundation +import SwiftUI + +struct CancellableResettableDialogForm: View { + let title: Title + + let components: Components + + var cancelAction: CancelAction + var resetAction: ResetAction + var applyAction: ApplyAction + + public init(@ViewBuilder title: () -> Title, + @ViewBuilder cancelAction: () -> CancelAction, + @ViewBuilder resetAction: () -> ResetAction, + @ViewBuilder applyAction: () -> ApplyAction, + @ViewBuilder components: () -> Components) + { + self.title = title() + self.cancelAction = cancelAction() + self.resetAction = resetAction() + self.applyAction = applyAction() + self.components = components() + } + + var body: some View { + VStack(spacing: UIDevice.current.userInterfaceIdiom == .pad ? 8 : 16) { + HStack { + cancelAction + Spacer() + title + Spacer() + resetAction + } + components + applyAction + } + .frame(minWidth: 375) + .padding(UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) + } +} + +struct ApplyButtonStyle: PrimitiveButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) + .padding(15) + .font(.body) + .fontWeight(.bold) + .foregroundStyle(Color.preferredColor(.base2)) + .background(RoundedRectangle(cornerRadius: 8).fill(Color.preferredColor(.tintColor))) + .onTapGesture { + configuration.trigger() + } + } +} + +struct CancelResetButtonStyle: PrimitiveButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.body) + .fontWeight(.bold) + .foregroundStyle(Color.preferredColor(.tintColor)) + .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) + .onTapGesture { + configuration.trigger() + } + } +} + +#Preview { + VStack { + Spacer() + CancellableResettableDialogForm { + Text("Date of Completion") + } cancelAction: { + Action(actionText: "Cancel", didSelectAction: nil) + } resetAction: { + Action(actionText: "Reset", didSelectAction: nil) + } applyAction: { + Action(actionText: "Apply", didSelectAction: nil) + .buttonStyle(ApplyButtonStyle()) + } components: { + DatePicker( + "date", + selection: Binding(get: { Date() }, set: { print($0) }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + } +} diff --git a/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift b/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift index 14215991d..a2d135c86 100644 --- a/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift +++ b/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift @@ -53,3 +53,52 @@ internal protocol _DurationPicker: _ComponentMultiPropGenerating, AnyObject { // sourcery: default.value = MeasurementFormatter() var measurementFormatter: MeasurementFormatter { get set } } + +internal protocol _SliderPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Int? { get set } + + // sourcery: no_view + // sourcery: default.value = nil + var formatter: String? { get } + + // sourcery: no_view + // sourcery: default.value = 0.0 + var minimumValue: Int { get } + + // sourcery: no_view + // sourcery: default.value = 100.0 + var maximumValue: Int { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} + +internal protocol _SwitchPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Bool? { get set } + + // sourcery: no_view + // sourcery: default.value = nil + var name: String? { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} + +internal protocol _OptionListPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: [Int] { get set } + + // sourcery: no_view + var valueOptions: [String] { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} diff --git a/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift b/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift index e9731f208..2020a4b4b 100644 --- a/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift +++ b/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift @@ -51,4 +51,6 @@ internal struct _Component: _ComponentGenerating { // sourcery: backingComponent=FootnoteIconStack // sourcery: customFunctionBuilder=FootnoteIconsBuilder let footnoteIcons_: [TextOrIcon]? + let leftIcon_: Image? + let rightIcon_: Image? } diff --git a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift new file mode 100644 index 000000000..d4b455777 --- /dev/null +++ b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift @@ -0,0 +1,521 @@ +import SwiftUI +import UIKit + +public enum SortFilterItem: Identifiable, Hashable { + public var id: String { + switch self { + case .picker(let item, _): + return item.id + case .filterfeedback(let item): + return item.id + case .switch(let item, _): + return item.id + case .slider(let item, _): + return item.id + case .datetime(let item, _): + return item.id + } + } + + case picker(item: PickerItem, isShownOnMenu: Bool) + case filterfeedback(item: PickerItem) + case `switch`(item: SwitchItem, isShownOnMenu: Bool) + case slider(item: SliderItem, isShownOnMenu: Bool) + case datetime(item: DateTimeItem, isShownOnMenu: Bool) + + public var isShownOnMenu: Bool { + switch self { + case .picker(_, let isShownOnMenu): + return isShownOnMenu + case .filterfeedback: + return true + case .switch(_, let isShownOnMenu): + return isShownOnMenu + case .slider(_, let isShownOnMenu): + return isShownOnMenu + case .datetime(_, let isShownOnMenu): + return isShownOnMenu + } + } + + public func hash(into hasher: inout Hasher) { + switch self { + case .picker(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .filterfeedback(let item): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .switch(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .slider(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .datetime(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + } + } +} + +/// (value: [Int], valueOptions: [String], keyName: String?, allowsMultipleSelection: Bool, allowsEmptySelection: Bool) +public struct PickerItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + + public var value: [Int] + public var workingValue: [Int] + let originalValue: [Int] + + var valueOptions: [String] + public let allowsMultipleSelection: Bool + public let allowsEmptySelection: Bool + public let icon: String? + + public init(value: [Int], valueOptions: [String], name: String, allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.valueOptions = valueOptions + self.name = name + self.allowsMultipleSelection = allowsMultipleSelection + self.allowsEmptySelection = allowsEmptySelection + self.icon = icon + } + + mutating func onTap(option: String) { + guard let index = valueOptions.firstIndex(of: option) else { return } + if self.workingValue.contains(index) { + if self.workingValue.count > 1 { + self.workingValue = self.workingValue.filter { $0 != index } + } else { + if self.allowsEmptySelection { + self.workingValue = [] + } else { + self.workingValue = index == 1 ? [0] : [1] + } + } + } else { + if self.allowsMultipleSelection { + self.workingValue.append(index) + } else { + self.workingValue = [index] + } + } + } + + mutating func optionOnTap(_ index: Int) { + if self.workingValue.contains(index) { + if self.workingValue.count > 1 { + self.workingValue = self.workingValue.filter { $0 != index } + } else { + if self.allowsEmptySelection { + self.workingValue = [] + } else { + self.workingValue = index == 1 ? [0] : [1] + } + } + } else { + if self.allowsMultipleSelection { + self.workingValue.append(index) + } else { + self.workingValue = [index] + } + } + } + + mutating func cancel() { + self.workingValue = self.value.map { $0 } + } + + mutating func reset() { + self.workingValue = self.originalValue.map { $0 } + } + + mutating func apply() { + self.value = self.workingValue.map { $0 } + } + + func isOptionSelected(_ option: String) -> Bool { + guard let idx = valueOptions.firstIndex(of: option) else { return false } + return self.workingValue.contains(idx) + } + + func isOptionSelected(index: Int) -> Bool { + self.workingValue.contains(index) + } + + var isChecked: Bool { + !self.value.isEmpty + } + + var label: String { + + if allowsMultipleSelection && self.value.count >= 1 { + if self.value.count == 1 { + return valueOptions[value[0]] + } else { + return "\(self.name) (\(self.value.count))" + } + } else { + return self.name + } + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +/// (value: Bool, keyName: String) +public struct SwitchItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + public var value: Bool? + var workingValue: Bool? + let originalValue: Bool? + public let icon: String? + public let hint: String? + + public init(id: String = UUID().uuidString, name: String, value: Bool?, icon: String? = nil, hint: String? = nil) { + self.id = id + self.name = name + self.value = value + self.workingValue = value + self.originalValue = value + self.icon = icon + self.hint = hint + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + mutating func apply() { + self.value = self.workingValue + } + + var isChecked: Bool { + self.value ?? false + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +/// (value: Float, minimumValue: Float, maximumValue: Float, keyName: String?) +public struct SliderItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + + public var value: Int? + var workingValue: Int? + let originalValue: Int? + public let minimumValue: Int + public let maximumValue: Int + let formatter: String? + public let icon: String? + public let hint: String? + + public init(value: Int? = nil, minimumValue: Int, maximumValue: Int, name: String, formatter: String? = nil, icon: String? = nil, hint: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.minimumValue = minimumValue + self.maximumValue = maximumValue + self.name = name + self.formatter = formatter + self.icon = icon + self.hint = hint + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + mutating func apply() { + self.value = self.workingValue + } + + var isChecked: Bool { + self.value != nil + } + + var label: String { + if let formatter = formatter, let value = value { + return String(format: formatter, value) + } + return name + } + + mutating func setValue(newValue: SliderItem) { + self.value = newValue.value + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +public struct DateTimeItem: Equatable, Hashable { + public let id = UUID().uuidString + + public var name: String + public var value: Date? + var workingValue: Date? + let originalValue: Date? + public var icon: String? + public let formatter: String? + + public init(value: Date?, name: String, formatter: String? = nil, icon: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.name = name + self.formatter = formatter + self.icon = icon + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func apply() { + self.value = self.workingValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + var isChecked: Bool { + self.value != nil + } + + var label: String { + if let value = self.value { + if let format = self.formatter { + let formatter = DateFormatter() + formatter.dateFormat = format + return formatter.string(from: value) + } else { + let dateFormatter: DateFormatter = DateFormatter() + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .short + return dateFormatter.string(from: value) + } + } else { + return self.name + } + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +extension SortFilterItem { + var picker: PickerItem { + get { + switch self { + case .picker(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .picker(_, let isShownOnMenu): + self = .picker(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var filterfeedback: PickerItem { + get { + switch self { + case .filterfeedback(let item): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .filterfeedback: + self = .filterfeedback(item: newValue) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var slider: SliderItem { + get { + switch self { + case .slider(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .slider(_, let isShownOnMenu): + self = .slider(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var datetime: DateTimeItem { + get { + switch self { + case .datetime(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .datetime(_, let isShownOnMenu): + self = .datetime(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var `switch`: SwitchItem { + get { + switch self { + case .switch(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .switch(_, let isShownOnMenu): + self = .switch(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + public var isChanged: Bool { + switch self { + case .picker(let item, _): + return item.isChanged + case .filterfeedback(let item): + return item.isChanged + case .switch(let item, _): + return item.isChanged + case .datetime(let item, _): + return item.isChanged + case .slider(let item, _): + return item.isChanged + } + } + + public mutating func cancel() { + switch self { + case .picker(var item, _): + item.cancel() + self.picker = item + case .filterfeedback(var item): + item.cancel() + self.filterfeedback = item + case .switch(var item, _): + item.cancel() + self.switch = item + case .datetime(var item, _): + item.cancel() + self.datetime = item + case .slider(var item, _): + item.cancel() + self.slider = item + } + } + + public mutating func reset() { + switch self { + case .picker(var item, _): + item.reset() + self.picker = item + case .filterfeedback(var item): + item.reset() + self.filterfeedback = item + case .switch(var item, _): + item.reset() + self.switch = item + case .datetime(var item, _): + item.reset() + self.datetime = item + case .slider(var item, _): + item.reset() + self.slider = item + } + } + + public mutating func apply() { + switch self { + case .picker(var item, _): + item.apply() + self.picker = item + case .filterfeedback(var item): + item.apply() + self.filterfeedback = item + case .switch(var item, _): + item.apply() + self.switch = item + case .datetime(var item, _): + item.apply() + self.datetime = item + case .slider(var item, _): + item.apply() + self.slider = item + } + } +} + +/* + Notes: + c. to resolve: keyName should not be nillable for menu item, but it can be nil for sheet + e. make filter feedback configuraion a separate item type, instead of sharing FilterItem. ?? + */ diff --git a/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift b/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift index 420d1ad11..7c615eecb 100644 --- a/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift +++ b/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift @@ -24,6 +24,22 @@ public struct _CancelActionDefault: ActionModel { public init() {} } +public struct _ResetActionDefault: ActionModel { + public var actionText: String? { + NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") + } + + public init() {} +} + +public struct _ApplyActionDefault: ActionModel { + public var actionText: String? { + NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") + } + + public init() {} +} + public struct _AgreeActionDefault: ActionModel { public var actionText: String? { NSLocalizedString("Agree", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index 41941e396..b625bb3f0 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -433,3 +433,71 @@ public protocol StepProgressIndicatorModel: AnyObject { // sourcery: default.value = _CancelActionDefault() var cancelAction: ActionModel? { get } } + +// sourcery: generated_component_composite +public protocol SortFilterMenuModel: AnyObject { + // sourcery: bindingProperty + // sourcery: backingComponent=_SortFilterMenuItemContainer + var items: [[SortFilterItem]] { get set } + + // sourcery: default.value = nil + // sourcery: no_view + var onUpdate: (() -> Void)? { get set } +} + +// sourcery: virtualPropActionHelper = "@State var context: SortFilterContext = SortFilterContext()" +// sourcery: add_env_props = "dismiss" +// sourcery: generated_component_composite +public protocol SortFilterFullCFGModel: AnyObject, TitleComponent { + // sourcery: bindingProperty + // sourcery: backingComponent=_SortFilterCFGItemContainer + var items: [[SortFilterItem]] { get set } + + // sourcery: genericParameter.name = CancelActionView + // sourcery: default.value = _CancelActionDefault() + var cancelAction: ActionModel? { get } + + // sourcery: genericParameter.name = ResetActionView + // sourcery: default.value = _ResetActionDefault() + var resetAction: ActionModel? { get } + + // sourcery: genericParameter.name = ApplyActionView + // sourcery: default.value = _ApplyActionDefault() + var applyAction: ActionModel? { get } + + // sourcery: default.value = nil + // sourcery: no_view + var onUpdate: (() -> Void)? { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: virtualPropActionHelper = "@State var context: SortFilterContext = SortFilterContext()" +// sourcery: generated_component_composite +public protocol SortFilterMenuItemModel: LeftIconComponent, TitleComponent, RightIconComponent { + // sourcery: no_view + var isSelected: Bool { get } +} + +// sourcery: add_env_props = "optionChipStyle" +// sourcery: generated_component_composite +public protocol OptionChipModel: LeftIconComponent, TitleComponent { + // sourcery: no_view + var isSelected: Bool { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +public protocol OptionListPickerModel: OptionListPickerComponent { + // sourcery: default.value = nil + // sourcery: no_view + var onTap: ((_ index: Int) -> Void)? { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +// sourcery: add_env_props = "fioriToggleStyle" +public protocol SwitchPickerModel: SwitchPickerComponent {} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +public protocol SliderPickerModel: SliderPickerComponent {} diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift new file mode 100644 index 000000000..82bcc02e9 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -0,0 +1,182 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum OptionChip { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + } +} + +extension OptionChip: View { + public var body: some View { + optionChipStyle.makeBody(configuration: OptionChipConfiguration(leftIcon: AnyView(leftIcon), title: AnyView(title), isSelected: _isSelected)) + } +} + +/* + // FIXME: - Implement OptionChip specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct OptionChipLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionChip(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +public struct OptionChipConfiguration { + let leftIcon: AnyView + let title: AnyView + let isSelected: Bool + + public init(leftIcon: AnyView, title: AnyView, isSelected: Bool) { + self.leftIcon = leftIcon + self.title = title + self.isSelected = isSelected + } +} + +public protocol OptionChipStyle { + associatedtype Body = View + + typealias Configuration = OptionChipConfiguration + + func makeBody(configuration: Self.Configuration) -> AnyView // Self.Body +} + +public struct DefaultOptionChipStyle: OptionChipStyle { + let font: Font + let foregroundColorSelected: Color + let foregroundColorUnselected: Color + let fillColorSelected: Color + let fillColorUnselected: Color + let strokeColorSelected: Color + let strokeColorUnselected: Color + let cornerRadius: CGFloat + let spacing: CGFloat + let padding: CGFloat + let borderWidth: CGFloat + let minHeight: CGFloat + + public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) { + self.font = font + self.foregroundColorSelected = foregroundColorSelected + self.foregroundColorUnselected = foregroundColorUnselected + self.fillColorSelected = fillColorSelected + self.fillColorUnselected = fillColorUnselected + self.strokeColorSelected = strokeColorSelected + self.strokeColorUnselected = strokeColorUnselected + self.cornerRadius = cornerRadius + self.spacing = spacing + self.padding = padding + self.borderWidth = borderWidth + self.minHeight = minHeight + } + + public func makeBody(configuration: Configuration) -> AnyView { + AnyView( + HStack(spacing: self.spacing) { + configuration.leftIcon + configuration.title + } + .font(self.font) + .foregroundColor(configuration.isSelected ? self.foregroundColorSelected : self.foregroundColorUnselected) + .padding(self.padding) + .frame(maxWidth: .infinity) + .background( + ZStack { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(configuration.isSelected ? fillColorSelected : fillColorUnselected) + RoundedRectangle(cornerRadius: cornerRadius) + .stroke(configuration.isSelected ? strokeColorSelected : strokeColorUnselected, lineWidth: borderWidth) + } + ) + ) + } +} + +struct OptionChipStyleKey: EnvironmentKey { + static var defaultValue: any OptionChipStyle = DefaultOptionChipStyle() +} + +extension EnvironmentValues { + var optionChipStyle: any OptionChipStyle { + get { + self[OptionChipStyleKey.self] + } + set { + self[OptionChipStyleKey.self] = newValue + } + } +} + +public extension View { + func optionChipStyle(_ style: S) -> some View where S: OptionChipStyle { + self.environment(\.optionChipStyle, style) + } + + func optionChipStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { + self.environment(\.optionChipStyle, + DefaultOptionChipStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) + } +} + +#Preview { + VStack { + Spacer() + + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: true) + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: false) + OptionChip(title: "Ship", isSelected: true) + OptionChip(title: "Ship", isSelected: false) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) + + Spacer() + + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: true) + .optionChipStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: false) + .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + + .optionChipStyle(cornerRadius: 16) + OptionChip(title: "Ship", isSelected: true) + .optionChipStyle(fillColorSelected: .yellow) + OptionChip(title: "Ship", isSelected: false) + .optionChipStyle(fillColorUnselected: .gray) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) + .optionChipStyle(cornerRadius: 20) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) + .optionChipStyle(cornerRadius: 20) + + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift new file mode 100644 index 000000000..f46c06e72 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift @@ -0,0 +1,61 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +import SwiftUI + +extension OptionListPicker: View { + public var body: some View { + Grid { + ForEach(0 ..< Int(ceil(Double(_valueOptions.count) / 2.0))) { rowIndex in + GridRow { + OptionChip( + leftIcon: _value.wrappedValue.contains(rowIndex * 2) ? Image(systemName: "checkmark") : nil, + title: _valueOptions[rowIndex * 2], + isSelected: _value.wrappedValue.contains(rowIndex * 2) + ) + .onTapGesture { + _onTap?(rowIndex * 2) + } + if rowIndex * 2 + 1 < _valueOptions.count { + OptionChip( + leftIcon: _value.wrappedValue.contains(rowIndex * 2 + 1) ? Image(systemName: "checkmark") : nil, + title: _valueOptions[rowIndex * 2 + 1], + isSelected: _value.wrappedValue.contains(rowIndex * 2 + 1) + ) + .onTapGesture { + _onTap?(rowIndex * 2 + 1) + } + } + } + } + } + } +} + +/* + // FIXME: - Implement OptionListPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct OptionListPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionListPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +#Preview { + VStack { + Spacer() + OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + .frame(width: 375) + Spacer() + OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + .optionChipStyle(font: .title, foregroundColorSelected: Color.red, strokeColorSelected: Color.red, cornerRadius: 25) + .frame(width: 375) + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift new file mode 100644 index 000000000..f1578c7af --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift @@ -0,0 +1,191 @@ +import SwiftUI + +extension SliderPicker: View { + public var body: some View { + VStack { + if let formatter = self._formatter { + HStack { + Text(String(format: formatter, _value.wrappedValue ?? _minimumValue)) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + } + HStack { + Slider(value: .convert(from: _value, ifNilUse: _minimumValue), in: Float(_minimumValue) ... Float(_maximumValue), step: 1.0) + TextField("", value: Binding(get: { _value.wrappedValue ?? _minimumValue }, set: { _value.wrappedValue = $0 }), format: .number) + .frame(width: calcWidth(font: .body)) + .keyboardType(.numberPad) + .textFieldStyle(.roundedBorder) + } + if let hint = _hint { + HStack { + Text(hint) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + } + } + } +} + +private extension SliderPicker { + func calcWidth(font: Font) -> CGFloat { + var width: CGFloat = 0 + for i in 0 ... 9 { + let fontAttributes = [NSAttributedString.Key.font: self.preferredFont(from: font)] + let size = String(i).size(withAttributes: fontAttributes) + width = max(size.width, width) + } + return floor(log10(CGFloat(_maximumValue)) + 1) * width + 2 * 12 + } + + func preferredFont(from font: Font) -> UIFont { + let uiFont: UIFont + + switch font { + case .largeTitle: + uiFont = UIFont.preferredFont(forTextStyle: .largeTitle) + case .title: + uiFont = UIFont.preferredFont(forTextStyle: .title1) + case .title2: + uiFont = UIFont.preferredFont(forTextStyle: .title2) + case .title3: + uiFont = UIFont.preferredFont(forTextStyle: .title3) + case .headline: + uiFont = UIFont.preferredFont(forTextStyle: .headline) + case .subheadline: + uiFont = UIFont.preferredFont(forTextStyle: .subheadline) + case .callout: + uiFont = UIFont.preferredFont(forTextStyle: .callout) + case .caption: + uiFont = UIFont.preferredFont(forTextStyle: .caption1) + case .caption2: + uiFont = UIFont.preferredFont(forTextStyle: .caption2) + case .footnote: + uiFont = UIFont.preferredFont(forTextStyle: .footnote) + case .body: + fallthrough + default: + uiFont = UIFont.preferredFont(forTextStyle: .body) + } + + return uiFont + } +} + +struct RangeIntegerStyle: ParseableFormatStyle { + var parseStrategy: RangeIntegerStrategy = .init() + let range: ClosedRange + + func format(_ value: Int) -> String { + let constrainedValue = min(max(value, range.lowerBound), self.range.upperBound) + return "\(constrainedValue)" + } +} + +struct RangeIntegerStrategy: ParseStrategy { + func parse(_ value: String) throws -> Int { + Int(value) ?? 1 + } +} + +extension FormatStyle where Self == RangeIntegerStyle { + static func ranged(_ range: ClosedRange) -> RangeIntegerStyle { + RangeIntegerStyle(range: range) + } +} + +extension Binding { + static func convert(from intBinding: Binding, ifNilUse defaultValue: TInt) -> Binding where TInt: BinaryInteger, TFloat: BinaryFloatingPoint { + Binding( + get: { + TFloat(intBinding.wrappedValue ?? defaultValue) + }, + set: { + intBinding.wrappedValue = TInt($0) + } + ) + } +} + +/* + // FIXME: - Implement SliderPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SliderPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SliderPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private struct SliderPickeTestView: View { + @State var value1: Int? = 10 + @State var value2: Int? = 20 + @State var value3: Int? = nil + + var body: some View { + VStack { + Spacer() + HStack { + Text("Value 1: \($value1.wrappedValue ?? 0)") + .font(.largeTitle) + .foregroundColor(value1 != nil ? .blue : .gray) + Spacer() + } + SliderPicker(value: Binding( + get: { + value1 + }, + set: { + value1 = $0 + } + ), minimumValue: 0, maximumValue: 1000, hint: nil) + Spacer() + HStack { + Text("Value 2: \(value2 ?? 0)") + .font(.largeTitle) + .foregroundColor(value2 != nil ? .blue : .gray) + + Spacer() + } + + SliderPicker(value: Binding( + get: { + value2 + }, + set: { + value2 = $0 + } + ), minimumValue: 0, maximumValue: 100, hint: "Pick an integer value") + Spacer() + HStack { + Text("Value 3: \(value3 ?? 0)") + .font(.largeTitle) + .foregroundColor(value3 != nil ? .blue : .gray) + + Spacer() + } + SliderPicker(value: Binding( + get: { + value3 + }, + set: { + value3 = $0 + } + ), minimumValue: 0, maximumValue: 100, hint: "Pick an integer value") + Spacer() + } + .frame(width: 375) + } +} + +struct SliderPickeTestView_Previews: PreviewProvider { + static var previews: some View { + SliderPickeTestView() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift new file mode 100644 index 000000000..8312f55ba --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift @@ -0,0 +1,77 @@ +import SwiftUI + +struct SortFilterOnModelUpdateAppCallbackKey: EnvironmentKey { + static let defaultValue: () -> Void = { print("default empty callback") } +} + +//struct SortFilterMenuCancelActionKey: EnvironmentKey { +// static var defaultValue: any View = Button("TO BE REPLACED") {} +//} +// +//struct SortFilterMenuResetActionKey: EnvironmentKey { +// static var defaultValue: any View = Button("TO BE REPLACED") {} +//} +// +//struct SortFilterMenuApplyActionKey: EnvironmentKey { +// static var defaultValue: any View = Button("TO BE REPLACED") {} +//} +// +extension EnvironmentValues { + var onModelUpdateAppCallback: () -> Void { + get { + self[SortFilterOnModelUpdateAppCallbackKey.self] + } + + set { + self[SortFilterOnModelUpdateAppCallbackKey.self] = newValue + } + } +// +// var cancelActionView: any View { +// get { +// self[SortFilterMenuCancelActionKey.self] +// } +// +// set { +// self[SortFilterMenuCancelActionKey.self] = newValue +// } +// } +// +// var resetActionView: any View { +// get { +// self[SortFilterMenuResetActionKey.self] +// } +// +// set { +// self[SortFilterMenuResetActionKey.self] = newValue +// } +// } +// +// var applyActionView: any View { +// get { +// self[SortFilterMenuApplyActionKey.self] +// } +// +// set { +// self[SortFilterMenuApplyActionKey.self] = newValue +// } +// } +} +// +extension View { + func onModelUpdateAppCallback(_ closure: @escaping () -> Void) -> some View { + self.environment(\.onModelUpdateAppCallback, closure) + } +// +// func cancelActionView(_ view: any View) -> some View { +// self.environment(\.cancelActionView, view) +// } +// +// func resetActionView(_ view: any View) -> some View { +// self.environment(\.resetActionView, view) +// } +// +// func applyActionView(_ view: any View) -> some View { +// self.environment(\.applyActionView, view) +// } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift new file mode 100644 index 000000000..c87a7c066 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift @@ -0,0 +1,14 @@ +import SwiftUI + +class SortFilterContext: ObservableObject { + @Published public var isResetButtonEnabled: Bool = false + @Published public var isApplyButtonEnabled: Bool = false + + @Published public var handleCancel: (() -> Void)? + @Published public var handleReset: (() -> Void)? + @Published public var handleApply: (() -> Void)? + + @Published public var handleDismiss: (() -> Void)? + + public init() {} +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift new file mode 100644 index 000000000..cc4939d47 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift @@ -0,0 +1,63 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* + import SwiftUI + + // FIXME: - Implement Fiori style definitions + + extension Fiori { + enum SortFilterDialog { + typealias Item = EmptyModifier + typealias ItemCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let item = Item() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let itemCumulative = ItemCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } + } + + // FIXME: - Implement SortFilterDialog View body + + extension SortFilterDialog: View { + public var body: some View { + <# View body #> + } + } + + // FIXME: - Implement SortFilterDialog specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterDialogLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterDialog(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift new file mode 100644 index 000000000..fb05dcfac --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift @@ -0,0 +1,102 @@ +import SwiftUI + +extension Fiori { + enum SortFilterFullCFG { +// typealias Title = EmptyModifier + struct Title: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/) + .foregroundStyle(Color.preferredColor(.primaryLabel)) + .multilineTextAlignment(.center) + } + } + typealias TitleCumulative = EmptyModifier + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + + static let title = Title() + static let items = Items() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let titleCumulative = TitleCumulative() + static let itemsCumulative = ItemsCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } +} + +extension SortFilterFullCFG: View { + public var body: some View { + CancellableResettableDialogForm { + title + } cancelAction: { + cancelAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Cancel...") + context.handleCancel?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + dismiss() + } + ) + .buttonStyle(CancelResetButtonStyle()) + } resetAction: { + resetAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Reset...") + context.handleReset?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + dismiss() + } + ) + .buttonStyle(CancelResetButtonStyle()) + } applyAction: { + applyAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Apply...") + context.handleApply?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + _onUpdate?() + dismiss() + } + ) + + .buttonStyle(ApplyButtonStyle()) + } components: { + _items + .environmentObject(context) + } + } +} + +/* + // FIXME: - Implement SortFilterFullCFG specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterFullCFGLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterFullCFG(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift new file mode 100644 index 000000000..965a2ca90 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift @@ -0,0 +1,29 @@ +// +// SortFilterItemTitle.swift +// +// +// Created by Xu, Charles on 10/24/23. +// + +import SwiftUI +import FioriThemeManager + +public struct SortFilterItemTitle: TitleComponent, View { + public let title: String + + public init(title: String) { + self.title = title + } + + public var body: some View { + Text(title) + .font(.body) + .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/) + .foregroundStyle(Color.preferredColor(.primaryLabel)) + .multilineTextAlignment(.center) + } +} + +#Preview { + SortFilterItemTitle(title: /*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift new file mode 100644 index 000000000..fb4afcfc2 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift @@ -0,0 +1,30 @@ +import SwiftUI + +extension Fiori { + enum SortFilterMenu { + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + + static let items = Items() + static let itemsCumulative = ItemsCumulative() + } +} + +extension SortFilterMenu: View { + public var body: some View { + items + .onModelUpdateAppCallback(_onUpdate!) + } +} + +/* + // FIXME: - Implement SortFilterMenu specific LibraryContentProvider + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterMenuLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenu(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift new file mode 100644 index 000000000..b843c7982 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift @@ -0,0 +1,102 @@ +import FioriThemeManager +import SwiftUI + +public struct SortFilterMenuItemConfiguration { + let leftIcon: AnyView + let title: AnyView + let isSelected: Bool + let rightIcon: AnyView + + public init(leftIcon: AnyView, title: AnyView, isSelected: Bool, rightIcon: AnyView) { + self.leftIcon = leftIcon + self.title = title + self.isSelected = isSelected + self.rightIcon = rightIcon + } +} + +public protocol SortFilterMenuItemStyle { + associatedtype Body = View + + typealias Configuration = SortFilterMenuItemConfiguration + + func makeBody(configuration: Self.Configuration) -> AnyView +} + +public struct DefaultSortFilterMenuItemStyle: SortFilterMenuItemStyle { + let font: Font + let foregroundColorSelected: Color + let foregroundColorUnselected: Color + let fillColorSelected: Color + let fillColorUnselected: Color + let strokeColorSelected: Color + let strokeColorUnselected: Color + let cornerRadius: CGFloat + let spacing: CGFloat + let padding: CGFloat + let borderWidth: CGFloat + let minHeight: CGFloat + + public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) { + self.font = font + self.foregroundColorSelected = foregroundColorSelected + self.foregroundColorUnselected = foregroundColorUnselected + self.fillColorSelected = fillColorSelected + self.fillColorUnselected = fillColorUnselected + self.strokeColorSelected = strokeColorSelected + self.strokeColorUnselected = strokeColorUnselected + self.cornerRadius = cornerRadius + self.spacing = spacing + self.padding = padding + self.borderWidth = borderWidth + self.minHeight = minHeight + } + + public func makeBody(configuration: Configuration) -> AnyView { + AnyView( + HStack(spacing: self.spacing) { + configuration.leftIcon + configuration.title + configuration.rightIcon + } + .font(self.font) + .foregroundColor(configuration.isSelected ? self.foregroundColorSelected : self.foregroundColorUnselected) + .padding(self.padding) + .frame(minHeight: self.minHeight) + .background( + ZStack { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(configuration.isSelected ? fillColorSelected : fillColorUnselected) + RoundedRectangle(cornerRadius: cornerRadius) + .stroke(configuration.isSelected ? strokeColorSelected : strokeColorUnselected, lineWidth: borderWidth) + } + ) + ) + } +} + +struct SortFilterMenuItemStyleKey: EnvironmentKey { + static var defaultValue: any SortFilterMenuItemStyle = DefaultSortFilterMenuItemStyle() +} + +extension EnvironmentValues { + var sortFilterMenuItemStyle: any SortFilterMenuItemStyle { + get { + self[SortFilterMenuItemStyleKey.self] + } + set { + self[SortFilterMenuItemStyleKey.self] = newValue + } + } +} + +public extension View { + func sortFilterMenuItemStyle(_ style: S) -> some View where S: SortFilterMenuItemStyle { + self.environment(\.sortFilterMenuItemStyle, style) + } + + func sortFilterMenuItemStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { + self.environment(\.sortFilterMenuItemStyle, + DefaultSortFilterMenuItemStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift new file mode 100644 index 000000000..33d87d64d --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -0,0 +1,464 @@ +import SwiftUI + +extension Fiori { + enum SortFilterMenuItem { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + typealias RightIcon = EmptyModifier + typealias RightIconCumulative = EmptyModifier + + static let leftIcon = LeftIcon() + static let title = Title() + static let rightIcon = RightIcon() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + static let rightIconCumulative = RightIconCumulative() + } +} + +extension SortFilterMenuItem: View { + public var body: some View { + sortFilterMenuItemStyle.makeBody(configuration: SortFilterMenuItemConfiguration(leftIcon: AnyView(_leftIcon), title: AnyView(_title), isSelected: _isSelected, rightIcon: AnyView(_rightIcon))).typeErased + } +} + +/* + // FIXME: - Implement SortFilterMenuItem specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterMenuItemLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenuItem(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private extension View { + func icon(name: String?, isVisible: Bool) -> Image? { + if isVisible { + if let name = name { + return Image(systemName: name) + } + } + return nil + } +} + +struct FilterFeedbackMenuItem: View { + @Binding var item: PickerItem + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + Group { + ForEach($item.valueOptions.wrappedValue, id: \.self) { opt in + SortFilterMenuItem(leftIcon: item.isOptionSelected(opt) ? icon(name: item.icon, isVisible: true) : nil, title: opt, isSelected: item.isOptionSelected(opt)) + .onTapGesture { + item.onTap(option: opt) + item.apply() + onUpdate() + } + } + } + } +} + +struct SliderMenuItem: View { + @Binding var item: SliderItem + + @State var isSheetVisible = false + + @State var detentHeight: CGFloat = 0 + + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + SortFilterItemTitle(title: item.name) + } cancelAction: { + Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + } resetAction: { + Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.reset() + }) + .buttonStyle(CancelResetButtonStyle()) + } applyAction: { + Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + + } components: { + SliderPicker(value: Binding(get: { item.workingValue }, set: { item.workingValue = $0 }), formatter: item.formatter, minimumValue: item.minimumValue, maximumValue: item.maximumValue) + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } +} + +struct PickerMenuItem: View { + @Binding var item: PickerItem + var onUpdate: () -> Void + + @State var isSheetVisible = false + + @State var detentHeight: CGFloat = 0 + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + if item.valueOptions.count > 4 { + button + } else { + menu + } + } + + @ViewBuilder + var button: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + SortFilterItemTitle(title: item.name) + } cancelAction: { + Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + } resetAction: { + Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.reset() + }) + .buttonStyle(CancelResetButtonStyle()) + } applyAction: { + Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + } components: { + OptionListPicker(value: $item.workingValue, valueOptions: item.valueOptions, hint: nil) { index in + item.onTap(option: item.valueOptions[index]) + } + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } + + @ViewBuilder + var menu: some View { + HStack { + Menu { + ForEach(item.valueOptions.indices) { idx in + if item.isOptionSelected(index: idx) { + Button { + item.onTap(option: item.valueOptions[idx]) + item.apply() + onUpdate() + } label: { + Label { Text(item.valueOptions[idx]) } icon: { Image(fioriName: "fiori.accept") } + } + } else { + Button(item.valueOptions[idx]) { + item.onTap(option: item.valueOptions[idx]) + item.apply() + onUpdate() + } + } + } + } label: { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, isSelected: item.isChecked) + } + } + } +} + +struct HeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat? + + static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) { + guard let nextValue = nextValue() else { return } + value = nextValue + } +} + +private extension View { + func readHeight() -> some View { + self.modifier(ReadHeightModifier()) + } +} + +private struct ReadHeightModifier: ViewModifier { + private var sizeView: some View { + GeometryReader { geometry in + Color.clear.preference(key: HeightPreferenceKey.self, value: geometry.size.height) + } + } + + func body(content: Content) -> some View { + content.background(self.sizeView) + } +} + +struct DateTimeMenuItem: View { + @Binding private var item: DateTimeItem + + @State private var isSheetVisible: Bool = false + + @State var detentHeight: CGFloat = 0 + + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + SortFilterItemTitle(title: item.name) + } cancelAction: { + Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + } resetAction: { + Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.reset() + }) + .buttonStyle(CancelResetButtonStyle()) + } applyAction: { + Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + } components: { + VStack { + HStack { + Text(NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "")) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + DatePicker( + "", + selection: Binding(get: { item.workingValue ?? Date() }, set: { item.workingValue = $0 }), + displayedComponents: [.hourAndMinute] + ) + .labelsHidden() + } + + DatePicker( + item.label, + selection: Binding(get: { item.workingValue ?? Date() }, set: { item.workingValue = $0 }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } +} + +struct SwitchMenuItem: View { + @Binding private var item: SwitchItem + +// @State var detentHeight: CGFloat = 0 + +// @State private var isSheetVisible: Bool = false + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, isSelected: item.isChecked) + .onTapGesture { + if item.value != nil { + item.workingValue?.toggle() + item.apply() + onUpdate() + } else { + item.workingValue = true + item.apply() + onUpdate() + } +// isSheetVisible.toggle() + } +// .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { +// CancellableResettableDialogForm { +// Text(item.name) +// } cancelAction: { +// Action(actionText: "Cancel", didSelectAction: { +// item.cancel() +// isSheetVisible.toggle() +// }) +// .buttonStyle(CancelResetButtonStyle()) +// } resetAction: { +// Action(actionText: "Reset", didSelectAction: { +// item.reset() +// }) +// .buttonStyle(CancelResetButtonStyle()) +// } applyAction: { +// Action(actionText: "Apply", didSelectAction: { +// item.apply() +// onUpdate() +// isSheetVisible.toggle() +// }) +// .buttonStyle(ApplyButtonStyle()) +// } components: { +// SwitchPicker(value: $item.workingValue) +// } +// } + } +} + +struct FullCFGMenuItem: View { + @Environment(\.sortFilterMenuItemFullConfigurationButton) var fullCFGButton + + @Binding var items: [[SortFilterItem]] + + @State var isSheetVisible = false + + var onUpdate: () -> Void + + public init(items: Binding<[[SortFilterItem]]>, onUpdate: @escaping () -> Void) { + self._items = items + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: fullCFGButton.icon, isVisible: true), title: fullCFGButton.name ?? "", isSelected: true) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + SortFilterFullCFG( + title: { + if let title = fullCFGButton.name { + Text(title) + } else { + EmptyView() + } + }, + items: { + _SortFilterCFGItemContainer(items: $items) + }, + cancelAction: { + Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + // item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + }, + resetAction: { + Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + // item.cancel() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + }, + applyAction: { + Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + // item.reset() + }) + .buttonStyle(ApplyButtonStyle()) + }, + onUpdate: {} + ) + } + } +} + +#Preview { + VStack { + Spacer() + + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) + + Spacer() + + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + .sortFilterMenuItemStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + .sortFilterMenuItemStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + + .sortFilterMenuItemStyle(cornerRadius: 16) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + .sortFilterMenuItemStyle(fillColorSelected: .yellow) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + .sortFilterMenuItemStyle(fillColorUnselected: .gray) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) + .sortFilterMenuItemStyle(cornerRadius: 20) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) + .sortFilterMenuItemStyle(cornerRadius: 20) + + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift new file mode 100644 index 000000000..e4ca740a5 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift @@ -0,0 +1,26 @@ +import FioriThemeManager +import SwiftUI + +public final class SortFilterStyle { + public static var instance = SortFilterStyle(iconForCheckedItem: Image(fioriName: "fiori.accept")) + + let iconForCheckedItem: Image? + let iconForMenuItem: Image + let foregroundColorForSelectedItem: Color + let backgroundColorForSelectedColor: Color + let foregroundColorForUnselectedItem: Color + let backgroundColorForUnselectedColor: Color + + public static var shared: SortFilterStyle { + instance + } + + public init(iconForCheckedItem: Image? = nil, iconForMenuItem: Image? = nil, foregroundColorForSelectedItem: Color? = nil, backgroundColorForSelectedColor: Color? = nil, foregroundColorForUnselectedItem: Color? = nil, backgroundColorForUnselectedColor: Color? = nil) { + self.iconForCheckedItem = iconForCheckedItem + self.iconForMenuItem = iconForMenuItem ?? Image(fioriName: "fiori.navigation.down.arrow")! + self.foregroundColorForSelectedItem = foregroundColorForSelectedItem ?? .preferredColor(.tintColor) + self.backgroundColorForSelectedColor = backgroundColorForSelectedColor ?? .preferredColor(.primaryBackground) + self.foregroundColorForUnselectedItem = foregroundColorForUnselectedItem ?? .preferredColor(.separator) + self.backgroundColorForUnselectedColor = foregroundColorForUnselectedItem ?? .preferredColor(.tertiaryFill) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift new file mode 100644 index 000000000..2fe7b2218 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -0,0 +1,173 @@ +// +import FioriThemeManager +// _SortFilterMenuItemContainer.swift +// +// +// Created by Xu, Charles on 9/25/23. +// +import SwiftUI + +public struct _SortFilterCFGItemContainer { + @EnvironmentObject var context: SortFilterContext + + @Binding var _items: [[SortFilterItem]] + + public init(items: Binding<[[SortFilterItem]]>) { + self.__items = items + } +} + +extension _SortFilterCFGItemContainer: View { + public var body: some View { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 20) { + ForEach(0 ..< _items.count) { r in + ForEach(0 ..< _items[r].count) { c in + switch _items[r][c] { + case .picker: + VStack { + HStack { + Text(_items[r][c].picker.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + OptionListPicker( + value: Binding<[Int]>(get: { _items[r][c].picker.workingValue }, set: { _items[r][c].picker.workingValue = $0 }), + valueOptions: _items[r][c].picker.valueOptions, + onTap: { index in + _items[r][c].picker.onTap(option: _items[r][c].picker.valueOptions[index]) + } + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .filterfeedback: + VStack { + HStack { + Text(_items[r][c].filterfeedback.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + OptionListPicker( + value: Binding<[Int]>(get: { _items[r][c].filterfeedback.workingValue }, set: { _items[r][c].filterfeedback.workingValue = $0 }), + valueOptions: _items[r][c].filterfeedback.valueOptions, + onTap: { index in + _items[r][c].filterfeedback.onTap(option: _items[r][c].filterfeedback.valueOptions[index]) + } + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + + case .switch: + VStack { +// Text(_items[r][c].switch.name) + SwitchPicker(value: Binding(get: { _items[r][c].switch.workingValue }, set: { _items[r][c].switch.workingValue = $0 }), name: _items[r][c].switch.name, hint: nil) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .slider: + VStack { + HStack { + Text(_items[r][c].slider.name) + .font(.headline) + Spacer() + } + SliderPicker( + value: Binding(get: { _items[r][c].slider.workingValue }, set: { _items[r][c].slider.workingValue = $0 }), + formatter: _items[r][c].slider.formatter, + minimumValue: _items[r][c].slider.minimumValue, + maximumValue: _items[r][c].slider.maximumValue + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .datetime: + VStack { + HStack { + Text(_items[r][c].datetime.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + + DatePicker( + NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.hourAndMinute] + ) + + DatePicker( + _items[r][c].datetime.label, + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + } + } + } + } + } + .onChange(of: _items) { _ in + for item in _items.joined() { + if item.isChanged { + context.isResetButtonEnabled = true + context.isApplyButtonEnabled = true + return + } + } + context.isResetButtonEnabled = true + } + .onAppear { + context.handleCancel = { + print("....cancel in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].cancel() + } + } + } + + context.handleReset = { + print("....reset in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].reset() + } + } + } + + context.handleApply = { + print("....apply in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].apply() + } + } + } + + context.isResetButtonEnabled = false + context.isApplyButtonEnabled = false + } + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift new file mode 100644 index 000000000..93941c514 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift @@ -0,0 +1,136 @@ +// +// _SortFilterMenuItemContainer.swift +// +// +// Created by Xu, Charles on 9/25/23. +// +import SwiftUI + +public struct _SortFilterMenuItemContainer { + @Environment(\.onModelUpdateAppCallback) var onUpdate: () -> Void +// @Environment(\.cancelActionView) var _cancelAction + @Environment(\.sortFilterMenuItemFullConfigurationButton) var fullCFGButton + @Binding var _items: [[SortFilterItem]] + + public init(items: Binding<[[SortFilterItem]]>) { + self.__items = items + } +} + +extension _SortFilterMenuItemContainer: View { + public var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 10) { + if fullCFGButton.positon == .leading { + FullCFGMenuItem(items: $_items, onUpdate: onUpdate) + } + ForEach(0 ..< _items.count) { r in + ForEach(0 ..< _items[r].count) { c in + if _items[r][c].isShownOnMenu { + switch _items[r][c] { + case .picker: + PickerMenuItem(item: Binding(get: { _items[r][c].picker }, set: { _items[r][c].picker = $0 }), onUpdate: onUpdate) + case .filterfeedback: + FilterFeedbackMenuItem(item: Binding(get: { _items[r][c].filterfeedback }, set: { _items[r][c].filterfeedback = $0 }), onUpdate: onUpdate) + case .switch: + SwitchMenuItem(item: Binding(get: { _items[r][c].switch }, set: { _items[r][c].switch = $0 }), onUpdate: onUpdate) + case .slider: + SliderMenuItem(item: Binding(get: { _items[r][c].slider }, set: { _items[r][c].slider = $0 }), onUpdate: onUpdate) + case .datetime: + DateTimeMenuItem(item: Binding(get: { _items[r][c].datetime }, set: { _items[r][c].datetime = $0 }), onUpdate: onUpdate) + } + } + } + } + if fullCFGButton.positon == .trailing { + FullCFGMenuItem(items: $_items, onUpdate: onUpdate) + } + } + } + .frame(minHeight: 44) + .padding(.leading, 5) + } +} + +public struct SortFilterMenuItemFullConfigurationButtonKey: EnvironmentKey { + public static var defaultValue: SortFilterMenuItemFullConfigurationButton = .none +} + +public struct SortFilterMenuItemFullConfigurationButton { + public let name: String? + public let icon: String? + public let positon: Position + + public enum Position { + case leading, trailing, none + } + + private init(name: String? = nil, icon: String? = nil, positon: Position) { + self.name = name + self.icon = icon + self.positon = positon + } + + public static func leading(name: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, positon: .leading) + } + + public static func leading(icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(icon: icon, positon: .leading) + } + + public static func leading(name: String, icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, icon: icon, positon: .leading) + } + + public static func trailing(name: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, positon: .trailing) + } + + public static func trailing(icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(icon: icon, positon: .trailing) + } + + public static func trailing(name: String, icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, icon: icon, positon: .trailing) + } + + static var none = SortFilterMenuItemFullConfigurationButton(positon: Position.none) +} + +public extension EnvironmentValues { + var sortFilterMenuItemFullConfigurationButton: SortFilterMenuItemFullConfigurationButton { + get { + self[SortFilterMenuItemFullConfigurationButtonKey.self] + } + set { + self[SortFilterMenuItemFullConfigurationButtonKey.self] = newValue + } + } +} + +public extension View { + func leadingFullConfigurationMenuItem(name: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name)) + } + + func leadingFullConfigurationMenuItem(icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(icon: icon)) + } + + func leadingFullConfigurationMenuItem(name: String, icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name, icon: icon)) + } + + func trailingFullConfigurationMenuItem(name: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(name: name)) + } + + func trailingFullConfigurationMenuItem(icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(icon: icon)) + } + + func trailingFullConfigurationMenuItem(name: String, icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(name: name, icon: icon)) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift new file mode 100644 index 000000000..851a694f5 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift @@ -0,0 +1,158 @@ +import FioriThemeManager +import SwiftUI + +extension SwitchPicker: View { + public var body: some View { + AnyView( + Toggle(_name ?? "", isOn: .convert(from: _value, ifNilUse: false)) + .toggleStyle(fioriToggleStyle) + ).typeErased + } +} + +private extension Binding { + static func convert(from value: Binding, ifNilUse defaultValue: Bool) -> Binding { + Binding( + get: { + value.wrappedValue ?? defaultValue + }, + set: { + value.wrappedValue = $0 + } + ) + } +} + +public struct FioriToggleStyle: ToggleStyle { + @ScaledMetric var scale: CGFloat = 1 + + let labelColor: Color + + let onColor: Color + let offColor: Color + + let onThumbColor: Color + let offThumbColor: Color + + let onBorderColor: Color + let offBorderColor: Color + + public init( + labelColor: Color = Color.preferredColor(.primaryLabel), + onColor: Color = Color.preferredColor(.tintColor), + offColor: Color = Color.preferredColor(.secondaryFill), + onThumbColor: Color = Color.preferredColor(.primaryBackground), + offThumbColor: Color = Color.preferredColor(.primaryBackground), + onBorderColor: Color = Color.preferredColor(.separator), + offBorderColor: Color = Color.preferredColor(.separator) + ) { + self.labelColor = labelColor + + self.onColor = onColor + self.offColor = offColor + + self.onThumbColor = onThumbColor + self.offThumbColor = offThumbColor + + self.onBorderColor = onBorderColor + self.offBorderColor = offBorderColor + } + + public func makeBody(configuration: Self.Configuration) -> some View { + HStack { + configuration.label + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(labelColor) + + Spacer() + ZStack { + RoundedRectangle(cornerRadius: 16 * scale, style: .circular) + .stroke(configuration.isOn ? onBorderColor : offBorderColor, lineWidth: 0.5 * scale) + .frame(width: 51 * scale, height: 30 * scale) + + RoundedRectangle(cornerRadius: 16 * scale, style: .circular) + .fill(configuration.isOn ? onColor : offColor) + .frame(width: 51 * scale, height: 30 * scale) + .overlay( + Circle() + .fill(configuration.isOn ? onThumbColor : offThumbColor) + .shadow(radius: 1 * scale, x: 0, y: 1 * scale) + .padding(1.5 * scale) + .offset(x: configuration.isOn ? 10 * scale : -10 * scale)) + .animation(Animation.easeInOut(duration: 0.2), value: configuration.isOn) + .frame(minHeight: 44) + .onTapGesture { configuration.isOn.toggle() } + } + } + .padding(.horizontal) + } +} + +// public struct DefaultToggleStyle: ToggleStyle { +// public func makeBody(configuration: Configuration) -> some View { +// VStack { +// Toggle(configuration) +// .labelsHidden() +// .foregroundColor(Color.tintColor) +// configuration.label +// .font(.system(size: 22, weight: .semibold)).lineLimit(2) +// .padding() +// .overlay( +// RoundedRectangle(cornerRadius: 10) +// .stroke(configuration.isOn ? Color.green: Color.gray, lineWidth: 1) +// ) +// } +// } +// } + +public struct FioriToggleStyleKey: EnvironmentKey { + public static var defaultValue: any ToggleStyle = FioriToggleStyle() +} + +public extension EnvironmentValues { + var fioriToggleStyle: any ToggleStyle { + get { + self[FioriToggleStyleKey.self] + } + set { + self[FioriToggleStyleKey.self] = newValue + } + } +} + +//public extension View { +// func fioriToggleStyle(_ style: FioriToggleStyle) -> some View { +// self.toggleStyle(style) +// } +//} +/* + // FIXME: - Implement SwitchPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SwitchPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SwitchPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private struct TestSwitchPicker: View { + @State var v1: Bool? = true + @State var v2: Bool? = false + @State var v3: Bool? = nil + + var body: some View { + VStack { + SwitchPicker(value: $v1, hint: nil) + SwitchPicker(value: $v2, hint: nil) + SwitchPicker(value: $v3, hint: nil) + } + } +} + +#Preview { + TestSwitchPicker() + .frame(width: 375) +} diff --git a/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift index 9ffbfc4de..ba02aa975 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -158,6 +158,14 @@ public protocol FootnoteIconsComponent { var footnoteIcons: [TextOrIcon]? { get } } +public protocol LeftIconComponent { + var leftIcon: Image? { get } +} + +public protocol RightIconComponent { + var rightIcon: Image? { get } +} + public protocol ActionComponent { var actionText: String? { get } @@ -194,10 +202,51 @@ public protocol KpiProgressComponent : KpiComponent { var fraction: Double? { get } } +public protocol OptionListPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: [Int] { get set } + // sourcery: no_view + var valueOptions: [String] { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + public protocol ProgressIndicatorComponent { var progressIndicatorText: String? { get } } +public protocol SliderPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Int? { get set } + // sourcery: default.value=nil + // sourcery: no_view + var formatter: String? { get } + // sourcery: default.value=0 + // sourcery: no_view + var minimumValue: Int { get } + // sourcery: default.value=100 + // sourcery: no_view + var maximumValue: Int { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + +public protocol SwitchPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Bool? { get set } + // sourcery: default.value=nil + // sourcery: no_view + var name: String? { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + public protocol TextInputComponent : AnyObject { // sourcery: bindingPropertyOptional=.constant("") var textInputValue: String { get set } diff --git a/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift index 97eae186c..013e39d75 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -142,12 +142,24 @@ public extension KpiProgressComponent { } } +public extension LeftIconComponent { + var leftIcon: Image? { + return nil + } +} + public extension LowerBoundTitleComponent { var lowerBoundTitle: String? { return nil } } +public extension OptionListPickerComponent { + var hint: String? { + return nil + } +} + public extension PlaceholderComponent { var placeholder: String? { return nil @@ -160,6 +172,12 @@ public extension ProgressIndicatorComponent { } } +public extension RightIconComponent { + var rightIcon: Image? { + return nil + } +} + public extension SecondActionTitleComponent { var secondActionTitle: String? { return nil @@ -184,6 +202,28 @@ public extension SecondaryValuesAxisTitleComponent { } } +public extension SliderPickerComponent { + var formatter: String? { + return nil + } + + var minimumValue: Int { + return 0 + } + + var maximumValue: Int { + return 100 + } + + var hint: String? { + return nil + } + + var value: Int? { + return nil + } +} + public extension StatusComponent { var status: TextOrIcon? { return nil @@ -202,6 +242,20 @@ public extension SubtitleComponent { } } +public extension SwitchPickerComponent { + var name: String? { + return nil + } + + var hint: String? { + return nil + } + + var value: Bool? { + return nil + } +} + public extension TagsComponent { var tags: [String]? { return nil diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift index 43459f573..98937a7c0 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -146,19 +146,23 @@ struct FootnoteIconsModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct ActionTextModifierKey: EnvironmentKey { +struct LeftIconModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct ActionItemsModifierKey: EnvironmentKey { +struct RightIconModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct ProgressIndicatorTextModifierKey: EnvironmentKey { +struct ActionTextModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct NodeModifierKey: EnvironmentKey { +struct ActionItemsModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ProgressIndicatorTextModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } @@ -206,6 +210,22 @@ struct SaveActionModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } +struct NodeModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ItemsModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ResetActionModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ApplyActionModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + struct NextActionModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift index 016d1dda8..98e973869 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -184,6 +184,16 @@ extension EnvironmentValues { set { self[FootnoteIconsModifierKey.self] = newValue } } + public var leftIconModifier: AnyViewModifier { + get { return self[LeftIconModifierKey.self] } + set { self[LeftIconModifierKey.self] = newValue } + } + + public var rightIconModifier: AnyViewModifier { + get { return self[RightIconModifierKey.self] } + set { self[RightIconModifierKey.self] = newValue } + } + public var actionTextModifier: AnyViewModifier { get { return self[ActionTextModifierKey.self] } set { self[ActionTextModifierKey.self] = newValue } @@ -199,11 +209,6 @@ extension EnvironmentValues { set { self[ProgressIndicatorTextModifierKey.self] = newValue } } - public var nodeModifier: AnyViewModifier { - get { return self[NodeModifierKey.self] } - set { self[NodeModifierKey.self] = newValue } - } - public var textInputValueModifier: AnyViewModifier { get { return self[TextInputValueModifierKey.self] } set { self[TextInputValueModifierKey.self] = newValue } @@ -259,6 +264,26 @@ extension EnvironmentValues { set { self[SaveActionModifierKey.self] = newValue } } + public var nodeModifier: AnyViewModifier { + get { return self[NodeModifierKey.self] } + set { self[NodeModifierKey.self] = newValue } + } + + public var itemsModifier: AnyViewModifier { + get { return self[ItemsModifierKey.self] } + set { self[ItemsModifierKey.self] = newValue } + } + + public var resetActionModifier: AnyViewModifier { + get { return self[ResetActionModifierKey.self] } + set { self[ResetActionModifierKey.self] = newValue } + } + + public var applyActionModifier: AnyViewModifier { + get { return self[ApplyActionModifierKey.self] } + set { self[ApplyActionModifierKey.self] = newValue } + } + public var nextActionModifier: AnyViewModifier { get { return self[NextActionModifierKey.self] } set { self[NextActionModifierKey.self] = newValue } @@ -463,6 +488,16 @@ public extension View { self.environment(\.footnoteIconsModifier, AnyViewModifier(transform)) } + @ViewBuilder + func leftIconModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.leftIconModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func rightIconModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.rightIconModifier, AnyViewModifier(transform)) + } + @ViewBuilder func actionTextModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.actionTextModifier, AnyViewModifier(transform)) @@ -478,11 +513,6 @@ public extension View { self.environment(\.progressIndicatorTextModifier, AnyViewModifier(transform)) } - @ViewBuilder - func nodeModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { - self.environment(\.nodeModifier, AnyViewModifier(transform)) - } - @ViewBuilder func textInputValueModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.textInputValueModifier, AnyViewModifier(transform)) @@ -538,6 +568,26 @@ public extension View { self.environment(\.saveActionModifier, AnyViewModifier(transform)) } + @ViewBuilder + func nodeModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.nodeModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func itemsModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.itemsModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func resetActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.resetActionModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func applyActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.applyActionModifier, AnyViewModifier(transform)) + } + @ViewBuilder func nextActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.nextActionModifier, AnyViewModifier(transform)) diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift new file mode 100644 index 000000000..b7e8c3e77 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct OptionChip { + @Environment(\.leftIconModifier) private var leftIconModifier + @Environment(\.titleModifier) private var titleModifier + @Environment(\.optionChipStyle) var optionChipStyle + + let _leftIcon: LeftIcon + let _title: Title + let _isSelected: Bool + + + private var isModelInit: Bool = false + private var isLeftIconNil: Bool = false + + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self._leftIcon = leftIcon() + self._title = title() + self._isSelected = isSelected + } + + @ViewBuilder var leftIcon: some View { + if isModelInit { + _leftIcon.modifier(leftIconModifier.concat(Fiori.OptionChip.leftIcon).concat(Fiori.OptionChip.leftIconCumulative)) + } else { + _leftIcon.modifier(leftIconModifier.concat(Fiori.OptionChip.leftIcon)) + } + } + @ViewBuilder var title: some View { + if isModelInit { + _title.modifier(titleModifier.concat(Fiori.OptionChip.title).concat(Fiori.OptionChip.titleCumulative)) + } else { + _title.modifier(titleModifier.concat(Fiori.OptionChip.title)) + } + } + + var isLeftIconEmptyView: Bool { + ((isModelInit && isLeftIconNil) || LeftIcon.self == EmptyView.self) ? true : false + } +} + +extension OptionChip where LeftIcon == _ConditionalContent, + Title == Text { + + public init(model: OptionChipModel) { + self.init(leftIcon: model.leftIcon, title: model.title, isSelected: model.isSelected) + } + + public init(leftIcon: Image? = nil, title: String, isSelected: Bool) { + self._leftIcon = leftIcon != nil ? ViewBuilder.buildEither(first: leftIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._title = Text(title) + self._isSelected = isSelected + + isModelInit = true + isLeftIconNil = leftIcon == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift new file mode 100644 index 000000000..ef062bf3f --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift @@ -0,0 +1,23 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct OptionListPicker { + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding<[Int]> + var _valueOptions: [String] + var _hint: String? = nil + var _onTap: ((_ index: Int) -> Void)? = nil + + public init(model: OptionListPickerModel) { + self.init(value: Binding<[Int]>(get: { model.value }, set: { model.value = $0 }), valueOptions: model.valueOptions, hint: model.hint, onTap: model.onTap) + } + + public init(value: Binding<[Int]>, valueOptions: [String] = [], hint: String? = nil, onTap: ((_ index: Int) -> Void)? = nil) { + self._value = value + self._valueOptions = valueOptions + self._hint = hint + self._onTap = onTap + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift new file mode 100644 index 000000000..ad4308fe7 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift @@ -0,0 +1,25 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SliderPicker { + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding + var _formatter: String? = nil + var _minimumValue: Int + var _maximumValue: Int + var _hint: String? = nil + + public init(model: SliderPickerModel) { + self.init(value: Binding(get: { model.value }, set: { model.value = $0 }), formatter: model.formatter, minimumValue: model.minimumValue, maximumValue: model.maximumValue, hint: model.hint) + } + + public init(value: Binding, formatter: String? = nil, minimumValue: Int = 0, maximumValue: Int = 100, hint: String? = nil) { + self._value = value + self._formatter = formatter + self._minimumValue = minimumValue + self._maximumValue = maximumValue + self._hint = hint + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift new file mode 100644 index 000000000..52b58f7b1 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift @@ -0,0 +1,116 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterFullCFG { + @Environment(\.titleModifier) private var titleModifier + @Environment(\.itemsModifier) private var itemsModifier + @Environment(\.cancelActionModifier) private var cancelActionModifier + @Environment(\.resetActionModifier) private var resetActionModifier + @Environment(\.applyActionModifier) private var applyActionModifier + @Environment(\.dismiss) var dismiss + + let _title: Title + var _items: Items + let _cancelAction: CancelActionView + let _resetAction: ResetActionView + let _applyAction: ApplyActionView + let _onUpdate: (() -> Void)? + @State var context: SortFilterContext = SortFilterContext() + + private var isModelInit: Bool = false + private var isCancelActionNil: Bool = false + private var isResetActionNil: Bool = false + private var isApplyActionNil: Bool = false + private var isOnUpdateNil: Bool = false + + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder resetAction: () -> ResetActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self._title = title() + self._items = items() + self._cancelAction = cancelAction() + self._resetAction = resetAction() + self._applyAction = applyAction() + self._onUpdate = onUpdate + } + + @ViewBuilder var title: some View { + if isModelInit { + _title.modifier(titleModifier.concat(Fiori.SortFilterFullCFG.title).concat(Fiori.SortFilterFullCFG.titleCumulative)) + } else { + _title.modifier(titleModifier.concat(Fiori.SortFilterFullCFG.title)) + } + } + @ViewBuilder var items: some View { + if isModelInit { + _items.modifier(itemsModifier.concat(Fiori.SortFilterFullCFG.items).concat(Fiori.SortFilterFullCFG.itemsCumulative)) + } else { + _items.modifier(itemsModifier.concat(Fiori.SortFilterFullCFG.items)) + } + } + @ViewBuilder var cancelAction: some View { + if isModelInit { + _cancelAction.modifier(cancelActionModifier.concat(Fiori.SortFilterFullCFG.cancelAction).concat(Fiori.SortFilterFullCFG.cancelActionCumulative)) + } else { + _cancelAction.modifier(cancelActionModifier.concat(Fiori.SortFilterFullCFG.cancelAction)) + } + } + @ViewBuilder var resetAction: some View { + if isModelInit { + _resetAction.modifier(resetActionModifier.concat(Fiori.SortFilterFullCFG.resetAction).concat(Fiori.SortFilterFullCFG.resetActionCumulative)) + } else { + _resetAction.modifier(resetActionModifier.concat(Fiori.SortFilterFullCFG.resetAction)) + } + } + @ViewBuilder var applyAction: some View { + if isModelInit { + _applyAction.modifier(applyActionModifier.concat(Fiori.SortFilterFullCFG.applyAction).concat(Fiori.SortFilterFullCFG.applyActionCumulative)) + } else { + _applyAction.modifier(applyActionModifier.concat(Fiori.SortFilterFullCFG.applyAction)) + } + } + + var isCancelActionEmptyView: Bool { + ((isModelInit && isCancelActionNil) || CancelActionView.self == EmptyView.self) ? true : false + } + + var isResetActionEmptyView: Bool { + ((isModelInit && isResetActionNil) || ResetActionView.self == EmptyView.self) ? true : false + } + + var isApplyActionEmptyView: Bool { + ((isModelInit && isApplyActionNil) || ApplyActionView.self == EmptyView.self) ? true : false + } +} + +extension SortFilterFullCFG where Title == Text, + Items == _SortFilterCFGItemContainer, + CancelActionView == _ConditionalContent, + ResetActionView == _ConditionalContent, + ApplyActionView == _ConditionalContent { + + public init(model: SortFilterFullCFGModel) { + self.init(title: model.title, items: Binding<[[SortFilterItem]]>(get: { model.items }, set: { model.items = $0 }), cancelAction: model.cancelAction != nil ? Action(model: model.cancelAction!) : nil, resetAction: model.resetAction != nil ? Action(model: model.resetAction!) : nil, applyAction: model.applyAction != nil ? Action(model: model.applyAction!) : nil, onUpdate: model.onUpdate) + } + + public init(title: String, items: Binding<[[SortFilterItem]]>, cancelAction: Action? = Action(model: _CancelActionDefault()), resetAction: Action? = Action(model: _ResetActionDefault()), applyAction: Action? = Action(model: _ApplyActionDefault()), onUpdate: (() -> Void)? = nil) { + self._title = Text(title) + self._items = _SortFilterCFGItemContainer(items: items) + self._cancelAction = cancelAction != nil ? ViewBuilder.buildEither(first: cancelAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._resetAction = resetAction != nil ? ViewBuilder.buildEither(first: resetAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._applyAction = applyAction != nil ? ViewBuilder.buildEither(first: applyAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._onUpdate = onUpdate + + isModelInit = true + isCancelActionNil = cancelAction == nil ? true : false + isResetActionNil = resetAction == nil ? true : false + isApplyActionNil = applyAction == nil ? true : false + isOnUpdateNil = onUpdate == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift new file mode 100644 index 000000000..97352cf88 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift @@ -0,0 +1,47 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterMenu { + @Environment(\.itemsModifier) private var itemsModifier + + var _items: Items + var _onUpdate: (() -> Void)? + + + private var isModelInit: Bool = false + private var isOnUpdateNil: Bool = false + + public init( + @ViewBuilder items: () -> Items, + onUpdate: (() -> Void)? = nil + ) { + self._items = items() + self._onUpdate = onUpdate + } + + @ViewBuilder var items: some View { + if isModelInit { + _items.modifier(itemsModifier.concat(Fiori.SortFilterMenu.items).concat(Fiori.SortFilterMenu.itemsCumulative)) + } else { + _items.modifier(itemsModifier.concat(Fiori.SortFilterMenu.items)) + } + } + + +} + +extension SortFilterMenu where Items == _SortFilterMenuItemContainer { + + public init(model: SortFilterMenuModel) { + self.init(items: Binding<[[SortFilterItem]]>(get: { model.items }, set: { model.items = $0 }), onUpdate: model.onUpdate) + } + + public init(items: Binding<[[SortFilterItem]]>, onUpdate: (() -> Void)? = nil) { + self._items = _SortFilterMenuItemContainer(items: items) + self._onUpdate = onUpdate + + isModelInit = true + isOnUpdateNil = onUpdate == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift new file mode 100644 index 000000000..5469109c4 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift @@ -0,0 +1,82 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterMenuItem { + @Environment(\.leftIconModifier) private var leftIconModifier + @Environment(\.titleModifier) private var titleModifier + @Environment(\.rightIconModifier) private var rightIconModifier + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + let _leftIcon: LeftIcon + let _title: Title + let _rightIcon: RightIcon + let _isSelected: Bool + @State var context: SortFilterContext = SortFilterContext() + + private var isModelInit: Bool = false + private var isLeftIconNil: Bool = false + private var isRightIconNil: Bool = false + + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + @ViewBuilder rightIcon: () -> RightIcon, + isSelected: Bool + ) { + self._leftIcon = leftIcon() + self._title = title() + self._rightIcon = rightIcon() + self._isSelected = isSelected + } + + @ViewBuilder var leftIcon: some View { + if isModelInit { + _leftIcon.modifier(leftIconModifier.concat(Fiori.SortFilterMenuItem.leftIcon).concat(Fiori.SortFilterMenuItem.leftIconCumulative)) + } else { + _leftIcon.modifier(leftIconModifier.concat(Fiori.SortFilterMenuItem.leftIcon)) + } + } + @ViewBuilder var title: some View { + if isModelInit { + _title.modifier(titleModifier.concat(Fiori.SortFilterMenuItem.title).concat(Fiori.SortFilterMenuItem.titleCumulative)) + } else { + _title.modifier(titleModifier.concat(Fiori.SortFilterMenuItem.title)) + } + } + @ViewBuilder var rightIcon: some View { + if isModelInit { + _rightIcon.modifier(rightIconModifier.concat(Fiori.SortFilterMenuItem.rightIcon).concat(Fiori.SortFilterMenuItem.rightIconCumulative)) + } else { + _rightIcon.modifier(rightIconModifier.concat(Fiori.SortFilterMenuItem.rightIcon)) + } + } + + var isLeftIconEmptyView: Bool { + ((isModelInit && isLeftIconNil) || LeftIcon.self == EmptyView.self) ? true : false + } + + var isRightIconEmptyView: Bool { + ((isModelInit && isRightIconNil) || RightIcon.self == EmptyView.self) ? true : false + } +} + +extension SortFilterMenuItem where LeftIcon == _ConditionalContent, + Title == Text, + RightIcon == _ConditionalContent { + + public init(model: SortFilterMenuItemModel) { + self.init(leftIcon: model.leftIcon, title: model.title, rightIcon: model.rightIcon, isSelected: model.isSelected) + } + + public init(leftIcon: Image? = nil, title: String, rightIcon: Image? = nil, isSelected: Bool) { + self._leftIcon = leftIcon != nil ? ViewBuilder.buildEither(first: leftIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._title = Text(title) + self._rightIcon = rightIcon != nil ? ViewBuilder.buildEither(first: rightIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._isSelected = isSelected + + isModelInit = true + isLeftIconNil = leftIcon == nil ? true : false + isRightIconNil = rightIcon == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift new file mode 100644 index 000000000..916d3cd29 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift @@ -0,0 +1,22 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SwitchPicker { + @Environment(\.fioriToggleStyle) var fioriToggleStyle + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding + var _name: String? = nil + var _hint: String? = nil + + public init(model: SwitchPickerModel) { + self.init(value: Binding(get: { model.value }, set: { model.value = $0 }), name: model.name, hint: model.hint) + } + + public init(value: Binding, name: String? = nil, hint: String? = nil) { + self._value = value + self._name = name + self._hint = hint + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift new file mode 100644 index 000000000..237897746 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift @@ -0,0 +1,62 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/OptionChip+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement OptionChip `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum OptionChip { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + } +} + +// FIXME: - Implement OptionChip View body + +extension OptionChip: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement OptionChip specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct OptionChipLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionChip(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift new file mode 100644 index 000000000..37e1d46ef --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/OptionListPicker+View.swift` +//TODO: Implement OptionListPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement OptionListPicker View body + +extension OptionListPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement OptionListPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct OptionListPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionListPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift new file mode 100644 index 000000000..2e6706c9b --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SliderPicker+View.swift` +//TODO: Implement SliderPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement SliderPicker View body + +extension SliderPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SliderPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SliderPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SliderPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift new file mode 100644 index 000000000..20e9cfd89 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift @@ -0,0 +1,74 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterFullCFG+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterFullCFG `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterFullCFG { + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let title = Title() + static let items = Items() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let titleCumulative = TitleCumulative() + static let itemsCumulative = ItemsCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } +} + +// FIXME: - Implement SortFilterFullCFG View body + +extension SortFilterFullCFG: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterFullCFG specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterFullCFGLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterFullCFG(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift new file mode 100644 index 000000000..d1ae49304 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift @@ -0,0 +1,58 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterMenu+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterMenu `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterMenu { + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let items = Items() + static let itemsCumulative = ItemsCumulative() + } +} + +// FIXME: - Implement SortFilterMenu View body + +extension SortFilterMenu: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterMenu specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterMenuLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenu(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift new file mode 100644 index 000000000..7e85ea3f1 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift @@ -0,0 +1,66 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterMenuItem+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterMenuItem `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterMenuItem { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + typealias RightIcon = EmptyModifier + typealias RightIconCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let rightIcon = RightIcon() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + static let rightIconCumulative = RightIconCumulative() + } +} + +// FIXME: - Implement SortFilterMenuItem View body + +extension SortFilterMenuItem: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterMenuItem specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterMenuItemLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenuItem(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift new file mode 100644 index 000000000..4ac15b331 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SwitchPicker+View.swift` +//TODO: Implement SwitchPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement SwitchPicker View body + +extension SwitchPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SwitchPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SwitchPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SwitchPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift new file mode 100644 index 000000000..b50093bb2 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift @@ -0,0 +1,16 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension OptionChip where LeftIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + isSelected: isSelected + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift new file mode 100644 index 000000000..bad77d3f6 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift @@ -0,0 +1,131 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension SortFilterFullCFG where CancelActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder resetAction: () -> ResetActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: resetAction, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ResetActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: cancelAction, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ApplyActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder resetAction: () -> ResetActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: cancelAction, + resetAction: resetAction, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder resetAction: () -> ResetActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: resetAction, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ResetActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: cancelAction, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift new file mode 100644 index 000000000..1f3fcd0e5 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift @@ -0,0 +1,47 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension SortFilterMenuItem where LeftIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder rightIcon: () -> RightIcon, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + rightIcon: rightIcon, + isSelected: isSelected + ) + } +} + +extension SortFilterMenuItem where RightIcon == EmptyView { + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: leftIcon, + title: title, + rightIcon: { EmptyView() }, + isSelected: isSelected + ) + } +} + +extension SortFilterMenuItem where LeftIcon == EmptyView, RightIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + rightIcon: { EmptyView() }, + isSelected: isSelected + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift new file mode 100644 index 000000000..bd22eec19 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift @@ -0,0 +1,9 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension OptionListPickerModel { + var onTap: ((_ index: Int) -> Void)? { + return nil + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift new file mode 100644 index 000000000..2a7904961 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift @@ -0,0 +1,21 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension SortFilterFullCFGModel { + var cancelAction: ActionModel? { + return _CancelActionDefault() + } + + var resetAction: ActionModel? { + return _ResetActionDefault() + } + + var applyAction: ActionModel? { + return _ApplyActionDefault() + } + + var onUpdate: (() -> Void)? { + return nil + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift new file mode 100644 index 000000000..e0d70748f --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift @@ -0,0 +1,9 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension SortFilterMenuModel { + var onUpdate: (() -> Void)? { + return nil + } +} diff --git a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings index 6516463b6..ddb06b936 100644 --- a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings @@ -129,3 +129,6 @@ /* XBUT: voice over label for row selection status in DataTable */ "not selected" = "not selected"; + +/* XBUT: calendar time component label */ +"Time" = "Time"; From 03c47aca2d3627bdd6f457f5b48370d042888168 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Thu, 19 Oct 2023 14:22:06 -0700 Subject: [PATCH 03/22] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20jira=202286=20sort?= =?UTF-8?q?=20&=20filter=20for=20SwiftUI=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A SwiftUI component for configuration criteria of performing sorting and filter. SortFilterMenu and SortFilterFullCFG are provided. ✅ Closes: 1 --- .../Examples.xcodeproj/project.pbxproj | 34 +- .../FioriSwiftUICore/CoreContentView.swift | 4 + .../SortFilter/SortFilterExample.swift | 101 ++++ .../SortFilter/View+Extensions.swift | 35 ++ .../iconfromapp.imageset/Contents.json | 21 + .../iconfromapp.imageset/icon.png | Bin 0 -> 934 bytes Package.swift | 2 +- .../CancellableResettableForm.swift | 92 ++++ .../Components/MultiPropertyComponents.swift | 48 ++ .../Components/SinglePropertyComponents.swift | 2 + .../DataTypes/SoftFilter+DataType.swift | 519 ++++++++++++++++++ .../Models/DefaultViewModels.swift | 16 + .../Models/ModelDefinitions.swift | 68 +++ .../Views/OptionChip+View.swift | 182 ++++++ .../Views/OptionListPicker+View.swift | 61 ++ .../Views/SliderPicker+View.swift | 191 +++++++ .../SortFilter/SortFilter+Environment.swift | 23 + .../Views/SortFilter/SortFilterContext.swift | 14 + .../SortFilter/SortFilterDialog+View.swift | 63 +++ .../SortFilter/SortFilterFullCFG+View.swift | 100 ++++ .../SortFilter/SortFilterItemTitle.swift | 29 + .../SortFilter/SortFilterMenu+View.swift | 30 + .../SortFilter/SortFilterMenuItem+Style.swift | 102 ++++ .../SortFilter/SortFilterMenuItem+View.swift | 464 ++++++++++++++++ .../Views/SortFilter/SortFilterStyle.swift | 26 + .../_SortFilterCFGItemContainer.swift | 173 ++++++ .../_SortFilterMenuItemContainer.swift | 136 +++++ .../Views/SwitchPicker+View.swift | 153 ++++++ .../Component+Protocols.generated.swift | 50 +- ...mponentProtocols+Extension.generated.swift | 56 +- .../EnvironmentKey+Styles.generated.swift | 28 +- .../EnvironmentValue+Styles.generated.swift | 72 ++- .../API/OptionChip+API.generated.swift | 63 +++ .../API/OptionListPicker+API.generated.swift | 23 + .../API/SliderPicker+API.generated.swift | 26 + .../API/SortFilterFullCFG+API.generated.swift | 116 ++++ .../API/SortFilterMenu+API.generated.swift | 47 ++ .../SortFilterMenuItem+API.generated.swift | 82 +++ .../API/SwitchPicker+API.generated.swift | 22 + .../OptionChip+View.generated.swift | 62 +++ .../OptionListPicker+View.generated.swift | 34 ++ .../SliderPicker+View.generated.swift | 34 ++ .../SortFilterFullCFG+View.generated.swift | 74 +++ .../SortFilterMenu+View.generated.swift | 58 ++ .../SortFilterMenuItem+View.generated.swift | 66 +++ .../SwitchPicker+View.generated.swift | 34 ++ .../OptionChip+Init.generated.swift | 16 + .../OptionListPicker+Init.generated.swift | 3 + .../SliderPicker+Init.generated.swift | 3 + .../SortFilterFullCFG+Init.generated.swift | 131 +++++ .../SortFilterMenu+Init.generated.swift | 3 + .../SortFilterMenuItem+Init.generated.swift | 47 ++ .../SwitchPicker+Init.generated.swift | 3 + ...ListPickerModel+Extensions.generated.swift | 9 + ...terFullCFGModel+Extensions.generated.swift | 21 + ...FilterMenuModel+Extensions.generated.swift | 9 + .../en.lproj/FioriSwiftUICore.strings | 3 + 57 files changed, 3864 insertions(+), 20 deletions(-) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift create mode 100644 Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json create mode 100644 Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png create mode 100644 Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift create mode 100644 Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift create mode 100644 Sources/FioriSwiftUICore/Views/OptionChip+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SliderPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift create mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift create mode 100644 Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 7f18def4a..22c0a0904 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -125,6 +125,8 @@ B8D4376F25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */; }; B8D4377125F983730024EE7D /* ObjectCell_Rules_Alignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */; }; B8D437732609479E0024EE7D /* SingleActionFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */; }; + C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A0FDB22AD893FA0001738E /* View+Extensions.swift */; }; + C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -307,6 +309,8 @@ B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Spec_Jan2018.swift; sourceTree = ""; }; B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Rules_Alignment.swift; sourceTree = ""; }; B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleActionFollowButton.swift; sourceTree = ""; }; + C1A0FDB22AD893FA0001738E /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; + C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortFilterExample.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -501,6 +505,7 @@ 8A5579C824C1293C0098003A /* FioriSwiftUICore */ = { isa = PBXGroup; children = ( + C1C764862A818BD600BCB0F7 /* SortFilter */, B100639129C0623300AF0CA2 /* StepProgressIndicator */, 108E43D3292DAB3E006532F3 /* EmptyStateView */, B1D41B1E291A2D2E004E64A5 /* Picker */, @@ -700,6 +705,15 @@ path = ObjectItem; sourceTree = ""; }; + C1C764862A818BD600BCB0F7 /* SortFilter */ = { + isa = PBXGroup; + children = ( + C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */, + C1A0FDB22AD893FA0001738E /* View+Extensions.swift */, + ); + path = SortFilter; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -889,6 +903,7 @@ 8A557A2424C12F380098003A /* ChartDetailView.swift in Sources */, 8A5579D024C1293C0098003A /* SettingsLine.swift in Sources */, 1FC30412270540FB004BEE00 /* 72-Fonts.swift in Sources */, + C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */, B84D24ED2652F343007F2373 /* HeaderChartExample.swift in Sources */, B100639329C0624D00AF0CA2 /* StepProgressIndicatorExample.swift in Sources */, B846F94626815CC90085044B /* ContactItemExample.swift in Sources */, @@ -937,6 +952,7 @@ 8A5579D524C1293C0098003A /* SettingsSeries.swift in Sources */, 8A557A2224C12C9B0098003A /* CoreContentView.swift in Sources */, 8A5579D224C1293C0098003A /* Color+Extensions.swift in Sources */, + C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */, B84D24EF2652F343007F2373 /* ObjectHeaderTestApp.swift in Sources */, B84D24EC2652F343007F2373 /* ObjectHeaderSpecCompact.swift in Sources */, 8A5579CD24C1293C0098003A /* SettingsLabel.swift in Sources */, @@ -1116,7 +1132,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1171,7 +1187,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -1188,10 +1204,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"Examples/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = H8QB58T8ZU; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Examples/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1210,10 +1226,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"Examples/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = H8QB58T8ZU; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Examples/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1232,7 +1248,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = ExamplesTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1253,7 +1269,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = ExamplesTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift index fcf70c39e..91545232c 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift @@ -107,6 +107,10 @@ struct CoreContentView: View { destination: EmptyStateViewExample()) { Text("EmptyStateViewExample") } + + NavigationLink(destination: SortFilterExample()) { + Text("SortFilterExample") + } } }.navigationBarTitle("FioriSwiftUICore") } diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift new file mode 100644 index 000000000..2ba825e21 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -0,0 +1,101 @@ +import FioriSwiftUICore +import SwiftUI + +struct SortFilterExample: View { + @State private var items: [[SortFilterItem]] = [[ + .picker(item: .init(value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], name: "JIRA Status", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), isShownOnMenu: true), + .picker(item: .init(value: [0], valueOptions: ["High", "Medium", "Low"], name: "Priority", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), isShownOnMenu: true), + .filterfeedback(item: .init(value: [0], valueOptions: ["Ascending", "Descending"], name: "Sort Order", allowsMultipleSelection: false, allowsEmptySelection: false, icon: "checkmark")), + .slider(item: .init(value: 10, minimumValue: 0, maximumValue: 100, name: "User Stories", formatter: "%2d Stories", icon: "number"), isShownOnMenu: true), + .slider(item: .init(value: nil, minimumValue: 0, maximumValue: 100, name: "Number of Tasks"), isShownOnMenu: true), + .datetime(item: .init(value: Date(), name: "Start Date", formatter: "yyyy-MM-dd HH:mm",icon: "calendar"), isShownOnMenu: true), + .datetime(item: .init(value: nil, name: "Completion Date"), isShownOnMenu: true), + .switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), isShownOnMenu: true), + .switch(item: .init(name: "Tagged", value: nil, icon: "tag"), isShownOnMenu: false) + ]] + + @State private var isShowingFullCFG: Bool = false + @State private var isCustomStyle: Bool = false + @State private var sortFilterList: [String] = [] + @State private var sortFilterButtonLabel: String = "Sort & Filter" + + var body: some View { + VStack { + if isCustomStyle { + SortFilterMenu(items: $items, onUpdate: performSortAndFilter) + .sortFilterMenuItemStyle(font: .subheadline, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + // .trailingFullConfigurationMenuItem(icon: "command") + // .leadingFullConfigurationMenuItem(icon: "command") + // .leadingFullConfigurationMenuItem(name: "All") + } else { + SortFilterMenu(items: $items, onUpdate: performSortAndFilter) + } + + List { + ForEach(sortFilterList, id: \.self) { line in + Text(line) + } + } + .listStyle(PlainListStyle()) + + VStack { + Toggle("Custom Style", isOn: $isCustomStyle) + .toggleStyle(FioriToggleStyle()) + + Button("Print") { + for line in sortFilterList { + print(line) + } + } + } + } + .navigationTitle("Sort & Filter") + .toolbar { + Button(sortFilterButtonLabel) { + isShowingFullCFG.toggle() + } + .popover(isPresented: $isShowingFullCFG, arrowEdge: .leading) { + if isCustomStyle { + SortFilterFullCFG( + title: "Configuration", + items: $items, + onUpdate: performSortAndFilter + ) + .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + } else { + SortFilterFullCFG( + title: "Configuration", + items: $items, + onUpdate: performSortAndFilter + ) + } + } + } + .onAppear { + performSortAndFilter() + } + } + + func numberOfItems() -> Int { + // randomly padding result to mimic impact of filterring + for i in 0 ... Int.random(in: 0 ... 5) { + self.sortFilterList.append("Non-SortFilterCFG: element \(i + 1)") + } + return self.sortFilterList.count + } + + func performSortAndFilter() { + self.sortFilterList = self.items.joined().map { value(of: $0) } + self.sortFilterButtonLabel = "Sort & Filter (\(self.numberOfItems()))" + } +} + +#if DEBUG + @available(iOS 16.0, *) + struct SortFilterExample_Previews: PreviewProvider { + static var previews: some View { + SortFilterExample() + } + } +#endif diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift new file mode 100644 index 000000000..10686c555 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift @@ -0,0 +1,35 @@ +import FioriSwiftUICore +import SwiftUI + +extension View { + func value(of item: SortFilterItem) -> String { + switch item { + case .picker(let v, _): + return self.json(item: v) + case .filterfeedback(let v): + return self.json(item: v) + case .slider(let v, _): + return self.json(item: v) + case .datetime(let v, _): + return self.json(item: v) + case .switch(let v, _): + return self.json(item: v) + } + } + + func json(item: PickerItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(item.value)}" + } + + func json(item: SliderItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } + + func json(item: DateTimeItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } + + func json(item: SwitchItem) -> String { + "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + } +} diff --git a/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json new file mode 100644 index 000000000..2945b36b9 --- /dev/null +++ b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png b/Apps/Examples/Examples/Preview Content/Preview Assets.xcassets/iconfromapp.imageset/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d91dd8e60c39253ab9753e4f1c0d137b4e06cbdb GIT binary patch literal 934 zcmV;X16lluP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR916rckD1ONa40RR916aWAK0J7ci{Qv+15J^NqR5%fpR7q=8K@k3`Ur#1# zP;(em)aY$okQ~I5H_?+4m-xnrzd%9sqT&YQilPTWe}v{G2K5p*@ZwD`t{6dF2^w)t z$YkE@F003;K0+Enn5^>MDW|pX^!4#1{#?F+J;N3Pft7AI1RK?4cu<-%$vqhUJOcTYeE!SPz4x-MpalaBV?OT2%N_V%~|lHDf<#OMsmekqr?EP$(w zd!|lVE4<@zm024s!|+I37O25D1C}Dsr?)Rb)pnxA-ayV2y)`>c>-S?O|KoT*FuRaz zkphwE@+;C}iBAJe_hvwqxI~Kuj8pYnoII3*5XK5Yx>O!l9;!|Z|MnGirpj{al`5B_ z7kqB>S9zP=vrX=m?6+X62R*ZvauB)PDtNaTEy*?7H}ENan=ds|jV6uq5p84g?w-)$ z=r)`kN|^3?m^*OQp;Siv95V}}4Up{KoMBxvXRJalpn}}d8V^^r(`@?GeI>UwJ`u{} zrgnTwn!Bd%J1HQQqftKFbysbS%Rj~hcOr4dX*yZ+j;d2OYXHl66k)q|c@GeplM2eVo?S6!>H~AIb7zEPL8C!x}yO*Kl~YoME5N zW0-$jCS+1&~()d5RhhJY&n zhtdI?g6V+WYo?d4z9%;8ka!v65>N;26qnkjQ{K5g#UR1@1r+>sK#p6*NdN!<07*qo IM6N<$g1&*0mH+?% literal 0 HcmV?d00001 diff --git a/Package.swift b/Package.swift index 0b4374da2..48162e04b 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "FioriSwiftUI", defaultLocalization: "en", - platforms: [.iOS(.v15), .watchOS(.v7)], + platforms: [.iOS(.v16), .watchOS(.v7)], products: [ .library( name: "FioriSwiftUI", diff --git a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift new file mode 100644 index 000000000..114413063 --- /dev/null +++ b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift @@ -0,0 +1,92 @@ +import Foundation +import SwiftUI + +struct CancellableResettableDialogForm: View { + let title: Title + + let components: Components + + var cancelAction: CancelAction + var resetAction: ResetAction + var applyAction: ApplyAction + + public init(@ViewBuilder title: () -> Title, + @ViewBuilder cancelAction: () -> CancelAction, + @ViewBuilder resetAction: () -> ResetAction, + @ViewBuilder applyAction: () -> ApplyAction, + @ViewBuilder components: () -> Components) + { + self.title = title() + self.cancelAction = cancelAction() + self.resetAction = resetAction() + self.applyAction = applyAction() + self.components = components() + } + + var body: some View { + VStack(spacing: UIDevice.current.userInterfaceIdiom == .pad ? 8 : 16) { + HStack { + cancelAction + Spacer() + title + Spacer() + resetAction + } + components + applyAction + } + .frame(minWidth: 375) + .padding(UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) + } +} + +struct ApplyButtonStyle: PrimitiveButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) + .padding(15) + .font(.body) + .fontWeight(.bold) + .foregroundStyle(Color.preferredColor(.base2)) + .background(RoundedRectangle(cornerRadius: 8).fill(Color.preferredColor(.tintColor))) + .onTapGesture { + configuration.trigger() + } + } +} + +struct CancelResetButtonStyle: PrimitiveButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.body) + .fontWeight(.bold) + .foregroundStyle(Color.preferredColor(.tintColor)) + .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) + .onTapGesture { + configuration.trigger() + } + } +} + +#Preview { + VStack { + Spacer() + CancellableResettableDialogForm { + Text("Date of Completion") + } cancelAction: { + Action(actionText: "Cancel", didSelectAction: nil) + } resetAction: { + Action(actionText: "Reset", didSelectAction: nil) + } applyAction: { + Action(actionText: "Apply", didSelectAction: nil) + .buttonStyle(ApplyButtonStyle()) + } components: { + DatePicker( + "date", + selection: Binding(get: { Date() }, set: { print($0) }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + } +} diff --git a/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift b/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift index 14215991d..43d0199bc 100644 --- a/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift +++ b/Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift @@ -53,3 +53,51 @@ internal protocol _DurationPicker: _ComponentMultiPropGenerating, AnyObject { // sourcery: default.value = MeasurementFormatter() var measurementFormatter: MeasurementFormatter { get set } } + +internal protocol _SliderPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Int? { get set } + + // sourcery: default.value = nil + var formatter: String? { get } + + // sourcery: no_view + // sourcery: default.value = 0.0 + var minimumValue: Int { get } + + // sourcery: no_view + // sourcery: default.value = 100.0 + var maximumValue: Int { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} + +internal protocol _SwitchPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Bool? { get set } + + // sourcery: no_view + // sourcery: default.value = nil + var name: String? { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} + +internal protocol _OptionListPicker: _ComponentMultiPropGenerating, AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: [Int] { get set } + + // sourcery: no_view + var valueOptions: [String] { get } + + // sourcery: no_view + // sourcery: default.value = nil + var hint: String? { get } +} diff --git a/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift b/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift index e9731f208..2020a4b4b 100644 --- a/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift +++ b/Sources/FioriSwiftUICore/Components/SinglePropertyComponents.swift @@ -51,4 +51,6 @@ internal struct _Component: _ComponentGenerating { // sourcery: backingComponent=FootnoteIconStack // sourcery: customFunctionBuilder=FootnoteIconsBuilder let footnoteIcons_: [TextOrIcon]? + let leftIcon_: Image? + let rightIcon_: Image? } diff --git a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift new file mode 100644 index 000000000..c49806354 --- /dev/null +++ b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift @@ -0,0 +1,519 @@ +import SwiftUI +import UIKit + +public enum SortFilterItem: Identifiable, Hashable { + public var id: String { + switch self { + case .picker(let item, _): + return item.id + case .filterfeedback(let item): + return item.id + case .switch(let item, _): + return item.id + case .slider(let item, _): + return item.id + case .datetime(let item, _): + return item.id + } + } + + case picker(item: PickerItem, isShownOnMenu: Bool) + case filterfeedback(item: PickerItem) + case `switch`(item: SwitchItem, isShownOnMenu: Bool) + case slider(item: SliderItem, isShownOnMenu: Bool) + case datetime(item: DateTimeItem, isShownOnMenu: Bool) + + public var isShownOnMenu: Bool { + switch self { + case .picker(_, let isShownOnMenu): + return isShownOnMenu + case .filterfeedback: + return true + case .switch(_, let isShownOnMenu): + return isShownOnMenu + case .slider(_, let isShownOnMenu): + return isShownOnMenu + case .datetime(_, let isShownOnMenu): + return isShownOnMenu + } + } + + public func hash(into hasher: inout Hasher) { + switch self { + case .picker(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .filterfeedback(let item): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .switch(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .slider(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + case .datetime(let item, _): + hasher.combine(item.id) + hasher.combine(item.originalValue) + hasher.combine(item.workingValue) + hasher.combine(item.value) + } + } +} + +/// (value: [Int], valueOptions: [String], keyName: String?, allowsMultipleSelection: Bool, allowsEmptySelection: Bool) +public struct PickerItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + + public var value: [Int] + public var workingValue: [Int] + let originalValue: [Int] + + var valueOptions: [String] + public let allowsMultipleSelection: Bool + public let allowsEmptySelection: Bool + public let icon: String? + + public init(value: [Int], valueOptions: [String], name: String, allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.valueOptions = valueOptions + self.name = name + self.allowsMultipleSelection = allowsMultipleSelection + self.allowsEmptySelection = allowsEmptySelection + self.icon = icon + } + + mutating func onTap(option: String) { + guard let index = valueOptions.firstIndex(of: option) else { return } + if self.workingValue.contains(index) { + if self.workingValue.count > 1 { + self.workingValue = self.workingValue.filter { $0 != index } + } else { + if self.allowsEmptySelection { + self.workingValue = [] + } else { + self.workingValue = index == 1 ? [0] : [1] + } + } + } else { + if self.allowsMultipleSelection { + self.workingValue.append(index) + } else { + self.workingValue = [index] + } + } + } + + mutating func optionOnTap(_ index: Int) { + if self.workingValue.contains(index) { + if self.workingValue.count > 1 { + self.workingValue = self.workingValue.filter { $0 != index } + } else { + if self.allowsEmptySelection { + self.workingValue = [] + } else { + self.workingValue = index == 1 ? [0] : [1] + } + } + } else { + if self.allowsMultipleSelection { + self.workingValue.append(index) + } else { + self.workingValue = [index] + } + } + } + + mutating func cancel() { + self.workingValue = self.value.map { $0 } + } + + mutating func reset() { + self.workingValue = self.originalValue.map { $0 } + } + + mutating func apply() { + self.value = self.workingValue.map { $0 } + } + + func isOptionSelected(_ option: String) -> Bool { + guard let idx = valueOptions.firstIndex(of: option) else { return false } + return self.workingValue.contains(idx) + } + + func isOptionSelected(index: Int) -> Bool { + self.workingValue.contains(index) + } + + var isChecked: Bool { + !self.value.isEmpty + } + + var label: String { + if allowsMultipleSelection && self.value.count >= 1 { + if self.value.count == 1 { + return valueOptions[value[0]] + } else { + return "\(self.name) (\(self.value.count))" + } + } else { + return self.name + } + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +/// (value: Bool, keyName: String) +public struct SwitchItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + public var value: Bool? + var workingValue: Bool? + let originalValue: Bool? + public let icon: String? + public let hint: String? + + public init(id: String = UUID().uuidString, name: String, value: Bool?, icon: String? = nil, hint: String? = nil) { + self.id = id + self.name = name + self.value = value + self.workingValue = value + self.originalValue = value + self.icon = icon + self.hint = hint + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + mutating func apply() { + self.value = self.workingValue + } + + var isChecked: Bool { + self.value ?? false + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +/// (value: Float, minimumValue: Float, maximumValue: Float, keyName: String?) +public struct SliderItem: Identifiable, Equatable { + public var id = UUID().uuidString + + public var name: String + + public var value: Int? + var workingValue: Int? + let originalValue: Int? + public let minimumValue: Int + public let maximumValue: Int + let formatter: String? + public let icon: String? + public let hint: String? + + public init(value: Int? = nil, minimumValue: Int, maximumValue: Int, name: String, formatter: String? = nil, icon: String? = nil, hint: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.minimumValue = minimumValue + self.maximumValue = maximumValue + self.name = name + self.formatter = formatter + self.icon = icon + self.hint = hint + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + mutating func apply() { + self.value = self.workingValue + } + + var isChecked: Bool { + self.value != nil + } + + var label: String { + if let formatter = formatter, let value = value { + return String(format: formatter, value) + } + return name + } + + mutating func setValue(newValue: SliderItem) { + self.value = newValue.value + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +public struct DateTimeItem: Equatable, Hashable { + public let id = UUID().uuidString + + public var name: String + public var value: Date? + var workingValue: Date? + let originalValue: Date? + public var icon: String? + public let formatter: String? + + public init(value: Date?, name: String, formatter: String? = nil, icon: String? = nil) { + self.value = value + self.workingValue = value + self.originalValue = value + self.name = name + self.formatter = formatter + self.icon = icon + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func apply() { + self.value = self.workingValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + var isChecked: Bool { + self.value != nil + } + + var label: String { + if let value = self.value { + if let format = self.formatter { + let formatter = DateFormatter() + formatter.dateFormat = format + return formatter.string(from: value) + } else { + let dateFormatter: DateFormatter = DateFormatter() + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .short + return dateFormatter.string(from: value) + } + } else { + return self.name + } + } + + var isChanged: Bool { + self.value != self.workingValue + } +} + +extension SortFilterItem { + var picker: PickerItem { + get { + switch self { + case .picker(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .picker(_, let isShownOnMenu): + self = .picker(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var filterfeedback: PickerItem { + get { + switch self { + case .filterfeedback(let item): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .filterfeedback: + self = .filterfeedback(item: newValue) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var slider: SliderItem { + get { + switch self { + case .slider(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .slider(_, let isShownOnMenu): + self = .slider(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var datetime: DateTimeItem { + get { + switch self { + case .datetime(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .datetime(_, let isShownOnMenu): + self = .datetime(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + var `switch`: SwitchItem { + get { + switch self { + case .switch(let item, _): + return item + default: + fatalError("Unexpected value \(self)") + } + } + + set { + switch self { + case .switch(_, let isShownOnMenu): + self = .switch(item: newValue, isShownOnMenu: isShownOnMenu) + default: + fatalError("Unexpected value \(self)") + } + } + } + + public var isChanged: Bool { + switch self { + case .picker(let item, _): + return item.isChanged + case .filterfeedback(let item): + return item.isChanged + case .switch(let item, _): + return item.isChanged + case .datetime(let item, _): + return item.isChanged + case .slider(let item, _): + return item.isChanged + } + } + + public mutating func cancel() { + switch self { + case .picker(var item, _): + item.cancel() + self.picker = item + case .filterfeedback(var item): + item.cancel() + self.filterfeedback = item + case .switch(var item, _): + item.cancel() + self.switch = item + case .datetime(var item, _): + item.cancel() + self.datetime = item + case .slider(var item, _): + item.cancel() + self.slider = item + } + } + + public mutating func reset() { + switch self { + case .picker(var item, _): + item.reset() + self.picker = item + case .filterfeedback(var item): + item.reset() + self.filterfeedback = item + case .switch(var item, _): + item.reset() + self.switch = item + case .datetime(var item, _): + item.reset() + self.datetime = item + case .slider(var item, _): + item.reset() + self.slider = item + } + } + + public mutating func apply() { + switch self { + case .picker(var item, _): + item.apply() + self.picker = item + case .filterfeedback(var item): + item.apply() + self.filterfeedback = item + case .switch(var item, _): + item.apply() + self.switch = item + case .datetime(var item, _): + item.apply() + self.datetime = item + case .slider(var item, _): + item.apply() + self.slider = item + } + } +} + +/* + Notes: + c. to resolve: keyName should not be nillable for menu item, but it can be nil for sheet + */ diff --git a/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift b/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift index 420d1ad11..7c615eecb 100644 --- a/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift +++ b/Sources/FioriSwiftUICore/Models/DefaultViewModels.swift @@ -24,6 +24,22 @@ public struct _CancelActionDefault: ActionModel { public init() {} } +public struct _ResetActionDefault: ActionModel { + public var actionText: String? { + NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") + } + + public init() {} +} + +public struct _ApplyActionDefault: ActionModel { + public var actionText: String? { + NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") + } + + public init() {} +} + public struct _AgreeActionDefault: ActionModel { public var actionText: String? { NSLocalizedString("Agree", tableName: "FioriSwiftUICore", bundle: fioriSwiftUICoreBundle, comment: "") diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index 41941e396..b625bb3f0 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -433,3 +433,71 @@ public protocol StepProgressIndicatorModel: AnyObject { // sourcery: default.value = _CancelActionDefault() var cancelAction: ActionModel? { get } } + +// sourcery: generated_component_composite +public protocol SortFilterMenuModel: AnyObject { + // sourcery: bindingProperty + // sourcery: backingComponent=_SortFilterMenuItemContainer + var items: [[SortFilterItem]] { get set } + + // sourcery: default.value = nil + // sourcery: no_view + var onUpdate: (() -> Void)? { get set } +} + +// sourcery: virtualPropActionHelper = "@State var context: SortFilterContext = SortFilterContext()" +// sourcery: add_env_props = "dismiss" +// sourcery: generated_component_composite +public protocol SortFilterFullCFGModel: AnyObject, TitleComponent { + // sourcery: bindingProperty + // sourcery: backingComponent=_SortFilterCFGItemContainer + var items: [[SortFilterItem]] { get set } + + // sourcery: genericParameter.name = CancelActionView + // sourcery: default.value = _CancelActionDefault() + var cancelAction: ActionModel? { get } + + // sourcery: genericParameter.name = ResetActionView + // sourcery: default.value = _ResetActionDefault() + var resetAction: ActionModel? { get } + + // sourcery: genericParameter.name = ApplyActionView + // sourcery: default.value = _ApplyActionDefault() + var applyAction: ActionModel? { get } + + // sourcery: default.value = nil + // sourcery: no_view + var onUpdate: (() -> Void)? { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: virtualPropActionHelper = "@State var context: SortFilterContext = SortFilterContext()" +// sourcery: generated_component_composite +public protocol SortFilterMenuItemModel: LeftIconComponent, TitleComponent, RightIconComponent { + // sourcery: no_view + var isSelected: Bool { get } +} + +// sourcery: add_env_props = "optionChipStyle" +// sourcery: generated_component_composite +public protocol OptionChipModel: LeftIconComponent, TitleComponent { + // sourcery: no_view + var isSelected: Bool { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +public protocol OptionListPickerModel: OptionListPickerComponent { + // sourcery: default.value = nil + // sourcery: no_view + var onTap: ((_ index: Int) -> Void)? { get } +} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +// sourcery: add_env_props = "fioriToggleStyle" +public protocol SwitchPickerModel: SwitchPickerComponent {} + +// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: generated_component_not_configurable +public protocol SliderPickerModel: SliderPickerComponent {} diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift new file mode 100644 index 000000000..82bcc02e9 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -0,0 +1,182 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum OptionChip { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + } +} + +extension OptionChip: View { + public var body: some View { + optionChipStyle.makeBody(configuration: OptionChipConfiguration(leftIcon: AnyView(leftIcon), title: AnyView(title), isSelected: _isSelected)) + } +} + +/* + // FIXME: - Implement OptionChip specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct OptionChipLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionChip(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +public struct OptionChipConfiguration { + let leftIcon: AnyView + let title: AnyView + let isSelected: Bool + + public init(leftIcon: AnyView, title: AnyView, isSelected: Bool) { + self.leftIcon = leftIcon + self.title = title + self.isSelected = isSelected + } +} + +public protocol OptionChipStyle { + associatedtype Body = View + + typealias Configuration = OptionChipConfiguration + + func makeBody(configuration: Self.Configuration) -> AnyView // Self.Body +} + +public struct DefaultOptionChipStyle: OptionChipStyle { + let font: Font + let foregroundColorSelected: Color + let foregroundColorUnselected: Color + let fillColorSelected: Color + let fillColorUnselected: Color + let strokeColorSelected: Color + let strokeColorUnselected: Color + let cornerRadius: CGFloat + let spacing: CGFloat + let padding: CGFloat + let borderWidth: CGFloat + let minHeight: CGFloat + + public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) { + self.font = font + self.foregroundColorSelected = foregroundColorSelected + self.foregroundColorUnselected = foregroundColorUnselected + self.fillColorSelected = fillColorSelected + self.fillColorUnselected = fillColorUnselected + self.strokeColorSelected = strokeColorSelected + self.strokeColorUnselected = strokeColorUnselected + self.cornerRadius = cornerRadius + self.spacing = spacing + self.padding = padding + self.borderWidth = borderWidth + self.minHeight = minHeight + } + + public func makeBody(configuration: Configuration) -> AnyView { + AnyView( + HStack(spacing: self.spacing) { + configuration.leftIcon + configuration.title + } + .font(self.font) + .foregroundColor(configuration.isSelected ? self.foregroundColorSelected : self.foregroundColorUnselected) + .padding(self.padding) + .frame(maxWidth: .infinity) + .background( + ZStack { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(configuration.isSelected ? fillColorSelected : fillColorUnselected) + RoundedRectangle(cornerRadius: cornerRadius) + .stroke(configuration.isSelected ? strokeColorSelected : strokeColorUnselected, lineWidth: borderWidth) + } + ) + ) + } +} + +struct OptionChipStyleKey: EnvironmentKey { + static var defaultValue: any OptionChipStyle = DefaultOptionChipStyle() +} + +extension EnvironmentValues { + var optionChipStyle: any OptionChipStyle { + get { + self[OptionChipStyleKey.self] + } + set { + self[OptionChipStyleKey.self] = newValue + } + } +} + +public extension View { + func optionChipStyle(_ style: S) -> some View where S: OptionChipStyle { + self.environment(\.optionChipStyle, style) + } + + func optionChipStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { + self.environment(\.optionChipStyle, + DefaultOptionChipStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) + } +} + +#Preview { + VStack { + Spacer() + + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: true) + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: false) + OptionChip(title: "Ship", isSelected: true) + OptionChip(title: "Ship", isSelected: false) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) + + Spacer() + + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: true) + .optionChipStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: false) + .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + + .optionChipStyle(cornerRadius: 16) + OptionChip(title: "Ship", isSelected: true) + .optionChipStyle(fillColorSelected: .yellow) + OptionChip(title: "Ship", isSelected: false) + .optionChipStyle(fillColorUnselected: .gray) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) + .optionChipStyle(cornerRadius: 20) + OptionChip(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) + .optionChipStyle(cornerRadius: 20) + + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift new file mode 100644 index 000000000..f46c06e72 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift @@ -0,0 +1,61 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +import SwiftUI + +extension OptionListPicker: View { + public var body: some View { + Grid { + ForEach(0 ..< Int(ceil(Double(_valueOptions.count) / 2.0))) { rowIndex in + GridRow { + OptionChip( + leftIcon: _value.wrappedValue.contains(rowIndex * 2) ? Image(systemName: "checkmark") : nil, + title: _valueOptions[rowIndex * 2], + isSelected: _value.wrappedValue.contains(rowIndex * 2) + ) + .onTapGesture { + _onTap?(rowIndex * 2) + } + if rowIndex * 2 + 1 < _valueOptions.count { + OptionChip( + leftIcon: _value.wrappedValue.contains(rowIndex * 2 + 1) ? Image(systemName: "checkmark") : nil, + title: _valueOptions[rowIndex * 2 + 1], + isSelected: _value.wrappedValue.contains(rowIndex * 2 + 1) + ) + .onTapGesture { + _onTap?(rowIndex * 2 + 1) + } + } + } + } + } + } +} + +/* + // FIXME: - Implement OptionListPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct OptionListPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionListPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +#Preview { + VStack { + Spacer() + OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + .frame(width: 375) + Spacer() + OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + .optionChipStyle(font: .title, foregroundColorSelected: Color.red, strokeColorSelected: Color.red, cornerRadius: 25) + .frame(width: 375) + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift new file mode 100644 index 000000000..f1578c7af --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift @@ -0,0 +1,191 @@ +import SwiftUI + +extension SliderPicker: View { + public var body: some View { + VStack { + if let formatter = self._formatter { + HStack { + Text(String(format: formatter, _value.wrappedValue ?? _minimumValue)) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + } + HStack { + Slider(value: .convert(from: _value, ifNilUse: _minimumValue), in: Float(_minimumValue) ... Float(_maximumValue), step: 1.0) + TextField("", value: Binding(get: { _value.wrappedValue ?? _minimumValue }, set: { _value.wrappedValue = $0 }), format: .number) + .frame(width: calcWidth(font: .body)) + .keyboardType(.numberPad) + .textFieldStyle(.roundedBorder) + } + if let hint = _hint { + HStack { + Text(hint) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + } + } + } +} + +private extension SliderPicker { + func calcWidth(font: Font) -> CGFloat { + var width: CGFloat = 0 + for i in 0 ... 9 { + let fontAttributes = [NSAttributedString.Key.font: self.preferredFont(from: font)] + let size = String(i).size(withAttributes: fontAttributes) + width = max(size.width, width) + } + return floor(log10(CGFloat(_maximumValue)) + 1) * width + 2 * 12 + } + + func preferredFont(from font: Font) -> UIFont { + let uiFont: UIFont + + switch font { + case .largeTitle: + uiFont = UIFont.preferredFont(forTextStyle: .largeTitle) + case .title: + uiFont = UIFont.preferredFont(forTextStyle: .title1) + case .title2: + uiFont = UIFont.preferredFont(forTextStyle: .title2) + case .title3: + uiFont = UIFont.preferredFont(forTextStyle: .title3) + case .headline: + uiFont = UIFont.preferredFont(forTextStyle: .headline) + case .subheadline: + uiFont = UIFont.preferredFont(forTextStyle: .subheadline) + case .callout: + uiFont = UIFont.preferredFont(forTextStyle: .callout) + case .caption: + uiFont = UIFont.preferredFont(forTextStyle: .caption1) + case .caption2: + uiFont = UIFont.preferredFont(forTextStyle: .caption2) + case .footnote: + uiFont = UIFont.preferredFont(forTextStyle: .footnote) + case .body: + fallthrough + default: + uiFont = UIFont.preferredFont(forTextStyle: .body) + } + + return uiFont + } +} + +struct RangeIntegerStyle: ParseableFormatStyle { + var parseStrategy: RangeIntegerStrategy = .init() + let range: ClosedRange + + func format(_ value: Int) -> String { + let constrainedValue = min(max(value, range.lowerBound), self.range.upperBound) + return "\(constrainedValue)" + } +} + +struct RangeIntegerStrategy: ParseStrategy { + func parse(_ value: String) throws -> Int { + Int(value) ?? 1 + } +} + +extension FormatStyle where Self == RangeIntegerStyle { + static func ranged(_ range: ClosedRange) -> RangeIntegerStyle { + RangeIntegerStyle(range: range) + } +} + +extension Binding { + static func convert(from intBinding: Binding, ifNilUse defaultValue: TInt) -> Binding where TInt: BinaryInteger, TFloat: BinaryFloatingPoint { + Binding( + get: { + TFloat(intBinding.wrappedValue ?? defaultValue) + }, + set: { + intBinding.wrappedValue = TInt($0) + } + ) + } +} + +/* + // FIXME: - Implement SliderPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SliderPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SliderPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private struct SliderPickeTestView: View { + @State var value1: Int? = 10 + @State var value2: Int? = 20 + @State var value3: Int? = nil + + var body: some View { + VStack { + Spacer() + HStack { + Text("Value 1: \($value1.wrappedValue ?? 0)") + .font(.largeTitle) + .foregroundColor(value1 != nil ? .blue : .gray) + Spacer() + } + SliderPicker(value: Binding( + get: { + value1 + }, + set: { + value1 = $0 + } + ), minimumValue: 0, maximumValue: 1000, hint: nil) + Spacer() + HStack { + Text("Value 2: \(value2 ?? 0)") + .font(.largeTitle) + .foregroundColor(value2 != nil ? .blue : .gray) + + Spacer() + } + + SliderPicker(value: Binding( + get: { + value2 + }, + set: { + value2 = $0 + } + ), minimumValue: 0, maximumValue: 100, hint: "Pick an integer value") + Spacer() + HStack { + Text("Value 3: \(value3 ?? 0)") + .font(.largeTitle) + .foregroundColor(value3 != nil ? .blue : .gray) + + Spacer() + } + SliderPicker(value: Binding( + get: { + value3 + }, + set: { + value3 = $0 + } + ), minimumValue: 0, maximumValue: 100, hint: "Pick an integer value") + Spacer() + } + .frame(width: 375) + } +} + +struct SliderPickeTestView_Previews: PreviewProvider { + static var previews: some View { + SliderPickeTestView() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift new file mode 100644 index 000000000..054e54dcf --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilter+Environment.swift @@ -0,0 +1,23 @@ +import SwiftUI + +struct SortFilterOnModelUpdateAppCallbackKey: EnvironmentKey { + static let defaultValue: () -> Void = { print("default empty callback") } +} + +extension EnvironmentValues { + var onModelUpdateAppCallback: () -> Void { + get { + self[SortFilterOnModelUpdateAppCallbackKey.self] + } + + set { + self[SortFilterOnModelUpdateAppCallbackKey.self] = newValue + } + } +} + +extension View { + func onModelUpdateAppCallback(_ closure: @escaping () -> Void) -> some View { + self.environment(\.onModelUpdateAppCallback, closure) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift new file mode 100644 index 000000000..c87a7c066 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift @@ -0,0 +1,14 @@ +import SwiftUI + +class SortFilterContext: ObservableObject { + @Published public var isResetButtonEnabled: Bool = false + @Published public var isApplyButtonEnabled: Bool = false + + @Published public var handleCancel: (() -> Void)? + @Published public var handleReset: (() -> Void)? + @Published public var handleApply: (() -> Void)? + + @Published public var handleDismiss: (() -> Void)? + + public init() {} +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift new file mode 100644 index 000000000..cc4939d47 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift @@ -0,0 +1,63 @@ +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* + import SwiftUI + + // FIXME: - Implement Fiori style definitions + + extension Fiori { + enum SortFilterDialog { + typealias Item = EmptyModifier + typealias ItemCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let item = Item() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let itemCumulative = ItemCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } + } + + // FIXME: - Implement SortFilterDialog View body + + extension SortFilterDialog: View { + public var body: some View { + <# View body #> + } + } + + // FIXME: - Implement SortFilterDialog specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterDialogLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterDialog(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift new file mode 100644 index 000000000..908206645 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift @@ -0,0 +1,100 @@ +import SwiftUI + +extension Fiori { + enum SortFilterFullCFG { + struct Title: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/) + .foregroundStyle(Color.preferredColor(.primaryLabel)) + .multilineTextAlignment(.center) + } + } + typealias TitleCumulative = EmptyModifier + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + static let title = Title() + static let items = Items() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let titleCumulative = TitleCumulative() + static let itemsCumulative = ItemsCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } +} + +extension SortFilterFullCFG: View { + public var body: some View { + CancellableResettableDialogForm { + title + } cancelAction: { + cancelAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Cancel...") + context.handleCancel?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + dismiss() + } + ) + .buttonStyle(CancelResetButtonStyle()) + } resetAction: { + resetAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Reset...") + context.handleReset?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + dismiss() + } + ) + .buttonStyle(CancelResetButtonStyle()) + } applyAction: { + applyAction + .simultaneousGesture( + TapGesture() + .onEnded { _ in + print("...Apply...") + context.handleApply?() + context.isApplyButtonEnabled = false + context.isResetButtonEnabled = false + _onUpdate?() + dismiss() + } + ) + + .buttonStyle(ApplyButtonStyle()) + } components: { + _items + .environmentObject(context) + } + } +} + +/* + // FIXME: - Implement SortFilterFullCFG specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterFullCFGLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterFullCFG(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift new file mode 100644 index 000000000..965a2ca90 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift @@ -0,0 +1,29 @@ +// +// SortFilterItemTitle.swift +// +// +// Created by Xu, Charles on 10/24/23. +// + +import SwiftUI +import FioriThemeManager + +public struct SortFilterItemTitle: TitleComponent, View { + public let title: String + + public init(title: String) { + self.title = title + } + + public var body: some View { + Text(title) + .font(.body) + .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/) + .foregroundStyle(Color.preferredColor(.primaryLabel)) + .multilineTextAlignment(.center) + } +} + +#Preview { + SortFilterItemTitle(title: /*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift new file mode 100644 index 000000000..fb4afcfc2 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift @@ -0,0 +1,30 @@ +import SwiftUI + +extension Fiori { + enum SortFilterMenu { + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + + static let items = Items() + static let itemsCumulative = ItemsCumulative() + } +} + +extension SortFilterMenu: View { + public var body: some View { + items + .onModelUpdateAppCallback(_onUpdate!) + } +} + +/* + // FIXME: - Implement SortFilterMenu specific LibraryContentProvider + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterMenuLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenu(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift new file mode 100644 index 000000000..b843c7982 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift @@ -0,0 +1,102 @@ +import FioriThemeManager +import SwiftUI + +public struct SortFilterMenuItemConfiguration { + let leftIcon: AnyView + let title: AnyView + let isSelected: Bool + let rightIcon: AnyView + + public init(leftIcon: AnyView, title: AnyView, isSelected: Bool, rightIcon: AnyView) { + self.leftIcon = leftIcon + self.title = title + self.isSelected = isSelected + self.rightIcon = rightIcon + } +} + +public protocol SortFilterMenuItemStyle { + associatedtype Body = View + + typealias Configuration = SortFilterMenuItemConfiguration + + func makeBody(configuration: Self.Configuration) -> AnyView +} + +public struct DefaultSortFilterMenuItemStyle: SortFilterMenuItemStyle { + let font: Font + let foregroundColorSelected: Color + let foregroundColorUnselected: Color + let fillColorSelected: Color + let fillColorUnselected: Color + let strokeColorSelected: Color + let strokeColorUnselected: Color + let cornerRadius: CGFloat + let spacing: CGFloat + let padding: CGFloat + let borderWidth: CGFloat + let minHeight: CGFloat + + public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) { + self.font = font + self.foregroundColorSelected = foregroundColorSelected + self.foregroundColorUnselected = foregroundColorUnselected + self.fillColorSelected = fillColorSelected + self.fillColorUnselected = fillColorUnselected + self.strokeColorSelected = strokeColorSelected + self.strokeColorUnselected = strokeColorUnselected + self.cornerRadius = cornerRadius + self.spacing = spacing + self.padding = padding + self.borderWidth = borderWidth + self.minHeight = minHeight + } + + public func makeBody(configuration: Configuration) -> AnyView { + AnyView( + HStack(spacing: self.spacing) { + configuration.leftIcon + configuration.title + configuration.rightIcon + } + .font(self.font) + .foregroundColor(configuration.isSelected ? self.foregroundColorSelected : self.foregroundColorUnselected) + .padding(self.padding) + .frame(minHeight: self.minHeight) + .background( + ZStack { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(configuration.isSelected ? fillColorSelected : fillColorUnselected) + RoundedRectangle(cornerRadius: cornerRadius) + .stroke(configuration.isSelected ? strokeColorSelected : strokeColorUnselected, lineWidth: borderWidth) + } + ) + ) + } +} + +struct SortFilterMenuItemStyleKey: EnvironmentKey { + static var defaultValue: any SortFilterMenuItemStyle = DefaultSortFilterMenuItemStyle() +} + +extension EnvironmentValues { + var sortFilterMenuItemStyle: any SortFilterMenuItemStyle { + get { + self[SortFilterMenuItemStyleKey.self] + } + set { + self[SortFilterMenuItemStyleKey.self] = newValue + } + } +} + +public extension View { + func sortFilterMenuItemStyle(_ style: S) -> some View where S: SortFilterMenuItemStyle { + self.environment(\.sortFilterMenuItemStyle, style) + } + + func sortFilterMenuItemStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { + self.environment(\.sortFilterMenuItemStyle, + DefaultSortFilterMenuItemStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift new file mode 100644 index 000000000..2ba29d277 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -0,0 +1,464 @@ +import SwiftUI + +extension Fiori { + enum SortFilterMenuItem { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + typealias RightIcon = EmptyModifier + typealias RightIconCumulative = EmptyModifier + + static let leftIcon = LeftIcon() + static let title = Title() + static let rightIcon = RightIcon() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + static let rightIconCumulative = RightIconCumulative() + } +} + +extension SortFilterMenuItem: View { + public var body: some View { + sortFilterMenuItemStyle.makeBody(configuration: SortFilterMenuItemConfiguration(leftIcon: AnyView(_leftIcon), title: AnyView(_title), isSelected: _isSelected, rightIcon: AnyView(_rightIcon))).typeErased + } +} + +/* + // FIXME: - Implement SortFilterMenuItem specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SortFilterMenuItemLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenuItem(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private extension View { + func icon(name: String?, isVisible: Bool) -> Image? { + if isVisible { + if let name = name { + return Image(systemName: name) + } + } + return nil + } +} + +struct FilterFeedbackMenuItem: View { + @Binding var item: PickerItem + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + Group { + ForEach($item.valueOptions.wrappedValue, id: \.self) { opt in + SortFilterMenuItem(leftIcon: item.isOptionSelected(opt) ? icon(name: item.icon, isVisible: true) : nil, title: opt, isSelected: item.isOptionSelected(opt)) + .onTapGesture { + item.onTap(option: opt) + item.apply() + onUpdate() + } + } + } + } +} + +struct SliderMenuItem: View { + @Binding var item: SliderItem + + @State var isSheetVisible = false + + @State var detentHeight: CGFloat = 0 + + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + SortFilterItemTitle(title: item.name) + } cancelAction: { + Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + } resetAction: { + Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.reset() + }) + .buttonStyle(CancelResetButtonStyle()) + } applyAction: { + Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + + } components: { + SliderPicker(value: Binding(get: { item.workingValue }, set: { item.workingValue = $0 }), formatter: item.formatter, minimumValue: item.minimumValue, maximumValue: item.maximumValue) + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } +} + +struct PickerMenuItem: View { + @Binding var item: PickerItem + var onUpdate: () -> Void + + @State var isSheetVisible = false + + @State var detentHeight: CGFloat = 0 + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + if item.valueOptions.count > 4 { + button + } else { + menu + } + } + + @ViewBuilder + var button: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + SortFilterItemTitle(title: item.name) + } cancelAction: { + Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + } resetAction: { + Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.reset() + }) + .buttonStyle(CancelResetButtonStyle()) + } applyAction: { + Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + } components: { + OptionListPicker(value: $item.workingValue, valueOptions: item.valueOptions, hint: nil) { index in + item.onTap(option: item.valueOptions[index]) + } + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } + + @ViewBuilder + var menu: some View { + HStack { + Menu { + ForEach(item.valueOptions.indices) { idx in + if item.isOptionSelected(index: idx) { + Button { + item.onTap(option: item.valueOptions[idx]) + item.apply() + onUpdate() + } label: { + Label { Text(item.valueOptions[idx]) } icon: { Image(fioriName: "fiori.accept") } + } + } else { + Button(item.valueOptions[idx]) { + item.onTap(option: item.valueOptions[idx]) + item.apply() + onUpdate() + } + } + } + } label: { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, isSelected: item.isChecked) + } + } + } +} + +struct HeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat? + + static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) { + guard let nextValue = nextValue() else { return } + value = nextValue + } +} + +private extension View { + func readHeight() -> some View { + self.modifier(ReadHeightModifier()) + } +} + +private struct ReadHeightModifier: ViewModifier { + private var sizeView: some View { + GeometryReader { geometry in + Color.clear.preference(key: HeightPreferenceKey.self, value: geometry.size.height) + } + } + + func body(content: Content) -> some View { + content.background(self.sizeView) + } +} + +struct DateTimeMenuItem: View { + @Binding private var item: DateTimeItem + + @State private var isSheetVisible: Bool = false + + @State var detentHeight: CGFloat = 0 + + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + CancellableResettableDialogForm { + SortFilterItemTitle(title: item.name) + } cancelAction: { + Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.cancel() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + } resetAction: { + Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.reset() + }) + .buttonStyle(CancelResetButtonStyle()) + } applyAction: { + Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(ApplyButtonStyle()) + } components: { + VStack { + HStack { + Text(NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "")) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + DatePicker( + "", + selection: Binding(get: { item.workingValue ?? Date() }, set: { item.workingValue = $0 }), + displayedComponents: [.hourAndMinute] + ) + .labelsHidden() + } + + DatePicker( + item.label, + selection: Binding(get: { item.workingValue ?? Date() }, set: { item.workingValue = $0 }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + } + .readHeight() + .onPreferenceChange(HeightPreferenceKey.self) { height in + if let height { + self.detentHeight = height + } + } + .presentationDetents([.height(self.detentHeight)]) + } + } +} + +struct SwitchMenuItem: View { + @Binding private var item: SwitchItem + +// @State var detentHeight: CGFloat = 0 + +// @State private var isSheetVisible: Bool = false + var onUpdate: () -> Void + + public init(item: Binding, onUpdate: @escaping () -> Void) { + self._item = item + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, isSelected: item.isChecked) + .onTapGesture { + if item.value != nil { + item.workingValue?.toggle() + item.apply() + onUpdate() + } else { + item.workingValue = true + item.apply() + onUpdate() + } +// isSheetVisible.toggle() + } +// .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { +// CancellableResettableDialogForm { +// Text(item.name) +// } cancelAction: { +// Action(actionText: "Cancel", didSelectAction: { +// item.cancel() +// isSheetVisible.toggle() +// }) +// .buttonStyle(CancelResetButtonStyle()) +// } resetAction: { +// Action(actionText: "Reset", didSelectAction: { +// item.reset() +// }) +// .buttonStyle(CancelResetButtonStyle()) +// } applyAction: { +// Action(actionText: "Apply", didSelectAction: { +// item.apply() +// onUpdate() +// isSheetVisible.toggle() +// }) +// .buttonStyle(ApplyButtonStyle()) +// } components: { +// SwitchPicker(value: $item.workingValue) +// } +// } + } +} + +struct FullCFGMenuItem: View { + @Environment(\.sortFilterMenuItemFullConfigurationButton) var fullCFGButton + + @Binding var items: [[SortFilterItem]] + + @State var isSheetVisible = false + + var onUpdate: () -> Void + + public init(items: Binding<[[SortFilterItem]]>, onUpdate: @escaping () -> Void) { + self._items = items + self.onUpdate = onUpdate + } + + var body: some View { + SortFilterMenuItem(leftIcon: icon(name: fullCFGButton.icon, isVisible: true), title: fullCFGButton.name ?? "", isSelected: true) + .onTapGesture { + isSheetVisible.toggle() + } + .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + SortFilterFullCFG( + title: { + if let title = fullCFGButton.name { + Text(title) + } else { + EmptyView() + } + }, + items: { + _SortFilterCFGItemContainer(items: $items) + }, + cancelAction: { + Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + // item.apply() + onUpdate() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + }, + resetAction: { + Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + // item.cancel() + isSheetVisible.toggle() + }) + .buttonStyle(CancelResetButtonStyle()) + }, + applyAction: { + Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { + // item.reset() + }) + .buttonStyle(ApplyButtonStyle()) + }, + onUpdate: {} + ) + } + } +} + +#Preview { + VStack { + Spacer() + + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) + + Spacer() + + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + .sortFilterMenuItemStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + .sortFilterMenuItemStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + + .sortFilterMenuItemStyle(cornerRadius: 16) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + .sortFilterMenuItemStyle(fillColorSelected: .yellow) + SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + .sortFilterMenuItemStyle(fillColorUnselected: .gray) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) + .sortFilterMenuItemStyle(cornerRadius: 20) + SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) + .sortFilterMenuItemStyle(cornerRadius: 20) + + Spacer() + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift new file mode 100644 index 000000000..e4ca740a5 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift @@ -0,0 +1,26 @@ +import FioriThemeManager +import SwiftUI + +public final class SortFilterStyle { + public static var instance = SortFilterStyle(iconForCheckedItem: Image(fioriName: "fiori.accept")) + + let iconForCheckedItem: Image? + let iconForMenuItem: Image + let foregroundColorForSelectedItem: Color + let backgroundColorForSelectedColor: Color + let foregroundColorForUnselectedItem: Color + let backgroundColorForUnselectedColor: Color + + public static var shared: SortFilterStyle { + instance + } + + public init(iconForCheckedItem: Image? = nil, iconForMenuItem: Image? = nil, foregroundColorForSelectedItem: Color? = nil, backgroundColorForSelectedColor: Color? = nil, foregroundColorForUnselectedItem: Color? = nil, backgroundColorForUnselectedColor: Color? = nil) { + self.iconForCheckedItem = iconForCheckedItem + self.iconForMenuItem = iconForMenuItem ?? Image(fioriName: "fiori.navigation.down.arrow")! + self.foregroundColorForSelectedItem = foregroundColorForSelectedItem ?? .preferredColor(.tintColor) + self.backgroundColorForSelectedColor = backgroundColorForSelectedColor ?? .preferredColor(.primaryBackground) + self.foregroundColorForUnselectedItem = foregroundColorForUnselectedItem ?? .preferredColor(.separator) + self.backgroundColorForUnselectedColor = foregroundColorForUnselectedItem ?? .preferredColor(.tertiaryFill) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift new file mode 100644 index 000000000..2fe7b2218 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -0,0 +1,173 @@ +// +import FioriThemeManager +// _SortFilterMenuItemContainer.swift +// +// +// Created by Xu, Charles on 9/25/23. +// +import SwiftUI + +public struct _SortFilterCFGItemContainer { + @EnvironmentObject var context: SortFilterContext + + @Binding var _items: [[SortFilterItem]] + + public init(items: Binding<[[SortFilterItem]]>) { + self.__items = items + } +} + +extension _SortFilterCFGItemContainer: View { + public var body: some View { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 20) { + ForEach(0 ..< _items.count) { r in + ForEach(0 ..< _items[r].count) { c in + switch _items[r][c] { + case .picker: + VStack { + HStack { + Text(_items[r][c].picker.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + OptionListPicker( + value: Binding<[Int]>(get: { _items[r][c].picker.workingValue }, set: { _items[r][c].picker.workingValue = $0 }), + valueOptions: _items[r][c].picker.valueOptions, + onTap: { index in + _items[r][c].picker.onTap(option: _items[r][c].picker.valueOptions[index]) + } + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .filterfeedback: + VStack { + HStack { + Text(_items[r][c].filterfeedback.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + OptionListPicker( + value: Binding<[Int]>(get: { _items[r][c].filterfeedback.workingValue }, set: { _items[r][c].filterfeedback.workingValue = $0 }), + valueOptions: _items[r][c].filterfeedback.valueOptions, + onTap: { index in + _items[r][c].filterfeedback.onTap(option: _items[r][c].filterfeedback.valueOptions[index]) + } + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + + case .switch: + VStack { +// Text(_items[r][c].switch.name) + SwitchPicker(value: Binding(get: { _items[r][c].switch.workingValue }, set: { _items[r][c].switch.workingValue = $0 }), name: _items[r][c].switch.name, hint: nil) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .slider: + VStack { + HStack { + Text(_items[r][c].slider.name) + .font(.headline) + Spacer() + } + SliderPicker( + value: Binding(get: { _items[r][c].slider.workingValue }, set: { _items[r][c].slider.workingValue = $0 }), + formatter: _items[r][c].slider.formatter, + minimumValue: _items[r][c].slider.minimumValue, + maximumValue: _items[r][c].slider.maximumValue + ) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + case .datetime: + VStack { + HStack { + Text(_items[r][c].datetime.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + + DatePicker( + NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.hourAndMinute] + ) + + DatePicker( + _items[r][c].datetime.label, + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + } + .padding() + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(.separator), lineWidth: 2) + ) + } + } + } + } + } + .onChange(of: _items) { _ in + for item in _items.joined() { + if item.isChanged { + context.isResetButtonEnabled = true + context.isApplyButtonEnabled = true + return + } + } + context.isResetButtonEnabled = true + } + .onAppear { + context.handleCancel = { + print("....cancel in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].cancel() + } + } + } + + context.handleReset = { + print("....reset in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].reset() + } + } + } + + context.handleApply = { + print("....apply in context...") + for r in 0 ..< _items.count { + for c in 0 ..< _items[r].count { + _items[r][c].apply() + } + } + } + + context.isResetButtonEnabled = false + context.isApplyButtonEnabled = false + } + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift new file mode 100644 index 000000000..93941c514 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift @@ -0,0 +1,136 @@ +// +// _SortFilterMenuItemContainer.swift +// +// +// Created by Xu, Charles on 9/25/23. +// +import SwiftUI + +public struct _SortFilterMenuItemContainer { + @Environment(\.onModelUpdateAppCallback) var onUpdate: () -> Void +// @Environment(\.cancelActionView) var _cancelAction + @Environment(\.sortFilterMenuItemFullConfigurationButton) var fullCFGButton + @Binding var _items: [[SortFilterItem]] + + public init(items: Binding<[[SortFilterItem]]>) { + self.__items = items + } +} + +extension _SortFilterMenuItemContainer: View { + public var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 10) { + if fullCFGButton.positon == .leading { + FullCFGMenuItem(items: $_items, onUpdate: onUpdate) + } + ForEach(0 ..< _items.count) { r in + ForEach(0 ..< _items[r].count) { c in + if _items[r][c].isShownOnMenu { + switch _items[r][c] { + case .picker: + PickerMenuItem(item: Binding(get: { _items[r][c].picker }, set: { _items[r][c].picker = $0 }), onUpdate: onUpdate) + case .filterfeedback: + FilterFeedbackMenuItem(item: Binding(get: { _items[r][c].filterfeedback }, set: { _items[r][c].filterfeedback = $0 }), onUpdate: onUpdate) + case .switch: + SwitchMenuItem(item: Binding(get: { _items[r][c].switch }, set: { _items[r][c].switch = $0 }), onUpdate: onUpdate) + case .slider: + SliderMenuItem(item: Binding(get: { _items[r][c].slider }, set: { _items[r][c].slider = $0 }), onUpdate: onUpdate) + case .datetime: + DateTimeMenuItem(item: Binding(get: { _items[r][c].datetime }, set: { _items[r][c].datetime = $0 }), onUpdate: onUpdate) + } + } + } + } + if fullCFGButton.positon == .trailing { + FullCFGMenuItem(items: $_items, onUpdate: onUpdate) + } + } + } + .frame(minHeight: 44) + .padding(.leading, 5) + } +} + +public struct SortFilterMenuItemFullConfigurationButtonKey: EnvironmentKey { + public static var defaultValue: SortFilterMenuItemFullConfigurationButton = .none +} + +public struct SortFilterMenuItemFullConfigurationButton { + public let name: String? + public let icon: String? + public let positon: Position + + public enum Position { + case leading, trailing, none + } + + private init(name: String? = nil, icon: String? = nil, positon: Position) { + self.name = name + self.icon = icon + self.positon = positon + } + + public static func leading(name: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, positon: .leading) + } + + public static func leading(icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(icon: icon, positon: .leading) + } + + public static func leading(name: String, icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, icon: icon, positon: .leading) + } + + public static func trailing(name: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, positon: .trailing) + } + + public static func trailing(icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(icon: icon, positon: .trailing) + } + + public static func trailing(name: String, icon: String) -> SortFilterMenuItemFullConfigurationButton { + SortFilterMenuItemFullConfigurationButton(name: name, icon: icon, positon: .trailing) + } + + static var none = SortFilterMenuItemFullConfigurationButton(positon: Position.none) +} + +public extension EnvironmentValues { + var sortFilterMenuItemFullConfigurationButton: SortFilterMenuItemFullConfigurationButton { + get { + self[SortFilterMenuItemFullConfigurationButtonKey.self] + } + set { + self[SortFilterMenuItemFullConfigurationButtonKey.self] = newValue + } + } +} + +public extension View { + func leadingFullConfigurationMenuItem(name: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name)) + } + + func leadingFullConfigurationMenuItem(icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(icon: icon)) + } + + func leadingFullConfigurationMenuItem(name: String, icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name, icon: icon)) + } + + func trailingFullConfigurationMenuItem(name: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(name: name)) + } + + func trailingFullConfigurationMenuItem(icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(icon: icon)) + } + + func trailingFullConfigurationMenuItem(name: String, icon: String) -> some View { + self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(name: name, icon: icon)) + } +} diff --git a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift new file mode 100644 index 000000000..b06d1d098 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift @@ -0,0 +1,153 @@ +import FioriThemeManager +import SwiftUI + +extension SwitchPicker: View { + public var body: some View { + AnyView( + Toggle(_name ?? "", isOn: .convert(from: _value, ifNilUse: false)) + .toggleStyle(fioriToggleStyle) + ).typeErased + } +} + +private extension Binding { + static func convert(from value: Binding, ifNilUse defaultValue: Bool) -> Binding { + Binding( + get: { + value.wrappedValue ?? defaultValue + }, + set: { + value.wrappedValue = $0 + } + ) + } +} + +public struct FioriToggleStyle: ToggleStyle { + @ScaledMetric var scale: CGFloat = 1 + + let labelColor: Color + + let onColor: Color + let offColor: Color + + let onThumbColor: Color + let offThumbColor: Color + + let onBorderColor: Color + let offBorderColor: Color + + public init( + labelColor: Color = Color.preferredColor(.primaryLabel), + onColor: Color = Color.preferredColor(.tintColor), + offColor: Color = Color.preferredColor(.secondaryFill), + onThumbColor: Color = Color.preferredColor(.primaryBackground), + offThumbColor: Color = Color.preferredColor(.primaryBackground), + onBorderColor: Color = Color.preferredColor(.separator), + offBorderColor: Color = Color.preferredColor(.separator) + ) { + self.labelColor = labelColor + + self.onColor = onColor + self.offColor = offColor + + self.onThumbColor = onThumbColor + self.offThumbColor = offThumbColor + + self.onBorderColor = onBorderColor + self.offBorderColor = offBorderColor + } + + public func makeBody(configuration: Self.Configuration) -> some View { + HStack { + configuration.label + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(labelColor) + + Spacer() + ZStack { + RoundedRectangle(cornerRadius: 16 * scale, style: .circular) + .stroke(configuration.isOn ? onBorderColor : offBorderColor, lineWidth: 0.5 * scale) + .frame(width: 51 * scale, height: 30 * scale) + + RoundedRectangle(cornerRadius: 16 * scale, style: .circular) + .fill(configuration.isOn ? onColor : offColor) + .frame(width: 51 * scale, height: 30 * scale) + .overlay( + Circle() + .fill(configuration.isOn ? onThumbColor : offThumbColor) + .shadow(radius: 1 * scale, x: 0, y: 1 * scale) + .padding(1.5 * scale) + .offset(x: configuration.isOn ? 10 * scale : -10 * scale)) + .animation(Animation.easeInOut(duration: 0.2), value: configuration.isOn) + .frame(minHeight: 44) + .onTapGesture { configuration.isOn.toggle() } + } + } + .padding(.horizontal) + } +} + +// public struct DefaultToggleStyle: ToggleStyle { +// public func makeBody(configuration: Configuration) -> some View { +// VStack { +// Toggle(configuration) +// .labelsHidden() +// .foregroundColor(Color.tintColor) +// configuration.label +// .font(.system(size: 22, weight: .semibold)).lineLimit(2) +// .padding() +// .overlay( +// RoundedRectangle(cornerRadius: 10) +// .stroke(configuration.isOn ? Color.green: Color.gray, lineWidth: 1) +// ) +// } +// } +// } + +public struct FioriToggleStyleKey: EnvironmentKey { + public static var defaultValue: any ToggleStyle = FioriToggleStyle() +} + +public extension EnvironmentValues { + var fioriToggleStyle: any ToggleStyle { + get { + self[FioriToggleStyleKey.self] + } + set { + self[FioriToggleStyleKey.self] = newValue + } + } +} + +/* + // FIXME: - Implement SwitchPicker specific LibraryContentProvider + + @available(iOS 14.0, macOS 11.0, *) + struct SwitchPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SwitchPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } + } + */ + +private struct TestSwitchPicker: View { + @State var v1: Bool? = true + @State var v2: Bool? = false + @State var v3: Bool? = nil + + var body: some View { + VStack { + SwitchPicker(value: $v1, hint: nil) + SwitchPicker(value: $v2, hint: nil) + SwitchPicker(value: $v3, hint: nil) + } + } +} + +#Preview { + TestSwitchPicker() + .frame(width: 375) +} diff --git a/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift index 9ffbfc4de..45b056079 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/Component+Protocols.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -158,6 +158,14 @@ public protocol FootnoteIconsComponent { var footnoteIcons: [TextOrIcon]? { get } } +public protocol LeftIconComponent { + var leftIcon: Image? { get } +} + +public protocol RightIconComponent { + var rightIcon: Image? { get } +} + public protocol ActionComponent { var actionText: String? { get } @@ -194,10 +202,50 @@ public protocol KpiProgressComponent : KpiComponent { var fraction: Double? { get } } +public protocol OptionListPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: [Int] { get set } + // sourcery: no_view + var valueOptions: [String] { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + public protocol ProgressIndicatorComponent { var progressIndicatorText: String? { get } } +public protocol SliderPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Int? { get set } + // sourcery: default.value=nil + var formatter: String? { get } + // sourcery: default.value=0 + // sourcery: no_view + var minimumValue: Int { get } + // sourcery: default.value=100 + // sourcery: no_view + var maximumValue: Int { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + +public protocol SwitchPickerComponent : AnyObject { + // sourcery: bindingProperty + // sourcery: no_view + var value: Bool? { get set } + // sourcery: default.value=nil + // sourcery: no_view + var name: String? { get } + // sourcery: default.value=nil + // sourcery: no_view + var hint: String? { get } +} + public protocol TextInputComponent : AnyObject { // sourcery: bindingPropertyOptional=.constant("") var textInputValue: String { get set } diff --git a/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift index 97eae186c..013e39d75 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/ComponentProtocols+Extension.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -142,12 +142,24 @@ public extension KpiProgressComponent { } } +public extension LeftIconComponent { + var leftIcon: Image? { + return nil + } +} + public extension LowerBoundTitleComponent { var lowerBoundTitle: String? { return nil } } +public extension OptionListPickerComponent { + var hint: String? { + return nil + } +} + public extension PlaceholderComponent { var placeholder: String? { return nil @@ -160,6 +172,12 @@ public extension ProgressIndicatorComponent { } } +public extension RightIconComponent { + var rightIcon: Image? { + return nil + } +} + public extension SecondActionTitleComponent { var secondActionTitle: String? { return nil @@ -184,6 +202,28 @@ public extension SecondaryValuesAxisTitleComponent { } } +public extension SliderPickerComponent { + var formatter: String? { + return nil + } + + var minimumValue: Int { + return 0 + } + + var maximumValue: Int { + return 100 + } + + var hint: String? { + return nil + } + + var value: Int? { + return nil + } +} + public extension StatusComponent { var status: TextOrIcon? { return nil @@ -202,6 +242,20 @@ public extension SubtitleComponent { } } +public extension SwitchPickerComponent { + var name: String? { + return nil + } + + var hint: String? { + return nil + } + + var value: Bool? { + return nil + } +} + public extension TagsComponent { var tags: [String]? { return nil diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift index 43459f573..926dab3f0 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -146,6 +146,14 @@ struct FootnoteIconsModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } +struct LeftIconModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct RightIconModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + struct ActionTextModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } @@ -158,7 +166,7 @@ struct ProgressIndicatorTextModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct NodeModifierKey: EnvironmentKey { +struct FormatterModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } @@ -206,6 +214,22 @@ struct SaveActionModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } +struct NodeModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ItemsModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ResetActionModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + +struct ApplyActionModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + struct NextActionModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift index 016d1dda8..90de927bd 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 1.1.1 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import SwiftUI @@ -184,6 +184,16 @@ extension EnvironmentValues { set { self[FootnoteIconsModifierKey.self] = newValue } } + public var leftIconModifier: AnyViewModifier { + get { return self[LeftIconModifierKey.self] } + set { self[LeftIconModifierKey.self] = newValue } + } + + public var rightIconModifier: AnyViewModifier { + get { return self[RightIconModifierKey.self] } + set { self[RightIconModifierKey.self] = newValue } + } + public var actionTextModifier: AnyViewModifier { get { return self[ActionTextModifierKey.self] } set { self[ActionTextModifierKey.self] = newValue } @@ -199,9 +209,9 @@ extension EnvironmentValues { set { self[ProgressIndicatorTextModifierKey.self] = newValue } } - public var nodeModifier: AnyViewModifier { - get { return self[NodeModifierKey.self] } - set { self[NodeModifierKey.self] = newValue } + public var formatterModifier: AnyViewModifier { + get { return self[FormatterModifierKey.self] } + set { self[FormatterModifierKey.self] = newValue } } public var textInputValueModifier: AnyViewModifier { @@ -259,6 +269,26 @@ extension EnvironmentValues { set { self[SaveActionModifierKey.self] = newValue } } + public var nodeModifier: AnyViewModifier { + get { return self[NodeModifierKey.self] } + set { self[NodeModifierKey.self] = newValue } + } + + public var itemsModifier: AnyViewModifier { + get { return self[ItemsModifierKey.self] } + set { self[ItemsModifierKey.self] = newValue } + } + + public var resetActionModifier: AnyViewModifier { + get { return self[ResetActionModifierKey.self] } + set { self[ResetActionModifierKey.self] = newValue } + } + + public var applyActionModifier: AnyViewModifier { + get { return self[ApplyActionModifierKey.self] } + set { self[ApplyActionModifierKey.self] = newValue } + } + public var nextActionModifier: AnyViewModifier { get { return self[NextActionModifierKey.self] } set { self[NextActionModifierKey.self] = newValue } @@ -463,6 +493,16 @@ public extension View { self.environment(\.footnoteIconsModifier, AnyViewModifier(transform)) } + @ViewBuilder + func leftIconModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.leftIconModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func rightIconModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.rightIconModifier, AnyViewModifier(transform)) + } + @ViewBuilder func actionTextModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.actionTextModifier, AnyViewModifier(transform)) @@ -479,8 +519,8 @@ public extension View { } @ViewBuilder - func nodeModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { - self.environment(\.nodeModifier, AnyViewModifier(transform)) + func formatterModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.formatterModifier, AnyViewModifier(transform)) } @ViewBuilder @@ -538,6 +578,26 @@ public extension View { self.environment(\.saveActionModifier, AnyViewModifier(transform)) } + @ViewBuilder + func nodeModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.nodeModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func itemsModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.itemsModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func resetActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.resetActionModifier, AnyViewModifier(transform)) + } + + @ViewBuilder + func applyActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.applyActionModifier, AnyViewModifier(transform)) + } + @ViewBuilder func nextActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.nextActionModifier, AnyViewModifier(transform)) diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift new file mode 100644 index 000000000..b7e8c3e77 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct OptionChip { + @Environment(\.leftIconModifier) private var leftIconModifier + @Environment(\.titleModifier) private var titleModifier + @Environment(\.optionChipStyle) var optionChipStyle + + let _leftIcon: LeftIcon + let _title: Title + let _isSelected: Bool + + + private var isModelInit: Bool = false + private var isLeftIconNil: Bool = false + + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self._leftIcon = leftIcon() + self._title = title() + self._isSelected = isSelected + } + + @ViewBuilder var leftIcon: some View { + if isModelInit { + _leftIcon.modifier(leftIconModifier.concat(Fiori.OptionChip.leftIcon).concat(Fiori.OptionChip.leftIconCumulative)) + } else { + _leftIcon.modifier(leftIconModifier.concat(Fiori.OptionChip.leftIcon)) + } + } + @ViewBuilder var title: some View { + if isModelInit { + _title.modifier(titleModifier.concat(Fiori.OptionChip.title).concat(Fiori.OptionChip.titleCumulative)) + } else { + _title.modifier(titleModifier.concat(Fiori.OptionChip.title)) + } + } + + var isLeftIconEmptyView: Bool { + ((isModelInit && isLeftIconNil) || LeftIcon.self == EmptyView.self) ? true : false + } +} + +extension OptionChip where LeftIcon == _ConditionalContent, + Title == Text { + + public init(model: OptionChipModel) { + self.init(leftIcon: model.leftIcon, title: model.title, isSelected: model.isSelected) + } + + public init(leftIcon: Image? = nil, title: String, isSelected: Bool) { + self._leftIcon = leftIcon != nil ? ViewBuilder.buildEither(first: leftIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._title = Text(title) + self._isSelected = isSelected + + isModelInit = true + isLeftIconNil = leftIcon == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift new file mode 100644 index 000000000..ef062bf3f --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift @@ -0,0 +1,23 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct OptionListPicker { + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding<[Int]> + var _valueOptions: [String] + var _hint: String? = nil + var _onTap: ((_ index: Int) -> Void)? = nil + + public init(model: OptionListPickerModel) { + self.init(value: Binding<[Int]>(get: { model.value }, set: { model.value = $0 }), valueOptions: model.valueOptions, hint: model.hint, onTap: model.onTap) + } + + public init(value: Binding<[Int]>, valueOptions: [String] = [], hint: String? = nil, onTap: ((_ index: Int) -> Void)? = nil) { + self._value = value + self._valueOptions = valueOptions + self._hint = hint + self._onTap = onTap + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift new file mode 100644 index 000000000..7d1965930 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SliderPicker+API.generated.swift @@ -0,0 +1,26 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SliderPicker { + @Environment(\.formatterModifier) private var formatterModifier + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding + var _formatter: String? = nil + var _minimumValue: Int + var _maximumValue: Int + var _hint: String? = nil + + public init(model: SliderPickerModel) { + self.init(value: Binding(get: { model.value }, set: { model.value = $0 }), formatter: model.formatter, minimumValue: model.minimumValue, maximumValue: model.maximumValue, hint: model.hint) + } + + public init(value: Binding, formatter: String? = nil, minimumValue: Int = 0, maximumValue: Int = 100, hint: String? = nil) { + self._value = value + self._formatter = formatter + self._minimumValue = minimumValue + self._maximumValue = maximumValue + self._hint = hint + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift new file mode 100644 index 000000000..52b58f7b1 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift @@ -0,0 +1,116 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterFullCFG { + @Environment(\.titleModifier) private var titleModifier + @Environment(\.itemsModifier) private var itemsModifier + @Environment(\.cancelActionModifier) private var cancelActionModifier + @Environment(\.resetActionModifier) private var resetActionModifier + @Environment(\.applyActionModifier) private var applyActionModifier + @Environment(\.dismiss) var dismiss + + let _title: Title + var _items: Items + let _cancelAction: CancelActionView + let _resetAction: ResetActionView + let _applyAction: ApplyActionView + let _onUpdate: (() -> Void)? + @State var context: SortFilterContext = SortFilterContext() + + private var isModelInit: Bool = false + private var isCancelActionNil: Bool = false + private var isResetActionNil: Bool = false + private var isApplyActionNil: Bool = false + private var isOnUpdateNil: Bool = false + + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder resetAction: () -> ResetActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self._title = title() + self._items = items() + self._cancelAction = cancelAction() + self._resetAction = resetAction() + self._applyAction = applyAction() + self._onUpdate = onUpdate + } + + @ViewBuilder var title: some View { + if isModelInit { + _title.modifier(titleModifier.concat(Fiori.SortFilterFullCFG.title).concat(Fiori.SortFilterFullCFG.titleCumulative)) + } else { + _title.modifier(titleModifier.concat(Fiori.SortFilterFullCFG.title)) + } + } + @ViewBuilder var items: some View { + if isModelInit { + _items.modifier(itemsModifier.concat(Fiori.SortFilterFullCFG.items).concat(Fiori.SortFilterFullCFG.itemsCumulative)) + } else { + _items.modifier(itemsModifier.concat(Fiori.SortFilterFullCFG.items)) + } + } + @ViewBuilder var cancelAction: some View { + if isModelInit { + _cancelAction.modifier(cancelActionModifier.concat(Fiori.SortFilterFullCFG.cancelAction).concat(Fiori.SortFilterFullCFG.cancelActionCumulative)) + } else { + _cancelAction.modifier(cancelActionModifier.concat(Fiori.SortFilterFullCFG.cancelAction)) + } + } + @ViewBuilder var resetAction: some View { + if isModelInit { + _resetAction.modifier(resetActionModifier.concat(Fiori.SortFilterFullCFG.resetAction).concat(Fiori.SortFilterFullCFG.resetActionCumulative)) + } else { + _resetAction.modifier(resetActionModifier.concat(Fiori.SortFilterFullCFG.resetAction)) + } + } + @ViewBuilder var applyAction: some View { + if isModelInit { + _applyAction.modifier(applyActionModifier.concat(Fiori.SortFilterFullCFG.applyAction).concat(Fiori.SortFilterFullCFG.applyActionCumulative)) + } else { + _applyAction.modifier(applyActionModifier.concat(Fiori.SortFilterFullCFG.applyAction)) + } + } + + var isCancelActionEmptyView: Bool { + ((isModelInit && isCancelActionNil) || CancelActionView.self == EmptyView.self) ? true : false + } + + var isResetActionEmptyView: Bool { + ((isModelInit && isResetActionNil) || ResetActionView.self == EmptyView.self) ? true : false + } + + var isApplyActionEmptyView: Bool { + ((isModelInit && isApplyActionNil) || ApplyActionView.self == EmptyView.self) ? true : false + } +} + +extension SortFilterFullCFG where Title == Text, + Items == _SortFilterCFGItemContainer, + CancelActionView == _ConditionalContent, + ResetActionView == _ConditionalContent, + ApplyActionView == _ConditionalContent { + + public init(model: SortFilterFullCFGModel) { + self.init(title: model.title, items: Binding<[[SortFilterItem]]>(get: { model.items }, set: { model.items = $0 }), cancelAction: model.cancelAction != nil ? Action(model: model.cancelAction!) : nil, resetAction: model.resetAction != nil ? Action(model: model.resetAction!) : nil, applyAction: model.applyAction != nil ? Action(model: model.applyAction!) : nil, onUpdate: model.onUpdate) + } + + public init(title: String, items: Binding<[[SortFilterItem]]>, cancelAction: Action? = Action(model: _CancelActionDefault()), resetAction: Action? = Action(model: _ResetActionDefault()), applyAction: Action? = Action(model: _ApplyActionDefault()), onUpdate: (() -> Void)? = nil) { + self._title = Text(title) + self._items = _SortFilterCFGItemContainer(items: items) + self._cancelAction = cancelAction != nil ? ViewBuilder.buildEither(first: cancelAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._resetAction = resetAction != nil ? ViewBuilder.buildEither(first: resetAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._applyAction = applyAction != nil ? ViewBuilder.buildEither(first: applyAction!) : ViewBuilder.buildEither(second: EmptyView()) + self._onUpdate = onUpdate + + isModelInit = true + isCancelActionNil = cancelAction == nil ? true : false + isResetActionNil = resetAction == nil ? true : false + isApplyActionNil = applyAction == nil ? true : false + isOnUpdateNil = onUpdate == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift new file mode 100644 index 000000000..97352cf88 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift @@ -0,0 +1,47 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterMenu { + @Environment(\.itemsModifier) private var itemsModifier + + var _items: Items + var _onUpdate: (() -> Void)? + + + private var isModelInit: Bool = false + private var isOnUpdateNil: Bool = false + + public init( + @ViewBuilder items: () -> Items, + onUpdate: (() -> Void)? = nil + ) { + self._items = items() + self._onUpdate = onUpdate + } + + @ViewBuilder var items: some View { + if isModelInit { + _items.modifier(itemsModifier.concat(Fiori.SortFilterMenu.items).concat(Fiori.SortFilterMenu.itemsCumulative)) + } else { + _items.modifier(itemsModifier.concat(Fiori.SortFilterMenu.items)) + } + } + + +} + +extension SortFilterMenu where Items == _SortFilterMenuItemContainer { + + public init(model: SortFilterMenuModel) { + self.init(items: Binding<[[SortFilterItem]]>(get: { model.items }, set: { model.items = $0 }), onUpdate: model.onUpdate) + } + + public init(items: Binding<[[SortFilterItem]]>, onUpdate: (() -> Void)? = nil) { + self._items = _SortFilterMenuItemContainer(items: items) + self._onUpdate = onUpdate + + isModelInit = true + isOnUpdateNil = onUpdate == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift new file mode 100644 index 000000000..5469109c4 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift @@ -0,0 +1,82 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SortFilterMenuItem { + @Environment(\.leftIconModifier) private var leftIconModifier + @Environment(\.titleModifier) private var titleModifier + @Environment(\.rightIconModifier) private var rightIconModifier + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + let _leftIcon: LeftIcon + let _title: Title + let _rightIcon: RightIcon + let _isSelected: Bool + @State var context: SortFilterContext = SortFilterContext() + + private var isModelInit: Bool = false + private var isLeftIconNil: Bool = false + private var isRightIconNil: Bool = false + + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + @ViewBuilder rightIcon: () -> RightIcon, + isSelected: Bool + ) { + self._leftIcon = leftIcon() + self._title = title() + self._rightIcon = rightIcon() + self._isSelected = isSelected + } + + @ViewBuilder var leftIcon: some View { + if isModelInit { + _leftIcon.modifier(leftIconModifier.concat(Fiori.SortFilterMenuItem.leftIcon).concat(Fiori.SortFilterMenuItem.leftIconCumulative)) + } else { + _leftIcon.modifier(leftIconModifier.concat(Fiori.SortFilterMenuItem.leftIcon)) + } + } + @ViewBuilder var title: some View { + if isModelInit { + _title.modifier(titleModifier.concat(Fiori.SortFilterMenuItem.title).concat(Fiori.SortFilterMenuItem.titleCumulative)) + } else { + _title.modifier(titleModifier.concat(Fiori.SortFilterMenuItem.title)) + } + } + @ViewBuilder var rightIcon: some View { + if isModelInit { + _rightIcon.modifier(rightIconModifier.concat(Fiori.SortFilterMenuItem.rightIcon).concat(Fiori.SortFilterMenuItem.rightIconCumulative)) + } else { + _rightIcon.modifier(rightIconModifier.concat(Fiori.SortFilterMenuItem.rightIcon)) + } + } + + var isLeftIconEmptyView: Bool { + ((isModelInit && isLeftIconNil) || LeftIcon.self == EmptyView.self) ? true : false + } + + var isRightIconEmptyView: Bool { + ((isModelInit && isRightIconNil) || RightIcon.self == EmptyView.self) ? true : false + } +} + +extension SortFilterMenuItem where LeftIcon == _ConditionalContent, + Title == Text, + RightIcon == _ConditionalContent { + + public init(model: SortFilterMenuItemModel) { + self.init(leftIcon: model.leftIcon, title: model.title, rightIcon: model.rightIcon, isSelected: model.isSelected) + } + + public init(leftIcon: Image? = nil, title: String, rightIcon: Image? = nil, isSelected: Bool) { + self._leftIcon = leftIcon != nil ? ViewBuilder.buildEither(first: leftIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._title = Text(title) + self._rightIcon = rightIcon != nil ? ViewBuilder.buildEither(first: rightIcon!) : ViewBuilder.buildEither(second: EmptyView()) + self._isSelected = isSelected + + isModelInit = true + isLeftIconNil = leftIcon == nil ? true : false + isRightIconNil = rightIcon == nil ? true : false + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift new file mode 100644 index 000000000..916d3cd29 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SwitchPicker+API.generated.swift @@ -0,0 +1,22 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public struct SwitchPicker { + @Environment(\.fioriToggleStyle) var fioriToggleStyle + @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle + + var _value: Binding + var _name: String? = nil + var _hint: String? = nil + + public init(model: SwitchPickerModel) { + self.init(value: Binding(get: { model.value }, set: { model.value = $0 }), name: model.name, hint: model.hint) + } + + public init(value: Binding, name: String? = nil, hint: String? = nil) { + self._value = value + self._name = name + self._hint = hint + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift new file mode 100644 index 000000000..237897746 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionChip+View.generated.swift @@ -0,0 +1,62 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/OptionChip+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement OptionChip `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum OptionChip { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + } +} + +// FIXME: - Implement OptionChip View body + +extension OptionChip: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement OptionChip specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct OptionChipLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionChip(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift new file mode 100644 index 000000000..37e1d46ef --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/OptionListPicker+View.swift` +//TODO: Implement OptionListPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement OptionListPicker View body + +extension OptionListPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement OptionListPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct OptionListPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(OptionListPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift new file mode 100644 index 000000000..2e6706c9b --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SliderPicker+View.swift` +//TODO: Implement SliderPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement SliderPicker View body + +extension SliderPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SliderPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SliderPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SliderPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift new file mode 100644 index 000000000..20e9cfd89 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift @@ -0,0 +1,74 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterFullCFG+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterFullCFG `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterFullCFG { + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + typealias CancelAction = EmptyModifier + typealias CancelActionCumulative = EmptyModifier + typealias ResetAction = EmptyModifier + typealias ResetActionCumulative = EmptyModifier + typealias ApplyAction = EmptyModifier + typealias ApplyActionCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let title = Title() + static let items = Items() + static let cancelAction = CancelAction() + static let resetAction = ResetAction() + static let applyAction = ApplyAction() + static let titleCumulative = TitleCumulative() + static let itemsCumulative = ItemsCumulative() + static let cancelActionCumulative = CancelActionCumulative() + static let resetActionCumulative = ResetActionCumulative() + static let applyActionCumulative = ApplyActionCumulative() + } +} + +// FIXME: - Implement SortFilterFullCFG View body + +extension SortFilterFullCFG: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterFullCFG specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterFullCFGLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterFullCFG(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift new file mode 100644 index 000000000..d1ae49304 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenu+View.generated.swift @@ -0,0 +1,58 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterMenu+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterMenu `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterMenu { + typealias Items = EmptyModifier + typealias ItemsCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let items = Items() + static let itemsCumulative = ItemsCumulative() + } +} + +// FIXME: - Implement SortFilterMenu View body + +extension SortFilterMenu: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterMenu specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterMenuLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenu(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift new file mode 100644 index 000000000..7e85ea3f1 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift @@ -0,0 +1,66 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterMenuItem+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement SortFilterMenuItem `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum SortFilterMenuItem { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + typealias RightIcon = EmptyModifier + typealias RightIconCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let rightIcon = RightIcon() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + static let rightIconCumulative = RightIconCumulative() + } +} + +// FIXME: - Implement SortFilterMenuItem View body + +extension SortFilterMenuItem: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SortFilterMenuItem specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SortFilterMenuItemLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SortFilterMenuItem(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift new file mode 100644 index 000000000..4ac15b331 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift @@ -0,0 +1,34 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SwitchPicker+View.swift` +//TODO: Implement SwitchPicker `View` body + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +// FIXME: - Implement SwitchPicker View body + +extension SwitchPicker: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement SwitchPicker specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct SwitchPickerLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(SwitchPicker(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift new file mode 100644 index 000000000..b50093bb2 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift @@ -0,0 +1,16 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension OptionChip where LeftIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + isSelected: isSelected + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionListPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift new file mode 100644 index 000000000..bad77d3f6 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift @@ -0,0 +1,131 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension SortFilterFullCFG where CancelActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder resetAction: () -> ResetActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: resetAction, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ResetActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: cancelAction, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ApplyActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + @ViewBuilder resetAction: () -> ResetActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: cancelAction, + resetAction: resetAction, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder applyAction: () -> ApplyActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: applyAction, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder resetAction: () -> ResetActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: resetAction, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where ResetActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + @ViewBuilder cancelAction: () -> CancelActionView, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: cancelAction, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} + +extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == Action, ApplyActionView == Action { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder items: () -> Items, + onUpdate: (() -> Void)? = nil + ) { + self.init( + title: title, + items: items, + cancelAction: { Action(model: _CancelActionDefault()) }, + resetAction: { Action(model: _ResetActionDefault()) }, + applyAction: { Action(model: _ApplyActionDefault()) }, + onUpdate: onUpdate + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift new file mode 100644 index 000000000..1f3fcd0e5 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift @@ -0,0 +1,47 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +extension SortFilterMenuItem where LeftIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + @ViewBuilder rightIcon: () -> RightIcon, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + rightIcon: rightIcon, + isSelected: isSelected + ) + } +} + +extension SortFilterMenuItem where RightIcon == EmptyView { + public init( + @ViewBuilder leftIcon: () -> LeftIcon, + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: leftIcon, + title: title, + rightIcon: { EmptyView() }, + isSelected: isSelected + ) + } +} + +extension SortFilterMenuItem where LeftIcon == EmptyView, RightIcon == EmptyView { + public init( + @ViewBuilder title: () -> Title, + isSelected: Bool + ) { + self.init( + leftIcon: { EmptyView() }, + title: title, + rightIcon: { EmptyView() }, + isSelected: isSelected + ) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift new file mode 100644 index 000000000..922a0296d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift @@ -0,0 +1,3 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift new file mode 100644 index 000000000..bd22eec19 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift @@ -0,0 +1,9 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension OptionListPickerModel { + var onTap: ((_ index: Int) -> Void)? { + return nil + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift new file mode 100644 index 000000000..2a7904961 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift @@ -0,0 +1,21 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension SortFilterFullCFGModel { + var cancelAction: ActionModel? { + return _CancelActionDefault() + } + + var resetAction: ActionModel? { + return _ResetActionDefault() + } + + var applyAction: ActionModel? { + return _ApplyActionDefault() + } + + var onUpdate: (() -> Void)? { + return nil + } +} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift new file mode 100644 index 000000000..e0d70748f --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift @@ -0,0 +1,9 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension SortFilterMenuModel { + var onUpdate: (() -> Void)? { + return nil + } +} diff --git a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings index 6516463b6..ddb06b936 100644 --- a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings @@ -129,3 +129,6 @@ /* XBUT: voice over label for row selection status in DataTable */ "not selected" = "not selected"; + +/* XBUT: calendar time component label */ +"Time" = "Time"; From f04f84621d0a4795a63e19155b883d39d29ad7d1 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Wed, 25 Oct 2023 13:55:24 -0700 Subject: [PATCH 04/22] merge changes from upstream --- Apps/Examples/Examples.xcodeproj/project.pbxproj | 8 ++++---- ...w+Extensions.swift => SortFilterView+Extensions.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename Apps/Examples/Examples/FioriSwiftUICore/SortFilter/{View+Extensions.swift => SortFilterView+Extensions.swift} (100%) diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 94d3f6d4d..22fae1363 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -100,7 +100,7 @@ B8D4376F25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */; }; B8D4377125F983730024EE7D /* ObjectCell_Rules_Alignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */; }; B8D437732609479E0024EE7D /* SingleActionFollowButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */; }; - C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A0FDB22AD893FA0001738E /* View+Extensions.swift */; }; + C1A0FDB32AD893FA0001738E /* SortFilterView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A0FDB22AD893FA0001738E /* SortFilterView+Extensions.swift */; }; C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */; }; /* End PBXBuildFile section */ @@ -242,7 +242,7 @@ B8D4376E25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Spec_Jan2018.swift; sourceTree = ""; }; B8D4377025F983730024EE7D /* ObjectCell_Rules_Alignment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCell_Rules_Alignment.swift; sourceTree = ""; }; B8D437722609479E0024EE7D /* SingleActionFollowButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleActionFollowButton.swift; sourceTree = ""; }; - C1A0FDB22AD893FA0001738E /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; + C1A0FDB22AD893FA0001738E /* SortFilterView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SortFilterView+Extensions.swift"; sourceTree = ""; }; C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortFilterExample.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -584,7 +584,7 @@ isa = PBXGroup; children = ( C1C764872A818BEC00BCB0F7 /* SortFilterExample.swift */, - C1A0FDB22AD893FA0001738E /* View+Extensions.swift */, + C1A0FDB22AD893FA0001738E /* SortFilterView+Extensions.swift */, ); path = SortFilter; sourceTree = ""; @@ -742,7 +742,7 @@ 8A557A2424C12F380098003A /* ChartDetailView.swift in Sources */, 8A5579D024C1293C0098003A /* SettingsLine.swift in Sources */, 1FC30412270540FB004BEE00 /* 72-Fonts.swift in Sources */, - C1A0FDB32AD893FA0001738E /* View+Extensions.swift in Sources */, + C1A0FDB32AD893FA0001738E /* SortFilterView+Extensions.swift in Sources */, B84D24ED2652F343007F2373 /* HeaderChartExample.swift in Sources */, B100639329C0624D00AF0CA2 /* StepProgressIndicatorExample.swift in Sources */, B846F94626815CC90085044B /* ContactItemExample.swift in Sources */, diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift similarity index 100% rename from Apps/Examples/Examples/FioriSwiftUICore/SortFilter/View+Extensions.swift rename to Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift From 1785af77e1715b94ca3d1340a7ba5c9115a996ed Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Thu, 26 Oct 2023 14:46:19 -0700 Subject: [PATCH 05/22] refactor --- .../SortFilter/SortFilterExample.swift | 43 ++-- .../SortFilterView+Extensions.swift | 8 +- .../CancellableResettableForm.swift | 9 +- .../DataTypes/SoftFilter+DataType.swift | 34 +++ .../Views/OptionChip+View.swift | 21 +- .../Views/OptionListPicker+View.swift | 7 +- .../Views/SliderPicker+View.swift | 34 ++- .../SortFilter/SortFilterItemTitle.swift | 1 + .../SortFilter/SortFilterMenuItem+View.swift | 4 + .../_SortFilterCFGItemContainer.swift | 207 +++++++++--------- .../Views/SwitchPicker+View.swift | 19 +- .../en.lproj/FioriSwiftUICore.strings | 3 + 12 files changed, 214 insertions(+), 176 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift index 2ba825e21..aad9617f0 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -2,17 +2,25 @@ import FioriSwiftUICore import SwiftUI struct SortFilterExample: View { - @State private var items: [[SortFilterItem]] = [[ - .picker(item: .init(value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], name: "JIRA Status", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), isShownOnMenu: true), - .picker(item: .init(value: [0], valueOptions: ["High", "Medium", "Low"], name: "Priority", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), isShownOnMenu: true), - .filterfeedback(item: .init(value: [0], valueOptions: ["Ascending", "Descending"], name: "Sort Order", allowsMultipleSelection: false, allowsEmptySelection: false, icon: "checkmark")), - .slider(item: .init(value: 10, minimumValue: 0, maximumValue: 100, name: "User Stories", formatter: "%2d Stories", icon: "number"), isShownOnMenu: true), - .slider(item: .init(value: nil, minimumValue: 0, maximumValue: 100, name: "Number of Tasks"), isShownOnMenu: true), - .datetime(item: .init(value: Date(), name: "Start Date", formatter: "yyyy-MM-dd HH:mm",icon: "calendar"), isShownOnMenu: true), - .datetime(item: .init(value: nil, name: "Completion Date"), isShownOnMenu: true), - .switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), isShownOnMenu: true), - .switch(item: .init(name: "Tagged", value: nil, icon: "tag"), isShownOnMenu: false) - ]] + @State private var items: [[SortFilterItem]] = [ + [ + .switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), isShownOnMenu: true), + .switch(item: .init(name: "Tagged", value: nil, icon: "tag"), isShownOnMenu: false), + .picker(item: .init(value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], name: "JIRA Status", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), isShownOnMenu: true) + ], + [ + .picker(item: .init(value: [0], valueOptions: ["High", "Medium", "Low"], name: "Priority", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), isShownOnMenu: true), + .filterfeedback(item: .init(value: [0], valueOptions: ["Ascending", "Descending"], name: "Sort Order", allowsMultipleSelection: false, allowsEmptySelection: false, icon: "checkmark")) + ], + [ + .slider(item: .init(value: 10, minimumValue: 0, maximumValue: 100, name: "User Stories", formatter: "%2d Stories", icon: "number"), isShownOnMenu: true), + .slider(item: .init(value: nil, minimumValue: 0, maximumValue: 100, name: "Number of Tasks"), isShownOnMenu: true), + .datetime(item: .init(value: Date(), name: "Start Date", formatter: "yyyy-MM-dd HH:mm",icon: "calendar"), isShownOnMenu: true) + ], + [ + .datetime(item: .init(value: nil, name: "Completion Date"), isShownOnMenu: true) + ] + ] @State private var isShowingFullCFG: Bool = false @State private var isCustomStyle: Bool = false @@ -25,9 +33,9 @@ struct SortFilterExample: View { SortFilterMenu(items: $items, onUpdate: performSortAndFilter) .sortFilterMenuItemStyle(font: .subheadline, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) - // .trailingFullConfigurationMenuItem(icon: "command") - // .leadingFullConfigurationMenuItem(icon: "command") - // .leadingFullConfigurationMenuItem(name: "All") +// .trailingFullConfigurationMenuItem(icon: "command") +// .leadingFullConfigurationMenuItem(icon: "command") +// .leadingFullConfigurationMenuItem(name: "All") } else { SortFilterMenu(items: $items, onUpdate: performSortAndFilter) } @@ -39,8 +47,9 @@ struct SortFilterExample: View { } .listStyle(PlainListStyle()) - VStack { + HStack { Toggle("Custom Style", isOn: $isCustomStyle) + .fixedSize() .toggleStyle(FioriToggleStyle()) Button("Print") { @@ -80,14 +89,14 @@ struct SortFilterExample: View { func numberOfItems() -> Int { // randomly padding result to mimic impact of filterring for i in 0 ... Int.random(in: 0 ... 5) { - self.sortFilterList.append("Non-SortFilterCFG: element \(i + 1)") + self.sortFilterList.append("padding element \(i + 1)") } return self.sortFilterList.count } func performSortAndFilter() { self.sortFilterList = self.items.joined().map { value(of: $0) } - self.sortFilterButtonLabel = "Sort & Filter (\(self.numberOfItems()))" + self.sortFilterButtonLabel = "CFG (\(self.numberOfItems()))" } } diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift index 10686c555..dccb21bc3 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift @@ -18,18 +18,18 @@ extension View { } func json(item: PickerItem) -> String { - "SortFilterCFG: {name: \(item.name), value: \(item.value)}" + "{name: \(item.name), value: \(item.value)}" } func json(item: SliderItem) -> String { - "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + "{name: \(item.name), value: \(String(describing: item.value))}" } func json(item: DateTimeItem) -> String { - "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + "{name: \(item.name), value: \(String(describing: item.value))}" } func json(item: SwitchItem) -> String { - "SortFilterCFG: {name: \(item.name), value: \(String(describing: item.value))}" + "{name: \(item.name), value: \(String(describing: item.value))}" } } diff --git a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift index 114413063..29c280f06 100644 --- a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift +++ b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift @@ -35,8 +35,9 @@ struct CancellableResettableDialogForm some View { configuration.label .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) - .padding(15) + .padding(8) .font(.body) .fontWeight(.bold) .foregroundStyle(Color.preferredColor(.base2)) @@ -52,6 +53,7 @@ struct ApplyButtonStyle: PrimitiveButtonStyle { .onTapGesture { configuration.trigger() } + .padding([.top, .bottom], UIDevice.current.userInterfaceIdiom == .pad ? 16 : 8) } } @@ -61,7 +63,6 @@ struct CancelResetButtonStyle: PrimitiveButtonStyle { .font(.body) .fontWeight(.bold) .foregroundStyle(Color.preferredColor(.tintColor)) - .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) .onTapGesture { configuration.trigger() } diff --git a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift index c49806354..623656ed6 100644 --- a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift +++ b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift @@ -1,6 +1,7 @@ import SwiftUI import UIKit +/// UI control types supporeted by Sort and Filter configuraiton public enum SortFilterItem: Identifiable, Hashable { public var id: String { switch self { @@ -17,10 +18,43 @@ public enum SortFilterItem: Identifiable, Hashable { } } + /// The type of UI control is used to buid: + /// + /// 1. Sort & Filter's menu item associating with sub-menu items when the number of selectable options is less than 8, + /// or a popover containing a collection of selectable buttons when the number of selectable options is greater than 7. + /// + /// 2. A section of view containing a collection of selectable buttons case picker(item: PickerItem, isShownOnMenu: Bool) + + /// The type of UI control is used to buid: + /// + /// 1. Sort & Filter's menu items associated with one and another; the number of selectable items, mutual exclusion, and + /// empty selection can be controlled + /// + /// 2. A section of view containing a collection of selectable buttons + /// + /// Note: `filterfeedback` is alwasy to be shown on menu bar case filterfeedback(item: PickerItem) + + /// The type of UI control is used to buid: + /// + /// 1. Sort & Filter's menu item to be toggled between selected and unselected states + /// + /// 2. A section of view containing a SwiftUI Toggle with Fiori style case `switch`(item: SwitchItem, isShownOnMenu: Bool) + + /// The type of UI control is used to buid: + /// + /// 1. Sort & Filter's menu item associated with a popover containing a SwiftUI Toggle with Fiori style + /// + /// 2. A section of view containing a SwiftUI Toggle with Fiori style case slider(item: SliderItem, isShownOnMenu: Bool) + + /// The type of UI control is used to buid: + /// + /// 1. Sort & Filter's menu item associated with a popover containing a SwiftUI Canlendar + /// + /// 2. A section of view containing a SwiftUI Canlendar case datetime(item: DateTimeItem, isShownOnMenu: Bool) public var isShownOnMenu: Bool { diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift index 82bcc02e9..5698410b1 100644 --- a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -1,12 +1,7 @@ -/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible -/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` -/// to declare a wrapped property -/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery import SwiftUI -// FIXME: - Implement Fiori style definitions - extension Fiori { enum OptionChip { typealias LeftIcon = EmptyModifier @@ -82,11 +77,11 @@ public struct DefaultOptionChipStyle: OptionChipStyle { let strokeColorUnselected: Color let cornerRadius: CGFloat let spacing: CGFloat - let padding: CGFloat let borderWidth: CGFloat let minHeight: CGFloat + let minTouchHeight: CGFloat - public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) { + public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 16, spacing: CGFloat = 6, borderWidth: CGFloat = 1, minHeight: CGFloat = 44, minTouchHeight: CGFloat = 56) { self.font = font self.foregroundColorSelected = foregroundColorSelected self.foregroundColorUnselected = foregroundColorUnselected @@ -96,9 +91,9 @@ public struct DefaultOptionChipStyle: OptionChipStyle { self.strokeColorUnselected = strokeColorUnselected self.cornerRadius = cornerRadius self.spacing = spacing - self.padding = padding self.borderWidth = borderWidth self.minHeight = minHeight + self.minTouchHeight = minTouchHeight } public func makeBody(configuration: Configuration) -> AnyView { @@ -109,8 +104,7 @@ public struct DefaultOptionChipStyle: OptionChipStyle { } .font(self.font) .foregroundColor(configuration.isSelected ? self.foregroundColorSelected : self.foregroundColorUnselected) - .padding(self.padding) - .frame(maxWidth: .infinity) + .frame(maxWidth: .infinity, minHeight: minHeight) .background( ZStack { RoundedRectangle(cornerRadius: cornerRadius) @@ -119,6 +113,7 @@ public struct DefaultOptionChipStyle: OptionChipStyle { .stroke(configuration.isSelected ? strokeColorSelected : strokeColorUnselected, lineWidth: borderWidth) } ) + .frame(minHeight: minTouchHeight) ) } } @@ -143,9 +138,9 @@ public extension View { self.environment(\.optionChipStyle, style) } - func optionChipStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { + func optionChipStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 16, spacing: CGFloat = 6, borderWidth: CGFloat = 1, minHeight: CGFloat = 44) -> some View { self.environment(\.optionChipStyle, - DefaultOptionChipStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) + DefaultOptionChipStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, borderWidth: borderWidth, minHeight: minHeight)) } } diff --git a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift index f46c06e72..caa12da1b 100644 --- a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift @@ -1,13 +1,10 @@ -/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible -/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` -/// to declare a wrapped property -/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery import SwiftUI extension OptionListPicker: View { public var body: some View { - Grid { + Grid(horizontalSpacing: 16) { ForEach(0 ..< Int(ceil(Double(_valueOptions.count) / 2.0))) { rowIndex in GridRow { OptionChip( diff --git a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift index f1578c7af..04b531038 100644 --- a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift @@ -3,21 +3,31 @@ import SwiftUI extension SliderPicker: View { public var body: some View { VStack { - if let formatter = self._formatter { - HStack { - Text(String(format: formatter, _value.wrappedValue ?? _minimumValue)) - .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) - .foregroundColor(Color.preferredColor(.primaryLabel)) - Spacer() - } + HStack { + Text(String(format: self._formatter ?? NSLocalizedString("Value: %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), _value.wrappedValue ?? _minimumValue)) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() } + .frame(minHeight: 44) + HStack { - Slider(value: .convert(from: _value, ifNilUse: _minimumValue), in: Float(_minimumValue) ... Float(_maximumValue), step: 1.0) - TextField("", value: Binding(get: { _value.wrappedValue ?? _minimumValue }, set: { _value.wrappedValue = $0 }), format: .number) - .frame(width: calcWidth(font: .body)) - .keyboardType(.numberPad) - .textFieldStyle(.roundedBorder) + Slider(value: .convert(from: _value, ifNilUse: _minimumValue), in: Float(_minimumValue) ... Float(_maximumValue), step: 1.0) { + EmptyView() + } minimumValueLabel: { + Text("\(_minimumValue)") + .font(.body) + .fontWeight(.regular) + .foregroundColor(.preferredColor(.primaryLabel)) + } maximumValueLabel: { + Text("\(_maximumValue)") + .font(.body) + .fontWeight(.regular) + .foregroundColor(.preferredColor(.primaryLabel)) + } } + .frame(minHeight: 44) + if let hint = _hint { HStack { Text(hint) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift index 965a2ca90..3f1c7900c 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift @@ -8,6 +8,7 @@ import SwiftUI import FioriThemeManager +/// Dialog titile component public struct SortFilterItemTitle: TitleComponent, View { public let title: String diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift index 2ba29d277..3a7ff21d6 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -305,6 +305,10 @@ struct DateTimeMenuItem: View { displayedComponents: [.date] ) .datePickerStyle(.graphical) + .labelsHidden() + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 + 32: UIScreen.main.bounds.size.width - 16) + .clipped() + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375: UIScreen.main.bounds.size.width) } } .readHeight() diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift index 2fe7b2218..d19587f2c 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -1,11 +1,10 @@ -// -import FioriThemeManager // _SortFilterMenuItemContainer.swift // // // Created by Xu, Charles on 9/25/23. // import SwiftUI +import FioriThemeManager public struct _SortFilterCFGItemContainer { @EnvironmentObject var context: SortFilterContext @@ -20,113 +19,29 @@ public struct _SortFilterCFGItemContainer { extension _SortFilterCFGItemContainer: View { public var body: some View { ScrollView(.vertical, showsIndicators: false) { - VStack(spacing: 20) { + VStack(spacing: 30) { ForEach(0 ..< _items.count) { r in - ForEach(0 ..< _items[r].count) { c in - switch _items[r][c] { - case .picker: - VStack { - HStack { - Text(_items[r][c].picker.name) - .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) - .foregroundColor(Color.preferredColor(.primaryLabel)) - Spacer() - } - OptionListPicker( - value: Binding<[Int]>(get: { _items[r][c].picker.workingValue }, set: { _items[r][c].picker.workingValue = $0 }), - valueOptions: _items[r][c].picker.valueOptions, - onTap: { index in - _items[r][c].picker.onTap(option: _items[r][c].picker.valueOptions[index]) - } - ) - } - .padding() - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(Color.preferredColor(.separator), lineWidth: 2) - ) - case .filterfeedback: - VStack { - HStack { - Text(_items[r][c].filterfeedback.name) - .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) - .foregroundColor(Color.preferredColor(.primaryLabel)) - Spacer() - } - OptionListPicker( - value: Binding<[Int]>(get: { _items[r][c].filterfeedback.workingValue }, set: { _items[r][c].filterfeedback.workingValue = $0 }), - valueOptions: _items[r][c].filterfeedback.valueOptions, - onTap: { index in - _items[r][c].filterfeedback.onTap(option: _items[r][c].filterfeedback.valueOptions[index]) - } - ) - } - .padding() - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(Color.preferredColor(.separator), lineWidth: 2) - ) - - case .switch: - VStack { -// Text(_items[r][c].switch.name) - SwitchPicker(value: Binding(get: { _items[r][c].switch.workingValue }, set: { _items[r][c].switch.workingValue = $0 }), name: _items[r][c].switch.name, hint: nil) - } - .padding() - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(Color.preferredColor(.separator), lineWidth: 2) - ) - case .slider: - VStack { - HStack { - Text(_items[r][c].slider.name) - .font(.headline) - Spacer() - } - SliderPicker( - value: Binding(get: { _items[r][c].slider.workingValue }, set: { _items[r][c].slider.workingValue = $0 }), - formatter: _items[r][c].slider.formatter, - minimumValue: _items[r][c].slider.minimumValue, - maximumValue: _items[r][c].slider.maximumValue - ) - } - .padding() - .overlay( - RoundedRectangle(cornerRadius: 10) - .stroke(Color.preferredColor(.separator), lineWidth: 2) - ) - case .datetime: - VStack { - HStack { - Text(_items[r][c].datetime.name) - .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) - .foregroundColor(Color.preferredColor(.primaryLabel)) - Spacer() - } - - DatePicker( - NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), - selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), - displayedComponents: [.hourAndMinute] - ) - - DatePicker( - _items[r][c].datetime.label, - selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), - displayedComponents: [.date] - ) - .datePickerStyle(.graphical) + VStack { + ForEach(0 ..< _items[r].count) { c in + switch _items[r][c] { + case .picker: + picker(row: r, column: c) + case .filterfeedback: + filterfeedback(row: r, column: c) + case .switch: + switcher(row: r, column: c) + case .slider: + slider(row: r, column: c) + case .datetime: + datetimePicker(row: r, column: c) } - .padding() - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(Color.preferredColor(.separator), lineWidth: 2) - ) } } + .padding([.top], 12) + .background(Color.preferredColor(.secondaryGroupedBackground)) } } + .background(Color.preferredColor(.secondaryBackground)) } .onChange(of: _items) { _ in for item in _items.joined() { @@ -170,4 +85,90 @@ extension _SortFilterCFGItemContainer: View { context.isApplyButtonEnabled = false } } + + func picker(row r: Int, column c: Int) -> some View { + VStack { + HStack { + Text(_items[r][c].picker.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + OptionListPicker( + value: Binding<[Int]>(get: { _items[r][c].picker.workingValue }, set: { _items[r][c].picker.workingValue = $0 }), + valueOptions: _items[r][c].picker.valueOptions, + onTap: { index in + _items[r][c].picker.onTap(option: _items[r][c].picker.valueOptions[index]) + } + ) + } + } + + func filterfeedback(row r: Int, column c: Int) -> some View { + VStack { + HStack { + Text(_items[r][c].filterfeedback.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + OptionListPicker( + value: Binding<[Int]>(get: { _items[r][c].filterfeedback.workingValue }, set: { _items[r][c].filterfeedback.workingValue = $0 }), + valueOptions: _items[r][c].filterfeedback.valueOptions, + onTap: { index in + _items[r][c].filterfeedback.onTap(option: _items[r][c].filterfeedback.valueOptions[index]) + } + ) + } + } + + func switcher(row r: Int, column c: Int) -> some View { + VStack { + SwitchPicker(value: Binding(get: { _items[r][c].switch.workingValue }, set: { _items[r][c].switch.workingValue = $0 }), name: _items[r][c].switch.name, hint: nil) + } + } + + func slider(row r: Int, column c: Int) -> some View { + VStack { + HStack { + Text(_items[r][c].slider.name) + .font(.headline) + Spacer() + } + SliderPicker( + value: Binding(get: { _items[r][c].slider.workingValue }, set: { _items[r][c].slider.workingValue = $0 }), + formatter: _items[r][c].slider.formatter, + minimumValue: _items[r][c].slider.minimumValue, + maximumValue: _items[r][c].slider.maximumValue + ) + } + } + + func datetimePicker(row r: Int, column c: Int) -> some View { + VStack { + HStack { + Text(_items[r][c].datetime.name) + .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + } + + DatePicker( + NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.hourAndMinute] + ) + + DatePicker( + "", + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.date] + ) + .datePickerStyle(.graphical) + .labelsHidden() + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 + 32: UIScreen.main.bounds.size.width - 16) + .clipped() + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375: UIScreen.main.bounds.size.width - 32) + } + } } diff --git a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift index b06d1d098..b7e47094c 100644 --- a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift @@ -84,27 +84,10 @@ public struct FioriToggleStyle: ToggleStyle { .onTapGesture { configuration.isOn.toggle() } } } - .padding(.horizontal) + .frame(minHeight: 44) } } -// public struct DefaultToggleStyle: ToggleStyle { -// public func makeBody(configuration: Configuration) -> some View { -// VStack { -// Toggle(configuration) -// .labelsHidden() -// .foregroundColor(Color.tintColor) -// configuration.label -// .font(.system(size: 22, weight: .semibold)).lineLimit(2) -// .padding() -// .overlay( -// RoundedRectangle(cornerRadius: 10) -// .stroke(configuration.isOn ? Color.green: Color.gray, lineWidth: 1) -// ) -// } -// } -// } - public struct FioriToggleStyleKey: EnvironmentKey { public static var defaultValue: any ToggleStyle = FioriToggleStyle() } diff --git a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings index a353ee961..558aaee13 100644 --- a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings @@ -144,3 +144,6 @@ /* XBUT: calendar time component label */ "Time" = "Time"; + +/* XBUT: slider default value label formatter for integer */ +"Value: %d" = "Value: %d"; From f2439433ca1dd3d7d2d5cabad253993596a804c4 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Tue, 31 Oct 2023 18:56:03 -0700 Subject: [PATCH 06/22] Support disabled reset button and refactor --- .../CancellableResettableForm.swift | 58 +++++++++++++++---- .../DataTypes/SoftFilter+DataType.swift | 37 +++++++++++- .../Models/ModelDefinitions.swift | 4 +- .../Views/SortFilter/SortFilterContext.swift | 6 +- .../SortFilter/SortFilterFullCFG+View.swift | 17 ++---- .../SortFilter/SortFilterMenuItem+View.swift | 19 +++--- .../_SortFilterCFGItemContainer.swift | 33 ++++++----- .../EnvironmentKey+Styles.generated.swift | 4 -- .../EnvironmentValue+Styles.generated.swift | 10 ---- .../API/KPIProgressItem+API.generated.swift | 2 +- .../API/ObjectHeader+API.generated.swift | 6 +- .../SearchableListView+API.generated.swift | 2 +- .../SignatureCaptureView+API.generated.swift | 36 ++++++------ .../API/SingleStep+API.generated.swift | 10 ++-- .../API/SliderPicker+API.generated.swift | 3 +- .../API/SortFilterFullCFG+API.generated.swift | 2 +- .../SortFilterMenuItem+API.generated.swift | 2 +- .../StepProgressIndicator+API.generated.swift | 6 +- 18 files changed, 156 insertions(+), 101 deletions(-) diff --git a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift index 29c280f06..f8f2bc286 100644 --- a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift +++ b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift @@ -42,22 +42,35 @@ struct CancellableResettableDialogForm some View { - configuration.label - .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) - .padding(8) - .font(.body) - .fontWeight(.bold) - .foregroundStyle(Color.preferredColor(.base2)) - .background(RoundedRectangle(cornerRadius: 8).fill(Color.preferredColor(.tintColor))) - .onTapGesture { - configuration.trigger() - } - .padding([.top, .bottom], UIDevice.current.userInterfaceIdiom == .pad ? 16 : 8) + if isEnabled { + configuration.label + .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) + .padding(8) + .font(.body) + .fontWeight(.bold) + .foregroundStyle(Color.preferredColor(.base2)) + .background(RoundedRectangle(cornerRadius: 8).fill(Color.preferredColor(.tintColor))) + .onTapGesture { + configuration.trigger() + } + .padding([.top, .bottom], UIDevice.current.userInterfaceIdiom == .pad ? 16 : 8) + } else { + configuration.label + .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) + .padding(8) + .font(.body) + .fontWeight(.bold) + .foregroundStyle(Color.preferredColor(.grey1)) + .background(RoundedRectangle(cornerRadius: 8).fill(Color.preferredColor(.grey5))) + .padding([.top, .bottom], UIDevice.current.userInterfaceIdiom == .pad ? 16 : 8) + } } } -struct CancelResetButtonStyle: PrimitiveButtonStyle { +struct CancelButtonStyle: PrimitiveButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.body) @@ -69,6 +82,27 @@ struct CancelResetButtonStyle: PrimitiveButtonStyle { } } +struct ResetButtonStyle: PrimitiveButtonStyle { + @Environment(\.isEnabled) private var isEnabled: Bool + + func makeBody(configuration: Configuration) -> some View { + if isEnabled { + configuration.label + .font(.body) + .fontWeight(.bold) + .foregroundStyle(Color.preferredColor(.tintColor)) + .onTapGesture { + configuration.trigger() + } + } else { + configuration.label + .font(.body) + .fontWeight(.bold) + .foregroundStyle(Color.preferredColor(.separator)) + } + } +} + #Preview { VStack { Spacer() diff --git a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift index 623656ed6..fe483867f 100644 --- a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift +++ b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift @@ -210,6 +210,10 @@ public struct PickerItem: Identifiable, Equatable { var isChanged: Bool { self.value != self.workingValue } + + var isOriginal: Bool { + self.workingValue == self.originalValue + } } /// (value: Bool, keyName: String) @@ -252,6 +256,10 @@ public struct SwitchItem: Identifiable, Equatable { var isChanged: Bool { self.value != self.workingValue } + + var isOriginal: Bool { + self.workingValue == self.originalValue + } } /// (value: Float, minimumValue: Float, maximumValue: Float, keyName: String?) @@ -259,7 +267,7 @@ public struct SliderItem: Identifiable, Equatable { public var id = UUID().uuidString public var name: String - + public var value: Int? var workingValue: Int? let originalValue: Int? @@ -292,7 +300,7 @@ public struct SliderItem: Identifiable, Equatable { mutating func apply() { self.value = self.workingValue } - + var isChecked: Bool { self.value != nil } @@ -311,6 +319,10 @@ public struct SliderItem: Identifiable, Equatable { var isChanged: Bool { self.value != self.workingValue } + + var isOriginal: Bool { + self.workingValue == self.originalValue + } } public struct DateTimeItem: Equatable, Hashable { @@ -331,7 +343,7 @@ public struct DateTimeItem: Equatable, Hashable { self.formatter = formatter self.icon = icon } - + mutating func reset() { self.workingValue = self.originalValue } @@ -368,6 +380,10 @@ public struct DateTimeItem: Equatable, Hashable { var isChanged: Bool { self.value != self.workingValue } + + var isOriginal: Bool { + self.workingValue == self.originalValue + } } extension SortFilterItem { @@ -486,6 +502,21 @@ extension SortFilterItem { } } + public var isOriginal: Bool { + switch self { + case .picker(let item, _): + return item.isOriginal + case .filterfeedback(let item): + return item.isOriginal + case .switch(let item, _): + return item.isOriginal + case .datetime(let item, _): + return item.isOriginal + case .slider(let item, _): + return item.isOriginal + } + } + public mutating func cancel() { switch self { case .picker(var item, _): diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index b625bb3f0..909a5995f 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -445,7 +445,7 @@ public protocol SortFilterMenuModel: AnyObject { var onUpdate: (() -> Void)? { get set } } -// sourcery: virtualPropActionHelper = "@State var context: SortFilterContext = SortFilterContext()" +// sourcery: virtualPropActionHelper = "@StateObject var context: SortFilterContext = SortFilterContext()" // sourcery: add_env_props = "dismiss" // sourcery: generated_component_composite public protocol SortFilterFullCFGModel: AnyObject, TitleComponent { @@ -471,7 +471,7 @@ public protocol SortFilterFullCFGModel: AnyObject, TitleComponent { } // sourcery: add_env_props = "sortFilterMenuItemStyle" -// sourcery: virtualPropActionHelper = "@State var context: SortFilterContext = SortFilterContext()" +// sourcery: virtualPropActionHelper = "@StateObject var context: SortFilterContext = SortFilterContext()" // sourcery: generated_component_composite public protocol SortFilterMenuItemModel: LeftIconComponent, TitleComponent, RightIconComponent { // sourcery: no_view diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift index c87a7c066..e5af8e86d 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterContext.swift @@ -1,6 +1,6 @@ import SwiftUI -class SortFilterContext: ObservableObject { +class SortFilterContext: Equatable, ObservableObject { @Published public var isResetButtonEnabled: Bool = false @Published public var isApplyButtonEnabled: Bool = false @@ -11,4 +11,8 @@ class SortFilterContext: ObservableObject { @Published public var handleDismiss: (() -> Void)? public init() {} + + static func == (lhs: SortFilterContext, rhs: SortFilterContext) -> Bool { + return lhs.isResetButtonEnabled == rhs.isResetButtonEnabled && lhs.isApplyButtonEnabled == rhs.isApplyButtonEnabled + } } diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift index 908206645..6adc07366 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift @@ -43,42 +43,33 @@ extension SortFilterFullCFG: View { .simultaneousGesture( TapGesture() .onEnded { _ in - print("...Cancel...") context.handleCancel?() - context.isApplyButtonEnabled = false - context.isResetButtonEnabled = false dismiss() } ) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(CancelButtonStyle()) } resetAction: { resetAction .simultaneousGesture( TapGesture() .onEnded { _ in - print("...Reset...") context.handleReset?() - context.isApplyButtonEnabled = false - context.isResetButtonEnabled = false - dismiss() } ) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(ResetButtonStyle()) + .environment(\.isEnabled, context.isResetButtonEnabled) } applyAction: { applyAction .simultaneousGesture( TapGesture() .onEnded { _ in - print("...Apply...") context.handleApply?() - context.isApplyButtonEnabled = false - context.isResetButtonEnabled = false _onUpdate?() dismiss() } ) - .buttonStyle(ApplyButtonStyle()) + .environment(\.isEnabled, true) } components: { _items .environmentObject(context) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift index 3a7ff21d6..1f94576c9 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -98,12 +98,13 @@ struct SliderMenuItem: View { item.cancel() isSheetVisible.toggle() }) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(CancelButtonStyle()) } resetAction: { Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { item.reset() }) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(ResetButtonStyle()) + .disabled(self.item.isOriginal) } applyAction: { Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { item.apply() @@ -161,12 +162,13 @@ struct PickerMenuItem: View { item.cancel() isSheetVisible.toggle() }) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(CancelButtonStyle()) } resetAction: { Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { item.reset() }) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(ResetButtonStyle()) + .disabled(self.item.isOriginal) } applyAction: { Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { item.apply() @@ -271,12 +273,13 @@ struct DateTimeMenuItem: View { item.cancel() isSheetVisible.toggle() }) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(CancelButtonStyle()) } resetAction: { Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { item.reset() }) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(ResetButtonStyle()) + .disabled(self.item.isOriginal) } applyAction: { Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { item.apply() @@ -414,14 +417,14 @@ struct FullCFGMenuItem: View { onUpdate() isSheetVisible.toggle() }) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(CancelButtonStyle()) }, resetAction: { Action(actionText: NSLocalizedString("Reset", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { // item.cancel() isSheetVisible.toggle() }) - .buttonStyle(CancelResetButtonStyle()) + .buttonStyle(ResetButtonStyle()) }, applyAction: { Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: { diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift index d19587f2c..a3df6fd9c 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -44,18 +44,10 @@ extension _SortFilterCFGItemContainer: View { .background(Color.preferredColor(.secondaryBackground)) } .onChange(of: _items) { _ in - for item in _items.joined() { - if item.isChanged { - context.isResetButtonEnabled = true - context.isApplyButtonEnabled = true - return - } - } - context.isResetButtonEnabled = true + checkUpdateButtonState() } .onAppear { context.handleCancel = { - print("....cancel in context...") for r in 0 ..< _items.count { for c in 0 ..< _items[r].count { _items[r][c].cancel() @@ -64,7 +56,6 @@ extension _SortFilterCFGItemContainer: View { } context.handleReset = { - print("....reset in context...") for r in 0 ..< _items.count { for c in 0 ..< _items[r].count { _items[r][c].reset() @@ -73,7 +64,6 @@ extension _SortFilterCFGItemContainer: View { } context.handleApply = { - print("....apply in context...") for r in 0 ..< _items.count { for c in 0 ..< _items[r].count { _items[r][c].apply() @@ -81,9 +71,26 @@ extension _SortFilterCFGItemContainer: View { } } - context.isResetButtonEnabled = false - context.isApplyButtonEnabled = false + checkUpdateButtonState() + } + } + + func checkUpdateButtonState() { + var isApplyButtonEnabled = false + var isResetButtonEnabled = false + + for item in _items.joined() { + if !isApplyButtonEnabled && item.isChanged { + isApplyButtonEnabled = true + print("Enable apply button.") + } + if !isResetButtonEnabled && !item.isOriginal { + isResetButtonEnabled = true + print("Enable reset button.") + } } + context.isApplyButtonEnabled = isApplyButtonEnabled + context.isResetButtonEnabled = isResetButtonEnabled } func picker(row r: Int, column c: Int) -> some View { diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift index 926dab3f0..98937a7c0 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift @@ -166,10 +166,6 @@ struct ProgressIndicatorTextModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct FormatterModifierKey: EnvironmentKey { - public static let defaultValue = AnyViewModifier { $0 } -} - struct TextInputValueModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift index 90de927bd..98e973869 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift @@ -209,11 +209,6 @@ extension EnvironmentValues { set { self[ProgressIndicatorTextModifierKey.self] = newValue } } - public var formatterModifier: AnyViewModifier { - get { return self[FormatterModifierKey.self] } - set { self[FormatterModifierKey.self] = newValue } - } - public var textInputValueModifier: AnyViewModifier { get { return self[TextInputValueModifierKey.self] } set { self[TextInputValueModifierKey.self] = newValue } @@ -518,11 +513,6 @@ public extension View { self.environment(\.progressIndicatorTextModifier, AnyViewModifier(transform)) } - @ViewBuilder - func formatterModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { - self.environment(\.formatterModifier, AnyViewModifier(transform)) - } - @ViewBuilder func textInputValueModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.textInputValueModifier, AnyViewModifier(transform)) diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/KPIProgressItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/KPIProgressItem+API.generated.swift index ecfd596a2..65ed06049 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/KPIProgressItem+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/KPIProgressItem+API.generated.swift @@ -13,8 +13,8 @@ public struct KPIProgressItem { let _fraction: Double? let _subtitle: Subtitle let _footnote: Footnote - var action: (() -> Void)? = nil @State var isPressed: Bool = false + var action: (() -> Void)? = nil private var isModelInit: Bool = false private var isKpiNil: Bool = false private var isSubtitleNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift index a62ed7686..ad0761dc8 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift @@ -26,11 +26,11 @@ public struct ObjectHeader { let _cancelAction: CancelActionView let _doneAction: DoneActionView - var dataHandler: (() -> ())? = nil var contentView: AnyView? = nil var isTopLevel: Bool = true + var dataHandler: (() -> ())? = nil private var isModelInit: Bool = false private var isCancelActionNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift index f5ebd79cc..2b8607d5a 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift @@ -19,32 +19,32 @@ public struct SignatureCaptureView Void)? let _onDelete: (() -> Void)? - var signatureLineColor = Color.preferredColor(.quarternaryLabel) + public private(set) var _heightDidChangePublisher = CurrentValueSubject(0) @State var drawings = [Drawing]() - var strokeColor = Color.preferredColor(.primaryLabel) - @State var fullSignatureImage: UIImage? + @State var isEditing = false + var watermarkText: String? + var addsTimestampInImage: Bool = false + var titleFont = Font.fiori(forTextStyle: .subheadline).weight(.semibold) + var xmarkColor = Color.preferredColor(.quarternaryLabel) + var strokeWidth: CGFloat = 3.0 + @State var isSaved = false + var _drawingViewMaxHeight: CGFloat? + var appliesTintColorToImage = true var hidesSignatureLine = false + var cropsImage = false let _drawingViewMinHeight: CGFloat = 256 - var watermarkTextFont: UIFont = .preferredFont(forTextStyle: .caption1) var watermarkTextAlignment: NSTextAlignment = .natural - @State var isSaved = false - var drawingViewBackgroundColor = Color.preferredColor(.primaryBackground) + @State var fullSignatureImage: UIImage? var timestampFormatter: DateFormatter? - var cropsImage = false - @State var currentDrawing = Drawing() - @State var isEditing = false - var xmarkColor = Color.preferredColor(.quarternaryLabel) - public private(set) var _heightDidChangePublisher = CurrentValueSubject(0) - var addsTimestampInImage: Bool = false - var _drawingViewMaxHeight: CGFloat? + var watermarkTextFont: UIFont = .preferredFont(forTextStyle: .caption1) + var strokeColor = Color.preferredColor(.primaryLabel) var watermarkTextColor: Color = .preferredColor(.tertiaryLabel) - var titleColor = Color.preferredColor(.primaryLabel) - var titleFont = Font.fiori(forTextStyle: .subheadline).weight(.semibold) var hidesXmark = false - var watermarkText: String? - var strokeWidth: CGFloat = 3.0 + var signatureLineColor = Color.preferredColor(.quarternaryLabel) @State var isReenterTapped = false - var appliesTintColorToImage = true + var drawingViewBackgroundColor = Color.preferredColor(.primaryBackground) + @State var currentDrawing = Drawing() + var titleColor = Color.preferredColor(.primaryLabel) private var isModelInit: Bool = false private var isTitleNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift index bbc6634b1..9457d134e 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift @@ -14,15 +14,15 @@ public struct SingleStep var _formatter: String? = nil diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift index 52b58f7b1..a2303d8f0 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterFullCFG+API.generated.swift @@ -16,7 +16,7 @@ public struct SortFilterFullCFG Void)? - @State var context: SortFilterContext = SortFilterContext() + @StateObject var context: SortFilterContext = SortFilterContext() private var isModelInit: Bool = false private var isCancelActionNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift index 5469109c4..e4e9e55da 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift @@ -12,7 +12,7 @@ public struct SortFilterMenuItem { let _title: Title let _rightIcon: RightIcon let _isSelected: Bool - @State var context: SortFilterContext = SortFilterContext() + @StateObject var context: SortFilterContext = SortFilterContext() private var isModelInit: Bool = false private var isLeftIconNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift index d24e5dfb9..e42687a7f 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift @@ -13,11 +13,11 @@ public struct StepProgressIndicator Date: Wed, 1 Nov 2023 14:35:46 -0700 Subject: [PATCH 07/22] renaming --- .../SortFilter/SortFilterExample.swift | 8 +-- .../Models/ModelDefinitions.swift | 6 +- ...Menu+View.swift => FeedbackBar+View.swift} | 4 +- .../SortFilter/SortFilterMenuItem+View.swift | 57 +++++++------------ .../Views/SortFilter/SortFilterStyle.swift | 26 --------- ...G+View.swift => SortFilterView+View.swift} | 4 +- .../EnvironmentKey+Styles.generated.swift | 8 +-- .../EnvironmentValue+Styles.generated.swift | 20 +++---- ... => FilterFeedbackBar+API.generated.swift} | 10 ++-- ...FilterFeedbackBarItem+API.generated.swift} | 18 +++--- .../API/ObjectHeader+API.generated.swift | 6 +- .../SearchableListView+API.generated.swift | 4 +- .../SignatureCaptureView+API.generated.swift | 36 ++++++------ .../API/SingleStep+API.generated.swift | 10 ++-- ...ift => SortFilterView+API.generated.swift} | 26 ++++----- .../StepProgressIndicator+API.generated.swift | 4 +- ...=> FilterFeedbackBar+View.generated.swift} | 16 +++--- ...ilterFeedbackBarItem+View.generated.swift} | 16 +++--- ...ft => SortFilterView+View.generated.swift} | 16 +++--- ...=> FilterFeedbackBar+Init.generated.swift} | 0 ...ilterFeedbackBarItem+Init.generated.swift} | 6 +- ...ft => SortFilterView+Init.generated.swift} | 14 ++--- ...edbackBarModel+Extensions.generated.swift} | 2 +- ...ilterViewModel+Extensions.generated.swift} | 2 +- 24 files changed, 140 insertions(+), 179 deletions(-) rename Sources/FioriSwiftUICore/Views/SortFilter/{SortFilterMenu+View.swift => FeedbackBar+View.swift} (91%) delete mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift rename Sources/FioriSwiftUICore/Views/SortFilter/{SortFilterFullCFG+View.swift => SortFilterView+View.swift} (97%) rename Sources/FioriSwiftUICore/_generated/ViewModels/API/{SortFilterMenu+API.generated.swift => FilterFeedbackBar+API.generated.swift} (72%) rename Sources/FioriSwiftUICore/_generated/ViewModels/API/{SortFilterMenuItem+API.generated.swift => FilterFeedbackBarItem+API.generated.swift} (70%) rename Sources/FioriSwiftUICore/_generated/ViewModels/API/{SortFilterFullCFG+API.generated.swift => SortFilterView+API.generated.swift} (85%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/{SortFilterMenu+View.generated.swift => FilterFeedbackBar+View.generated.swift} (78%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/{SortFilterMenuItem+View.generated.swift => FilterFeedbackBarItem+View.generated.swift} (80%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/{SortFilterFullCFG+View.generated.swift => SortFilterView+View.generated.swift} (84%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/{SortFilterMenu+Init.generated.swift => FilterFeedbackBar+Init.generated.swift} (100%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/{SortFilterMenuItem+Init.generated.swift => FilterFeedbackBarItem+Init.generated.swift} (80%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/{SortFilterFullCFG+Init.generated.swift => SortFilterView+Init.generated.swift} (84%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/{SortFilterMenuModel+Extensions.generated.swift => FilterFeedbackBarModel+Extensions.generated.swift} (80%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/{SortFilterFullCFGModel+Extensions.generated.swift => SortFilterViewModel+Extensions.generated.swift} (90%) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift index aad9617f0..17dc77428 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -30,14 +30,14 @@ struct SortFilterExample: View { var body: some View { VStack { if isCustomStyle { - SortFilterMenu(items: $items, onUpdate: performSortAndFilter) + FilterFeedbackBar(items: $items, onUpdate: performSortAndFilter) .sortFilterMenuItemStyle(font: .subheadline, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) // .trailingFullConfigurationMenuItem(icon: "command") // .leadingFullConfigurationMenuItem(icon: "command") // .leadingFullConfigurationMenuItem(name: "All") } else { - SortFilterMenu(items: $items, onUpdate: performSortAndFilter) + FilterFeedbackBar(items: $items, onUpdate: performSortAndFilter) } List { @@ -66,14 +66,14 @@ struct SortFilterExample: View { } .popover(isPresented: $isShowingFullCFG, arrowEdge: .leading) { if isCustomStyle { - SortFilterFullCFG( + SortFilterView( title: "Configuration", items: $items, onUpdate: performSortAndFilter ) .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) } else { - SortFilterFullCFG( + SortFilterView( title: "Configuration", items: $items, onUpdate: performSortAndFilter diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index 909a5995f..ccc45c8a9 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -435,7 +435,7 @@ public protocol StepProgressIndicatorModel: AnyObject { } // sourcery: generated_component_composite -public protocol SortFilterMenuModel: AnyObject { +public protocol FilterFeedbackBarModel: AnyObject { // sourcery: bindingProperty // sourcery: backingComponent=_SortFilterMenuItemContainer var items: [[SortFilterItem]] { get set } @@ -448,7 +448,7 @@ public protocol SortFilterMenuModel: AnyObject { // sourcery: virtualPropActionHelper = "@StateObject var context: SortFilterContext = SortFilterContext()" // sourcery: add_env_props = "dismiss" // sourcery: generated_component_composite -public protocol SortFilterFullCFGModel: AnyObject, TitleComponent { +public protocol SortFilterViewModel: AnyObject, TitleComponent { // sourcery: bindingProperty // sourcery: backingComponent=_SortFilterCFGItemContainer var items: [[SortFilterItem]] { get set } @@ -473,7 +473,7 @@ public protocol SortFilterFullCFGModel: AnyObject, TitleComponent { // sourcery: add_env_props = "sortFilterMenuItemStyle" // sourcery: virtualPropActionHelper = "@StateObject var context: SortFilterContext = SortFilterContext()" // sourcery: generated_component_composite -public protocol SortFilterMenuItemModel: LeftIconComponent, TitleComponent, RightIconComponent { +public protocol FilterFeedbackBarItemModel: LeftIconComponent, TitleComponent, RightIconComponent { // sourcery: no_view var isSelected: Bool { get } } diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift similarity index 91% rename from Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift rename to Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift index fb4afcfc2..a48f1dc51 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenu+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift @@ -1,7 +1,7 @@ import SwiftUI extension Fiori { - enum SortFilterMenu { + enum FilterFeedbackBar { typealias Items = EmptyModifier typealias ItemsCumulative = EmptyModifier @@ -10,7 +10,7 @@ extension Fiori { } } -extension SortFilterMenu: View { +extension FilterFeedbackBar: View { public var body: some View { items .onModelUpdateAppCallback(_onUpdate!) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift index 1f94576c9..c75baa660 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -1,7 +1,7 @@ import SwiftUI extension Fiori { - enum SortFilterMenuItem { + enum FilterFeedbackBarItem { typealias LeftIcon = EmptyModifier typealias LeftIconCumulative = EmptyModifier typealias Title = EmptyModifier @@ -18,25 +18,12 @@ extension Fiori { } } -extension SortFilterMenuItem: View { +extension FilterFeedbackBarItem: View { public var body: some View { sortFilterMenuItemStyle.makeBody(configuration: SortFilterMenuItemConfiguration(leftIcon: AnyView(_leftIcon), title: AnyView(_title), isSelected: _isSelected, rightIcon: AnyView(_rightIcon))).typeErased } } -/* - // FIXME: - Implement SortFilterMenuItem specific LibraryContentProvider - - @available(iOS 14.0, macOS 11.0, *) - struct SortFilterMenuItemLibraryContent: LibraryContentProvider { - @LibraryContentBuilder - var views: [LibraryItem] { - LibraryItem(SortFilterMenuItem(model: LibraryPreviewData.Person.laurelosborn), - category: .control) - } - } - */ - private extension View { func icon(name: String?, isVisible: Bool) -> Image? { if isVisible { @@ -60,7 +47,7 @@ struct FilterFeedbackMenuItem: View { var body: some View { Group { ForEach($item.valueOptions.wrappedValue, id: \.self) { opt in - SortFilterMenuItem(leftIcon: item.isOptionSelected(opt) ? icon(name: item.icon, isVisible: true) : nil, title: opt, isSelected: item.isOptionSelected(opt)) + FilterFeedbackBarItem(leftIcon: item.isOptionSelected(opt) ? icon(name: item.icon, isVisible: true) : nil, title: opt, isSelected: item.isOptionSelected(opt)) .onTapGesture { item.onTap(option: opt) item.apply() @@ -86,7 +73,7 @@ struct SliderMenuItem: View { } var body: some View { - SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + FilterFeedbackBarItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) .onTapGesture { isSheetVisible.toggle() } @@ -150,7 +137,7 @@ struct PickerMenuItem: View { @ViewBuilder var button: some View { - SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + FilterFeedbackBarItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) .onTapGesture { isSheetVisible.toggle() } @@ -213,7 +200,7 @@ struct PickerMenuItem: View { } } } label: { - SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, isSelected: item.isChecked) + FilterFeedbackBarItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, isSelected: item.isChecked) } } } @@ -261,7 +248,7 @@ struct DateTimeMenuItem: View { } var body: some View { - SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) + FilterFeedbackBarItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: item.isChecked) .onTapGesture { isSheetVisible.toggle() } @@ -339,7 +326,7 @@ struct SwitchMenuItem: View { } var body: some View { - SortFilterMenuItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, isSelected: item.isChecked) + FilterFeedbackBarItem(leftIcon: icon(name: item.icon, isVisible: true), title: item.name, isSelected: item.isChecked) .onTapGesture { if item.value != nil { item.workingValue?.toggle() @@ -395,12 +382,12 @@ struct FullCFGMenuItem: View { } var body: some View { - SortFilterMenuItem(leftIcon: icon(name: fullCFGButton.icon, isVisible: true), title: fullCFGButton.name ?? "", isSelected: true) + FilterFeedbackBarItem(leftIcon: icon(name: fullCFGButton.icon, isVisible: true), title: fullCFGButton.name ?? "", isSelected: true) .onTapGesture { isSheetVisible.toggle() } .popover(isPresented: $isSheetVisible, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { - SortFilterFullCFG( + SortFilterView( title: { if let title = fullCFGButton.name { Text(title) @@ -442,28 +429,28 @@ struct FullCFGMenuItem: View { VStack { Spacer() - SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) - SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) - SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) - SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) - SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) - SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) + FilterFeedbackBarItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + FilterFeedbackBarItem(leftIcon: Image(systemName: "airplane"), title: "Airplane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + FilterFeedbackBarItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + FilterFeedbackBarItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + FilterFeedbackBarItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) + FilterFeedbackBarItem(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) Spacer() - SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + FilterFeedbackBarItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) .sortFilterMenuItemStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) - SortFilterMenuItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + FilterFeedbackBarItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) .sortFilterMenuItemStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) .sortFilterMenuItemStyle(cornerRadius: 16) - SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) + FilterFeedbackBarItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) .sortFilterMenuItemStyle(fillColorSelected: .yellow) - SortFilterMenuItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) + FilterFeedbackBarItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) .sortFilterMenuItemStyle(fillColorUnselected: .gray) - SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) + FilterFeedbackBarItem(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) .sortFilterMenuItemStyle(cornerRadius: 20) - SortFilterMenuItem(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) + FilterFeedbackBarItem(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) .sortFilterMenuItemStyle(cornerRadius: 20) Spacer() diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift deleted file mode 100644 index e4ca740a5..000000000 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterStyle.swift +++ /dev/null @@ -1,26 +0,0 @@ -import FioriThemeManager -import SwiftUI - -public final class SortFilterStyle { - public static var instance = SortFilterStyle(iconForCheckedItem: Image(fioriName: "fiori.accept")) - - let iconForCheckedItem: Image? - let iconForMenuItem: Image - let foregroundColorForSelectedItem: Color - let backgroundColorForSelectedColor: Color - let foregroundColorForUnselectedItem: Color - let backgroundColorForUnselectedColor: Color - - public static var shared: SortFilterStyle { - instance - } - - public init(iconForCheckedItem: Image? = nil, iconForMenuItem: Image? = nil, foregroundColorForSelectedItem: Color? = nil, backgroundColorForSelectedColor: Color? = nil, foregroundColorForUnselectedItem: Color? = nil, backgroundColorForUnselectedColor: Color? = nil) { - self.iconForCheckedItem = iconForCheckedItem - self.iconForMenuItem = iconForMenuItem ?? Image(fioriName: "fiori.navigation.down.arrow")! - self.foregroundColorForSelectedItem = foregroundColorForSelectedItem ?? .preferredColor(.tintColor) - self.backgroundColorForSelectedColor = backgroundColorForSelectedColor ?? .preferredColor(.primaryBackground) - self.foregroundColorForUnselectedItem = foregroundColorForUnselectedItem ?? .preferredColor(.separator) - self.backgroundColorForUnselectedColor = foregroundColorForUnselectedItem ?? .preferredColor(.tertiaryFill) - } -} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterView+View.swift similarity index 97% rename from Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift rename to Sources/FioriSwiftUICore/Views/SortFilter/SortFilterView+View.swift index 6adc07366..0dde8bf1d 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterFullCFG+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterView+View.swift @@ -1,7 +1,7 @@ import SwiftUI extension Fiori { - enum SortFilterFullCFG { + enum SortFilterView { struct Title: ViewModifier { func body(content: Content) -> some View { content @@ -34,7 +34,7 @@ extension Fiori { } } -extension SortFilterFullCFG: View { +extension SortFilterView: View { public var body: some View { CancellableResettableDialogForm { title diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift index 98937a7c0..09eb4a960 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentKey+Styles.generated.swift @@ -186,6 +186,10 @@ struct CancelActionModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } +struct ItemsModifierKey: EnvironmentKey { + public static let defaultValue = AnyViewModifier { $0 } +} + struct ProgressIndicatorModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } @@ -214,10 +218,6 @@ struct NodeModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } -struct ItemsModifierKey: EnvironmentKey { - public static let defaultValue = AnyViewModifier { $0 } -} - struct ResetActionModifierKey: EnvironmentKey { public static let defaultValue = AnyViewModifier { $0 } } diff --git a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift index 98e973869..63d866699 100644 --- a/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/Components/EnvironmentValue+Styles.generated.swift @@ -234,6 +234,11 @@ extension EnvironmentValues { set { self[CancelActionModifierKey.self] = newValue } } + public var itemsModifier: AnyViewModifier { + get { return self[ItemsModifierKey.self] } + set { self[ItemsModifierKey.self] = newValue } + } + public var progressIndicatorModifier: AnyViewModifier { get { return self[ProgressIndicatorModifierKey.self] } set { self[ProgressIndicatorModifierKey.self] = newValue } @@ -269,11 +274,6 @@ extension EnvironmentValues { set { self[NodeModifierKey.self] = newValue } } - public var itemsModifier: AnyViewModifier { - get { return self[ItemsModifierKey.self] } - set { self[ItemsModifierKey.self] = newValue } - } - public var resetActionModifier: AnyViewModifier { get { return self[ResetActionModifierKey.self] } set { self[ResetActionModifierKey.self] = newValue } @@ -538,6 +538,11 @@ public extension View { self.environment(\.cancelActionModifier, AnyViewModifier(transform)) } + @ViewBuilder + func itemsModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { + self.environment(\.itemsModifier, AnyViewModifier(transform)) + } + @ViewBuilder func progressIndicatorModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.progressIndicatorModifier, AnyViewModifier(transform)) @@ -573,11 +578,6 @@ public extension View { self.environment(\.nodeModifier, AnyViewModifier(transform)) } - @ViewBuilder - func itemsModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { - self.environment(\.itemsModifier, AnyViewModifier(transform)) - } - @ViewBuilder func resetActionModifier(_ transform: @escaping (AnyViewModifier.Content) -> V) -> some View { self.environment(\.resetActionModifier, AnyViewModifier(transform)) diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBar+API.generated.swift similarity index 72% rename from Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBar+API.generated.swift index 97352cf88..9b92d59b7 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenu+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBar+API.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -public struct SortFilterMenu { +public struct FilterFeedbackBar { @Environment(\.itemsModifier) private var itemsModifier var _items: Items @@ -22,18 +22,18 @@ public struct SortFilterMenu { @ViewBuilder var items: some View { if isModelInit { - _items.modifier(itemsModifier.concat(Fiori.SortFilterMenu.items).concat(Fiori.SortFilterMenu.itemsCumulative)) + _items.modifier(itemsModifier.concat(Fiori.FilterFeedbackBar.items).concat(Fiori.FilterFeedbackBar.itemsCumulative)) } else { - _items.modifier(itemsModifier.concat(Fiori.SortFilterMenu.items)) + _items.modifier(itemsModifier.concat(Fiori.FilterFeedbackBar.items)) } } } -extension SortFilterMenu where Items == _SortFilterMenuItemContainer { +extension FilterFeedbackBar where Items == _SortFilterMenuItemContainer { - public init(model: SortFilterMenuModel) { + public init(model: FilterFeedbackBarModel) { self.init(items: Binding<[[SortFilterItem]]>(get: { model.items }, set: { model.items = $0 }), onUpdate: model.onUpdate) } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarItem+API.generated.swift similarity index 70% rename from Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarItem+API.generated.swift index e4e9e55da..59ab4fe5f 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SortFilterMenuItem+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarItem+API.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -public struct SortFilterMenuItem { +public struct FilterFeedbackBarItem { @Environment(\.leftIconModifier) private var leftIconModifier @Environment(\.titleModifier) private var titleModifier @Environment(\.rightIconModifier) private var rightIconModifier @@ -32,23 +32,23 @@ public struct SortFilterMenuItem { @ViewBuilder var leftIcon: some View { if isModelInit { - _leftIcon.modifier(leftIconModifier.concat(Fiori.SortFilterMenuItem.leftIcon).concat(Fiori.SortFilterMenuItem.leftIconCumulative)) + _leftIcon.modifier(leftIconModifier.concat(Fiori.FilterFeedbackBarItem.leftIcon).concat(Fiori.FilterFeedbackBarItem.leftIconCumulative)) } else { - _leftIcon.modifier(leftIconModifier.concat(Fiori.SortFilterMenuItem.leftIcon)) + _leftIcon.modifier(leftIconModifier.concat(Fiori.FilterFeedbackBarItem.leftIcon)) } } @ViewBuilder var title: some View { if isModelInit { - _title.modifier(titleModifier.concat(Fiori.SortFilterMenuItem.title).concat(Fiori.SortFilterMenuItem.titleCumulative)) + _title.modifier(titleModifier.concat(Fiori.FilterFeedbackBarItem.title).concat(Fiori.FilterFeedbackBarItem.titleCumulative)) } else { - _title.modifier(titleModifier.concat(Fiori.SortFilterMenuItem.title)) + _title.modifier(titleModifier.concat(Fiori.FilterFeedbackBarItem.title)) } } @ViewBuilder var rightIcon: some View { if isModelInit { - _rightIcon.modifier(rightIconModifier.concat(Fiori.SortFilterMenuItem.rightIcon).concat(Fiori.SortFilterMenuItem.rightIconCumulative)) + _rightIcon.modifier(rightIconModifier.concat(Fiori.FilterFeedbackBarItem.rightIcon).concat(Fiori.FilterFeedbackBarItem.rightIconCumulative)) } else { - _rightIcon.modifier(rightIconModifier.concat(Fiori.SortFilterMenuItem.rightIcon)) + _rightIcon.modifier(rightIconModifier.concat(Fiori.FilterFeedbackBarItem.rightIcon)) } } @@ -61,11 +61,11 @@ public struct SortFilterMenuItem { } } -extension SortFilterMenuItem where LeftIcon == _ConditionalContent, +extension FilterFeedbackBarItem where LeftIcon == _ConditionalContent, Title == Text, RightIcon == _ConditionalContent { - public init(model: SortFilterMenuItemModel) { + public init(model: FilterFeedbackBarItemModel) { self.init(leftIcon: model.leftIcon, title: model.title, rightIcon: model.rightIcon, isSelected: model.isSelected) } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift index ad0761dc8..2a4d3ec70 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift @@ -25,12 +25,12 @@ public struct ObjectHeader { let _cancelAction: CancelActionView let _doneAction: DoneActionView - var contentView: AnyView? = nil - var isTopLevel: Bool = true var dataHandler: (() -> ())? = nil + var isTopLevel: Bool = true + var contentView: AnyView? = nil private var isModelInit: Bool = false private var isCancelActionNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift index 2b8607d5a..bb6c2fe3e 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift @@ -19,32 +19,32 @@ public struct SignatureCaptureView Void)? let _onDelete: (() -> Void)? - public private(set) var _heightDidChangePublisher = CurrentValueSubject(0) - @State var drawings = [Drawing]() + var hidesXmark = false + var signatureLineColor = Color.preferredColor(.quarternaryLabel) + @State var isSaved = false + @State var currentDrawing = Drawing() @State var isEditing = false + var watermarkTextColor: Color = .preferredColor(.tertiaryLabel) + var cropsImage = false var watermarkText: String? - var addsTimestampInImage: Bool = false - var titleFont = Font.fiori(forTextStyle: .subheadline).weight(.semibold) var xmarkColor = Color.preferredColor(.quarternaryLabel) - var strokeWidth: CGFloat = 3.0 - @State var isSaved = false - var _drawingViewMaxHeight: CGFloat? var appliesTintColorToImage = true - var hidesSignatureLine = false - var cropsImage = false - let _drawingViewMinHeight: CGFloat = 256 - var watermarkTextAlignment: NSTextAlignment = .natural - @State var fullSignatureImage: UIImage? + var titleFont = Font.fiori(forTextStyle: .subheadline).weight(.semibold) var timestampFormatter: DateFormatter? - var watermarkTextFont: UIFont = .preferredFont(forTextStyle: .caption1) var strokeColor = Color.preferredColor(.primaryLabel) - var watermarkTextColor: Color = .preferredColor(.tertiaryLabel) - var hidesXmark = false - var signatureLineColor = Color.preferredColor(.quarternaryLabel) + var titleColor = Color.preferredColor(.primaryLabel) + var addsTimestampInImage: Bool = false + public private(set) var _heightDidChangePublisher = CurrentValueSubject(0) + var watermarkTextFont: UIFont = .preferredFont(forTextStyle: .caption1) @State var isReenterTapped = false + @State var drawings = [Drawing]() + var watermarkTextAlignment: NSTextAlignment = .natural + var hidesSignatureLine = false + var strokeWidth: CGFloat = 3.0 + @State var fullSignatureImage: UIImage? + var _drawingViewMaxHeight: CGFloat? + let _drawingViewMinHeight: CGFloat = 256 var drawingViewBackgroundColor = Color.preferredColor(.primaryBackground) - @State var currentDrawing = Drawing() - var titleColor = Color.preferredColor(.primaryLabel) private var isModelInit: Bool = false private var isTitleNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift index 9457d134e..ac98d72d5 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift @@ -14,15 +14,15 @@ public struct SingleStep { +public struct SortFilterView { @Environment(\.titleModifier) private var titleModifier @Environment(\.itemsModifier) private var itemsModifier @Environment(\.cancelActionModifier) private var cancelActionModifier @@ -42,37 +42,37 @@ public struct SortFilterFullCFG, ResetActionView == _ConditionalContent, ApplyActionView == _ConditionalContent { - public init(model: SortFilterFullCFGModel) { + public init(model: SortFilterViewModel) { self.init(title: model.title, items: Binding<[[SortFilterItem]]>(get: { model.items }, set: { model.items = $0 }), cancelAction: model.cancelAction != nil ? Action(model: model.cancelAction!) : nil, resetAction: model.resetAction != nil ? Action(model: model.resetAction!) : nil, applyAction: model.applyAction != nil ? Action(model: model.applyAction!) : nil, onUpdate: model.onUpdate) } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift index e42687a7f..e5ced1f69 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift @@ -13,11 +13,11 @@ public struct StepProgressIndicator } } -// FIXME: - Implement SortFilterMenu specific LibraryContentProvider +// FIXME: - Implement FilterFeedbackBar specific LibraryContentProvider @available(iOS 14.0, macOS 11.0, *) -struct SortFilterMenuLibraryContent: LibraryContentProvider { +struct FilterFeedbackBarLibraryContent: LibraryContentProvider { @LibraryContentBuilder var views: [LibraryItem] { - LibraryItem(SortFilterMenu(model: LibraryPreviewData.Person.laurelosborn), + LibraryItem(FilterFeedbackBar(model: LibraryPreviewData.Person.laurelosborn), category: .control) } } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/FilterFeedbackBarItem+View.generated.swift similarity index 80% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/FilterFeedbackBarItem+View.generated.swift index 7e85ea3f1..61f4b96bd 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterMenuItem+View.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/FilterFeedbackBarItem+View.generated.swift @@ -1,8 +1,8 @@ // Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT -//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterMenuItem+View.swift` +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/FilterFeedbackBarItem+View.swift` //TODO: Implement default Fiori style definitions as `ViewModifier` -//TODO: Implement SortFilterMenuItem `View` body +//TODO: Implement FilterFeedbackBarItem `View` body //TODO: Implement LibraryContentProvider /// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible @@ -16,7 +16,7 @@ import SwiftUI // FIXME: - Implement Fiori style definitions extension Fiori { - enum SortFilterMenuItem { + enum FilterFeedbackBarItem { typealias LeftIcon = EmptyModifier typealias LeftIconCumulative = EmptyModifier typealias Title = EmptyModifier @@ -45,21 +45,21 @@ extension Fiori { } } -// FIXME: - Implement SortFilterMenuItem View body +// FIXME: - Implement FilterFeedbackBarItem View body -extension SortFilterMenuItem: View { +extension FilterFeedbackBarItem: View { public var body: some View { <# View body #> } } -// FIXME: - Implement SortFilterMenuItem specific LibraryContentProvider +// FIXME: - Implement FilterFeedbackBarItem specific LibraryContentProvider @available(iOS 14.0, macOS 11.0, *) -struct SortFilterMenuItemLibraryContent: LibraryContentProvider { +struct FilterFeedbackBarItemLibraryContent: LibraryContentProvider { @LibraryContentBuilder var views: [LibraryItem] { - LibraryItem(SortFilterMenuItem(model: LibraryPreviewData.Person.laurelosborn), + LibraryItem(FilterFeedbackBarItem(model: LibraryPreviewData.Person.laurelosborn), category: .control) } } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterView+View.generated.swift similarity index 84% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterView+View.generated.swift index 20e9cfd89..f4ab8a5e6 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterFullCFG+View.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SortFilterView+View.generated.swift @@ -1,8 +1,8 @@ // Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT -//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterFullCFG+View.swift` +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SortFilterView+View.swift` //TODO: Implement default Fiori style definitions as `ViewModifier` -//TODO: Implement SortFilterFullCFG `View` body +//TODO: Implement SortFilterView `View` body //TODO: Implement LibraryContentProvider /// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible @@ -16,7 +16,7 @@ import SwiftUI // FIXME: - Implement Fiori style definitions extension Fiori { - enum SortFilterFullCFG { + enum SortFilterView { typealias Title = EmptyModifier typealias TitleCumulative = EmptyModifier typealias Items = EmptyModifier @@ -53,21 +53,21 @@ extension Fiori { } } -// FIXME: - Implement SortFilterFullCFG View body +// FIXME: - Implement SortFilterView View body -extension SortFilterFullCFG: View { +extension SortFilterView: View { public var body: some View { <# View body #> } } -// FIXME: - Implement SortFilterFullCFG specific LibraryContentProvider +// FIXME: - Implement SortFilterView specific LibraryContentProvider @available(iOS 14.0, macOS 11.0, *) -struct SortFilterFullCFGLibraryContent: LibraryContentProvider { +struct SortFilterViewLibraryContent: LibraryContentProvider { @LibraryContentBuilder var views: [LibraryItem] { - LibraryItem(SortFilterFullCFG(model: LibraryPreviewData.Person.laurelosborn), + LibraryItem(SortFilterView(model: LibraryPreviewData.Person.laurelosborn), category: .control) } } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/FilterFeedbackBar+Init.generated.swift similarity index 100% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenu+Init.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/FilterFeedbackBar+Init.generated.swift diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/FilterFeedbackBarItem+Init.generated.swift similarity index 80% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/FilterFeedbackBarItem+Init.generated.swift index 1f3fcd0e5..80725f317 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterMenuItem+Init.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/FilterFeedbackBarItem+Init.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -extension SortFilterMenuItem where LeftIcon == EmptyView { +extension FilterFeedbackBarItem where LeftIcon == EmptyView { public init( @ViewBuilder title: () -> Title, @ViewBuilder rightIcon: () -> RightIcon, @@ -17,7 +17,7 @@ extension SortFilterMenuItem where LeftIcon == EmptyView { } } -extension SortFilterMenuItem where RightIcon == EmptyView { +extension FilterFeedbackBarItem where RightIcon == EmptyView { public init( @ViewBuilder leftIcon: () -> LeftIcon, @ViewBuilder title: () -> Title, @@ -32,7 +32,7 @@ extension SortFilterMenuItem where RightIcon == EmptyView { } } -extension SortFilterMenuItem where LeftIcon == EmptyView, RightIcon == EmptyView { +extension FilterFeedbackBarItem where LeftIcon == EmptyView, RightIcon == EmptyView { public init( @ViewBuilder title: () -> Title, isSelected: Bool diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterView+Init.generated.swift similarity index 84% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterView+Init.generated.swift index bad77d3f6..65f6a658d 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterFullCFG+Init.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SortFilterView+Init.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -extension SortFilterFullCFG where CancelActionView == Action { +extension SortFilterView where CancelActionView == Action { public init( @ViewBuilder title: () -> Title, @ViewBuilder items: () -> Items, @@ -21,7 +21,7 @@ extension SortFilterFullCFG where CancelActionView == Action { } } -extension SortFilterFullCFG where ResetActionView == Action { +extension SortFilterView where ResetActionView == Action { public init( @ViewBuilder title: () -> Title, @ViewBuilder items: () -> Items, @@ -40,7 +40,7 @@ extension SortFilterFullCFG where ResetActionView == Action { } } -extension SortFilterFullCFG where ApplyActionView == Action { +extension SortFilterView where ApplyActionView == Action { public init( @ViewBuilder title: () -> Title, @ViewBuilder items: () -> Items, @@ -59,7 +59,7 @@ extension SortFilterFullCFG where ApplyActionView == Action { } } -extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == Action { +extension SortFilterView where CancelActionView == Action, ResetActionView == Action { public init( @ViewBuilder title: () -> Title, @ViewBuilder items: () -> Items, @@ -77,7 +77,7 @@ extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == } } -extension SortFilterFullCFG where CancelActionView == Action, ApplyActionView == Action { +extension SortFilterView where CancelActionView == Action, ApplyActionView == Action { public init( @ViewBuilder title: () -> Title, @ViewBuilder items: () -> Items, @@ -95,7 +95,7 @@ extension SortFilterFullCFG where CancelActionView == Action, ApplyActionView == } } -extension SortFilterFullCFG where ResetActionView == Action, ApplyActionView == Action { +extension SortFilterView where ResetActionView == Action, ApplyActionView == Action { public init( @ViewBuilder title: () -> Title, @ViewBuilder items: () -> Items, @@ -113,7 +113,7 @@ extension SortFilterFullCFG where ResetActionView == Action, ApplyActionView == } } -extension SortFilterFullCFG where CancelActionView == Action, ResetActionView == Action, ApplyActionView == Action { +extension SortFilterView where CancelActionView == Action, ResetActionView == Action, ApplyActionView == Action { public init( @ViewBuilder title: () -> Title, @ViewBuilder items: () -> Items, diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/FilterFeedbackBarModel+Extensions.generated.swift similarity index 80% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/FilterFeedbackBarModel+Extensions.generated.swift index e0d70748f..8ce7ea931 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterMenuModel+Extensions.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/FilterFeedbackBarModel+Extensions.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -public extension SortFilterMenuModel { +public extension FilterFeedbackBarModel { var onUpdate: (() -> Void)? { return nil } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterViewModel+Extensions.generated.swift similarity index 90% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterViewModel+Extensions.generated.swift index 2a7904961..a433876de 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterFullCFGModel+Extensions.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/SortFilterViewModel+Extensions.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -public extension SortFilterFullCFGModel { +public extension SortFilterViewModel { var cancelAction: ActionModel? { return _CancelActionDefault() } From 25359657049ea695d2a9f8a8d16c1363e1cbb757 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Thu, 2 Nov 2023 15:25:29 -0700 Subject: [PATCH 08/22] improvement for designer review --- .../CancellableResettableForm.swift | 19 +++--- .../DataTypes/SoftFilter+DataType.swift | 4 +- .../Views/SliderPicker+View.swift | 2 - .../Views/SortFilter/FeedbackBar+View.swift | 1 - .../SortFilter/SortFilterDialog+View.swift | 63 ------------------- .../SortFilter/SortFilterMenuItem+View.swift | 10 ++- .../SortFilter/SortFilterView+View.swift | 2 - .../_SortFilterCFGItemContainer.swift | 31 ++++++--- .../Views/SwitchPicker+View.swift | 2 - 9 files changed, 42 insertions(+), 92 deletions(-) delete mode 100644 Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift diff --git a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift index f8f2bc286..f79dd2adb 100644 --- a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift +++ b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift @@ -32,11 +32,12 @@ struct CancellableResettableDialogForm some View { if isEnabled { configuration.label - .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) - .padding(8) + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 - 13 * 2 : + UIScreen.main.bounds.size.width - 16 * 2) + .padding([.top, .bottom], 8) .font(.body) .fontWeight(.bold) .foregroundStyle(Color.preferredColor(.base2)) @@ -56,16 +58,17 @@ struct ApplyButtonStyle: PrimitiveButtonStyle { .onTapGesture { configuration.trigger() } - .padding([.top, .bottom], UIDevice.current.userInterfaceIdiom == .pad ? 16 : 8) + .padding([.top], UIDevice.current.userInterfaceIdiom == .pad ? 16 : 8) } else { configuration.label - .frame(minWidth: UIDevice.current.userInterfaceIdiom == .pad ? 375 : 200, maxWidth: .infinity) - .padding(8) + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 - 13 * 2 : + UIScreen.main.bounds.size.width - 16 * 2) + .padding([.top, .bottom], 8) .font(.body) .fontWeight(.bold) .foregroundStyle(Color.preferredColor(.grey1)) .background(RoundedRectangle(cornerRadius: 8).fill(Color.preferredColor(.grey5))) - .padding([.top, .bottom], UIDevice.current.userInterfaceIdiom == .pad ? 16 : 8) + .padding([.top], UIDevice.current.userInterfaceIdiom == .pad ? 16 : 8) } } } diff --git a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift index fe483867f..5e6b83f38 100644 --- a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift +++ b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift @@ -306,8 +306,8 @@ public struct SliderItem: Identifiable, Equatable { } var label: String { - if let formatter = formatter, let value = value { - return String(format: formatter, value) + if let value = self.value { + return "\(name): \(value)" } return name } diff --git a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift index 04b531038..603e8a936 100644 --- a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift @@ -121,8 +121,6 @@ extension Binding { } /* - // FIXME: - Implement SliderPicker specific LibraryContentProvider - @available(iOS 14.0, macOS 11.0, *) struct SliderPickerLibraryContent: LibraryContentProvider { @LibraryContentBuilder diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift index a48f1dc51..0ba0b5e54 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift @@ -18,7 +18,6 @@ extension FilterFeedbackBar: View { } /* - // FIXME: - Implement SortFilterMenu specific LibraryContentProvider @available(iOS 14.0, macOS 11.0, *) struct SortFilterMenuLibraryContent: LibraryContentProvider { @LibraryContentBuilder diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift deleted file mode 100644 index cc4939d47..000000000 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterDialog+View.swift +++ /dev/null @@ -1,63 +0,0 @@ -/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible -/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` -/// to declare a wrapped property -/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` - -/* - import SwiftUI - - // FIXME: - Implement Fiori style definitions - - extension Fiori { - enum SortFilterDialog { - typealias Item = EmptyModifier - typealias ItemCumulative = EmptyModifier - typealias CancelAction = EmptyModifier - typealias CancelActionCumulative = EmptyModifier - typealias ResetAction = EmptyModifier - typealias ResetActionCumulative = EmptyModifier - typealias ApplyAction = EmptyModifier - typealias ApplyActionCumulative = EmptyModifier - - // TODO: - substitute type-specific ViewModifier for EmptyModifier - /* - // replace `typealias Subtitle = EmptyModifier` with: - - struct Subtitle: ViewModifier { - func body(content: Content) -> some View { - content - .font(.body) - .foregroundColor(.preferredColor(.primary3)) - } - } - */ - static let item = Item() - static let cancelAction = CancelAction() - static let resetAction = ResetAction() - static let applyAction = ApplyAction() - static let itemCumulative = ItemCumulative() - static let cancelActionCumulative = CancelActionCumulative() - static let resetActionCumulative = ResetActionCumulative() - static let applyActionCumulative = ApplyActionCumulative() - } - } - - // FIXME: - Implement SortFilterDialog View body - - extension SortFilterDialog: View { - public var body: some View { - <# View body #> - } - } - - // FIXME: - Implement SortFilterDialog specific LibraryContentProvider - - @available(iOS 14.0, macOS 11.0, *) - struct SortFilterDialogLibraryContent: LibraryContentProvider { - @LibraryContentBuilder - var views: [LibraryItem] { - LibraryItem(SortFilterDialog(model: LibraryPreviewData.Person.laurelosborn), - category: .control) - } - } - */ diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift index c75baa660..e078518ce 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -102,6 +102,7 @@ struct SliderMenuItem: View { } components: { SliderPicker(value: Binding(get: { item.workingValue }, set: { item.workingValue = $0 }), formatter: item.formatter, minimumValue: item.minimumValue, maximumValue: item.maximumValue) + .padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) } .readHeight() .onPreferenceChange(HeightPreferenceKey.self) { height in @@ -167,6 +168,7 @@ struct PickerMenuItem: View { OptionListPicker(value: $item.workingValue, valueOptions: item.valueOptions, hint: nil) { index in item.onTap(option: item.valueOptions[index]) } + .padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) } .readHeight() .onPreferenceChange(HeightPreferenceKey.self) { height in @@ -278,7 +280,7 @@ struct DateTimeMenuItem: View { VStack { HStack { Text(NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "")) - .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .font(.fiori(forTextStyle: .headline, weight: .bold, isItalic: false, isCondensed: false)) .foregroundColor(Color.preferredColor(.primaryLabel)) Spacer() DatePicker( @@ -288,6 +290,7 @@ struct DateTimeMenuItem: View { ) .labelsHidden() } + .padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) DatePicker( item.label, @@ -296,10 +299,11 @@ struct DateTimeMenuItem: View { ) .datePickerStyle(.graphical) .labelsHidden() - .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 + 32: UIScreen.main.bounds.size.width - 16) + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 - 13 : UIScreen.main.bounds.size.width - 16) .clipped() - .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375: UIScreen.main.bounds.size.width) } + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 : UIScreen.main.bounds.size.width) + } .readHeight() .onPreferenceChange(HeightPreferenceKey.self) { height in diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterView+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterView+View.swift index 0dde8bf1d..7b4063483 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterView+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterView+View.swift @@ -78,8 +78,6 @@ extension SortFilterView: View { } /* - // FIXME: - Implement SortFilterFullCFG specific LibraryContentProvider - @available(iOS 14.0, macOS 11.0, *) struct SortFilterFullCFGLibraryContent: LibraryContentProvider { @LibraryContentBuilder diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift index a3df6fd9c..8d15dafe6 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -34,10 +34,13 @@ extension _SortFilterCFGItemContainer: View { slider(row: r, column: c) case .datetime: datetimePicker(row: r, column: c) + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 : UIScreen.main.bounds.size.width) } } } .padding([.top], 12) + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 - 13 * 2: UIScreen.main.bounds.size.width - 16 * 2) + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 : UIScreen.main.bounds.size.width) .background(Color.preferredColor(.secondaryGroupedBackground)) } } @@ -155,16 +158,25 @@ extension _SortFilterCFGItemContainer: View { VStack { HStack { Text(_items[r][c].datetime.name) - .font(.fiori(forTextStyle: .subheadline, weight: .bold, isItalic: false, isCondensed: false)) + .font(.fiori(forTextStyle: .headline, weight: .bold, isItalic: false, isCondensed: false)) .foregroundColor(Color.preferredColor(.primaryLabel)) Spacer() } - - DatePicker( - NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), - selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), - displayedComponents: [.hourAndMinute] - ) + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 - 13 * 2 : UIScreen.main.bounds.size.width - 16 * 2) + + HStack { + Text(NSLocalizedString("Time", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "")) + .font(.fiori(forTextStyle: .headline, weight: .bold, isItalic: false, isCondensed: false)) + .foregroundColor(Color.preferredColor(.primaryLabel)) + Spacer() + DatePicker( + "", + selection: Binding(get: { _items[r][c].datetime.workingValue ?? Date() }, set: { _items[r][c].datetime.workingValue = $0 }), + displayedComponents: [.hourAndMinute] + ) + .labelsHidden() + } + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 - 13 * 2 : UIScreen.main.bounds.size.width - 16 * 2) DatePicker( "", @@ -173,9 +185,10 @@ extension _SortFilterCFGItemContainer: View { ) .datePickerStyle(.graphical) .labelsHidden() - .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 + 32: UIScreen.main.bounds.size.width - 16) + .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 - 13 : UIScreen.main.bounds.size.width - 16) +// .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 : UIScreen.main.bounds.size.width) .clipped() - .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375: UIScreen.main.bounds.size.width - 32) +// .frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 375 - 13 * 2: UIScreen.main.bounds.size.width) } } } diff --git a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift index b7e47094c..adef59f5a 100644 --- a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift @@ -104,8 +104,6 @@ public extension EnvironmentValues { } /* - // FIXME: - Implement SwitchPicker specific LibraryContentProvider - @available(iOS 14.0, macOS 11.0, *) struct SwitchPickerLibraryContent: LibraryContentProvider { @LibraryContentBuilder From f972c89ab5a4cee0db4aee52dc8774a309815943 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Fri, 3 Nov 2023 13:05:04 -0700 Subject: [PATCH 09/22] improvement for designer review 2 --- Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift index adef59f5a..998f1fee4 100644 --- a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift @@ -41,8 +41,8 @@ public struct FioriToggleStyle: ToggleStyle { labelColor: Color = Color.preferredColor(.primaryLabel), onColor: Color = Color.preferredColor(.tintColor), offColor: Color = Color.preferredColor(.secondaryFill), - onThumbColor: Color = Color.preferredColor(.primaryBackground), - offThumbColor: Color = Color.preferredColor(.primaryBackground), + onThumbColor: Color = Color.preferredColor(.baseWhite), + offThumbColor: Color = Color.preferredColor(.baseWhite), onBorderColor: Color = Color.preferredColor(.separator), offBorderColor: Color = Color.preferredColor(.separator) ) { From 91bf3b859c8fc4175591e052908d12e574960d26 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 10:29:32 -0800 Subject: [PATCH 10/22] refactor & review --- .../SortFilter/SortFilterExample.swift | 18 ++-- .../DataTypes/SoftFilter+DataType.swift | 95 +++++++++---------- .../Views/OptionChip+View.swift | 2 - .../Views/OptionListPicker+View.swift | 2 - .../SortFilter/SortFilterItemTitle.swift | 2 +- .../_SortFilterMenuItemContainer.swift | 2 +- 6 files changed, 56 insertions(+), 65 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift index 17dc77428..e730eb9e8 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -4,21 +4,21 @@ import SwiftUI struct SortFilterExample: View { @State private var items: [[SortFilterItem]] = [ [ - .switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), isShownOnMenu: true), - .switch(item: .init(name: "Tagged", value: nil, icon: "tag"), isShownOnMenu: false), - .picker(item: .init(value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], name: "JIRA Status", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), isShownOnMenu: true) + .switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), showsOnFilterFeedbackBar: true), + .switch(item: .init(name: "Tagged", value: nil, icon: "tag"), showsOnFilterFeedbackBar: false), + .picker(item: .init(name: "JIRA Status", value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), showsOnFilterFeedbackBar: true) ], [ - .picker(item: .init(value: [0], valueOptions: ["High", "Medium", "Low"], name: "Priority", allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), isShownOnMenu: true), - .filterfeedback(item: .init(value: [0], valueOptions: ["Ascending", "Descending"], name: "Sort Order", allowsMultipleSelection: false, allowsEmptySelection: false, icon: "checkmark")) + .picker(item: .init(name: "Priority", value: [0], valueOptions: ["High", "Medium", "Low"], allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), showsOnFilterFeedbackBar: true), + .filterfeedback(item: .init(name: "Sort Order", value: [0], valueOptions: ["Ascending", "Descending"], allowsMultipleSelection: false, allowsEmptySelection: false, icon: "checkmark")) ], [ - .slider(item: .init(value: 10, minimumValue: 0, maximumValue: 100, name: "User Stories", formatter: "%2d Stories", icon: "number"), isShownOnMenu: true), - .slider(item: .init(value: nil, minimumValue: 0, maximumValue: 100, name: "Number of Tasks"), isShownOnMenu: true), - .datetime(item: .init(value: Date(), name: "Start Date", formatter: "yyyy-MM-dd HH:mm",icon: "calendar"), isShownOnMenu: true) + .slider(item: .init(name: "User Stories", value: 10, minimumValue: 0, maximumValue: 100, formatter: "%2d Stories", icon: "number"), showsOnFilterFeedbackBar: true), + .slider(item: .init(name: "Number of Tasks", value: nil, minimumValue: 0, maximumValue: 100), showsOnFilterFeedbackBar: true), + .datetime(item: .init(name: "Start Date", value: Date(), formatter: "yyyy-MM-dd HH:mm",icon: "calendar"), showsOnFilterFeedbackBar: true) ], [ - .datetime(item: .init(value: nil, name: "Completion Date"), isShownOnMenu: true) + .datetime(item: .init(name: "Completion Date", value: nil), showsOnFilterFeedbackBar: true) ] ] diff --git a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift index 5e6b83f38..d9ee7d805 100644 --- a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift +++ b/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift @@ -3,6 +3,7 @@ import UIKit /// UI control types supporeted by Sort and Filter configuraiton public enum SortFilterItem: Identifiable, Hashable { + /// :nodoc: public var id: String { switch self { case .picker(let item, _): @@ -24,7 +25,7 @@ public enum SortFilterItem: Identifiable, Hashable { /// or a popover containing a collection of selectable buttons when the number of selectable options is greater than 7. /// /// 2. A section of view containing a collection of selectable buttons - case picker(item: PickerItem, isShownOnMenu: Bool) + case picker(item: PickerItem, showsOnFilterFeedbackBar: Bool) /// The type of UI control is used to buid: /// @@ -41,37 +42,38 @@ public enum SortFilterItem: Identifiable, Hashable { /// 1. Sort & Filter's menu item to be toggled between selected and unselected states /// /// 2. A section of view containing a SwiftUI Toggle with Fiori style - case `switch`(item: SwitchItem, isShownOnMenu: Bool) + case `switch`(item: SwitchItem, showsOnFilterFeedbackBar: Bool) /// The type of UI control is used to buid: /// /// 1. Sort & Filter's menu item associated with a popover containing a SwiftUI Toggle with Fiori style /// /// 2. A section of view containing a SwiftUI Toggle with Fiori style - case slider(item: SliderItem, isShownOnMenu: Bool) + case slider(item: SliderItem, showsOnFilterFeedbackBar: Bool) /// The type of UI control is used to buid: /// /// 1. Sort & Filter's menu item associated with a popover containing a SwiftUI Canlendar /// /// 2. A section of view containing a SwiftUI Canlendar - case datetime(item: DateTimeItem, isShownOnMenu: Bool) + case datetime(item: DateTimeItem, showsOnFilterFeedbackBar: Bool) - public var isShownOnMenu: Bool { + public var showsOnFilterFeedbackBar: Bool { switch self { - case .picker(_, let isShownOnMenu): - return isShownOnMenu + case .picker(_, let showsOnFilterFeedbackBar): + return showsOnFilterFeedbackBar case .filterfeedback: return true - case .switch(_, let isShownOnMenu): - return isShownOnMenu - case .slider(_, let isShownOnMenu): - return isShownOnMenu - case .datetime(_, let isShownOnMenu): - return isShownOnMenu + case .switch(_, let showsOnFilterFeedbackBar): + return showsOnFilterFeedbackBar + case .slider(_, let showsOnFilterFeedbackBar): + return showsOnFilterFeedbackBar + case .datetime(_, let showsOnFilterFeedbackBar): + return showsOnFilterFeedbackBar } } + /// :nodoc: public func hash(into hasher: inout Hasher) { switch self { case .picker(let item, _): @@ -103,12 +105,10 @@ public enum SortFilterItem: Identifiable, Hashable { } } -/// (value: [Int], valueOptions: [String], keyName: String?, allowsMultipleSelection: Bool, allowsEmptySelection: Bool) +/// Data structure for filter feedback, option list picker, public struct PickerItem: Identifiable, Equatable { - public var id = UUID().uuidString - + public let id: String public var name: String - public var value: [Int] public var workingValue: [Int] let originalValue: [Int] @@ -118,12 +118,13 @@ public struct PickerItem: Identifiable, Equatable { public let allowsEmptySelection: Bool public let icon: String? - public init(value: [Int], valueOptions: [String], name: String, allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil) { + public init(id: String = UUID().uuidString, name: String, value: [Int], valueOptions: [String], allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil) { + self.id = id + self.name = name self.value = value self.workingValue = value self.originalValue = value self.valueOptions = valueOptions - self.name = name self.allowsMultipleSelection = allowsMultipleSelection self.allowsEmptySelection = allowsEmptySelection self.icon = icon @@ -216,10 +217,9 @@ public struct PickerItem: Identifiable, Equatable { } } -/// (value: Bool, keyName: String) +/// Data structure for boolean type public struct SwitchItem: Identifiable, Equatable { - public var id = UUID().uuidString - + public var id: String public var name: String public var value: Bool? var workingValue: Bool? @@ -262,12 +262,10 @@ public struct SwitchItem: Identifiable, Equatable { } } -/// (value: Float, minimumValue: Float, maximumValue: Float, keyName: String?) +/// Data structure for integer type slider public struct SliderItem: Identifiable, Equatable { - public var id = UUID().uuidString - + public let id: String public var name: String - public var value: Int? var workingValue: Int? let originalValue: Int? @@ -277,13 +275,14 @@ public struct SliderItem: Identifiable, Equatable { public let icon: String? public let hint: String? - public init(value: Int? = nil, minimumValue: Int, maximumValue: Int, name: String, formatter: String? = nil, icon: String? = nil, hint: String? = nil) { + public init(id: String = UUID().uuidString, name: String, value: Int? = nil, minimumValue: Int, maximumValue: Int, formatter: String? = nil, icon: String? = nil, hint: String? = nil) { + self.id = id + self.name = name self.value = value self.workingValue = value self.originalValue = value self.minimumValue = minimumValue self.maximumValue = maximumValue - self.name = name self.formatter = formatter self.icon = icon self.hint = hint @@ -325,9 +324,9 @@ public struct SliderItem: Identifiable, Equatable { } } +/// Data structure for datetime data public struct DateTimeItem: Equatable, Hashable { - public let id = UUID().uuidString - + public let id: String public var name: String public var value: Date? var workingValue: Date? @@ -335,11 +334,12 @@ public struct DateTimeItem: Equatable, Hashable { public var icon: String? public let formatter: String? - public init(value: Date?, name: String, formatter: String? = nil, icon: String? = nil) { + public init(id: String = UUID().uuidString, name: String, value: Date?, formatter: String? = nil, icon: String? = nil) { + self.id = id + self.name = name self.value = value self.workingValue = value self.originalValue = value - self.name = name self.formatter = formatter self.icon = icon } @@ -399,8 +399,8 @@ extension SortFilterItem { set { switch self { - case .picker(_, let isShownOnMenu): - self = .picker(item: newValue, isShownOnMenu: isShownOnMenu) + case .picker(_, let showsOnFilterFeedbackBar): + self = .picker(item: newValue, showsOnFilterFeedbackBar: showsOnFilterFeedbackBar) default: fatalError("Unexpected value \(self)") } @@ -439,8 +439,8 @@ extension SortFilterItem { set { switch self { - case .slider(_, let isShownOnMenu): - self = .slider(item: newValue, isShownOnMenu: isShownOnMenu) + case .slider(_, let showsOnFilterFeedbackBar): + self = .slider(item: newValue, showsOnFilterFeedbackBar: showsOnFilterFeedbackBar) default: fatalError("Unexpected value \(self)") } @@ -459,8 +459,8 @@ extension SortFilterItem { set { switch self { - case .datetime(_, let isShownOnMenu): - self = .datetime(item: newValue, isShownOnMenu: isShownOnMenu) + case .datetime(_, let showsOnFilterFeedbackBar): + self = .datetime(item: newValue, showsOnFilterFeedbackBar: showsOnFilterFeedbackBar) default: fatalError("Unexpected value \(self)") } @@ -479,15 +479,15 @@ extension SortFilterItem { set { switch self { - case .switch(_, let isShownOnMenu): - self = .switch(item: newValue, isShownOnMenu: isShownOnMenu) + case .switch(_, let showsOnFilterFeedbackBar): + self = .switch(item: newValue, showsOnFilterFeedbackBar: showsOnFilterFeedbackBar) default: fatalError("Unexpected value \(self)") } } } - public var isChanged: Bool { + var isChanged: Bool { switch self { case .picker(let item, _): return item.isChanged @@ -502,7 +502,7 @@ extension SortFilterItem { } } - public var isOriginal: Bool { + var isOriginal: Bool { switch self { case .picker(let item, _): return item.isOriginal @@ -517,7 +517,7 @@ extension SortFilterItem { } } - public mutating func cancel() { + mutating func cancel() { switch self { case .picker(var item, _): item.cancel() @@ -537,7 +537,7 @@ extension SortFilterItem { } } - public mutating func reset() { + mutating func reset() { switch self { case .picker(var item, _): item.reset() @@ -557,7 +557,7 @@ extension SortFilterItem { } } - public mutating func apply() { + mutating func apply() { switch self { case .picker(var item, _): item.apply() @@ -577,8 +577,3 @@ extension SortFilterItem { } } } - -/* - Notes: - c. to resolve: keyName should not be nillable for menu item, but it can be nil for sheet - */ diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift index 5698410b1..1b8bfcf3d 100644 --- a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -35,8 +35,6 @@ extension OptionChip: View { } /* - // FIXME: - Implement OptionChip specific LibraryContentProvider - @available(iOS 14.0, macOS 11.0, *) struct OptionChipLibraryContent: LibraryContentProvider { @LibraryContentBuilder diff --git a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift index caa12da1b..b7a59dfd2 100644 --- a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift @@ -32,8 +32,6 @@ extension OptionListPicker: View { } /* - // FIXME: - Implement OptionListPicker specific LibraryContentProvider - @available(iOS 14.0, macOS 11.0, *) struct OptionListPickerLibraryContent: LibraryContentProvider { @LibraryContentBuilder diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift index 3f1c7900c..2c4d611c1 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift @@ -9,7 +9,7 @@ import SwiftUI import FioriThemeManager /// Dialog titile component -public struct SortFilterItemTitle: TitleComponent, View { +struct SortFilterItemTitle: TitleComponent, View { public let title: String public init(title: String) { diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift index 93941c514..9806d9a64 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift @@ -26,7 +26,7 @@ extension _SortFilterMenuItemContainer: View { } ForEach(0 ..< _items.count) { r in ForEach(0 ..< _items[r].count) { c in - if _items[r][c].isShownOnMenu { + if _items[r][c].showsOnFilterFeedbackBar { switch _items[r][c] { case .picker: PickerMenuItem(item: Binding(get: { _items[r][c].picker }, set: { _items[r][c].picker = $0 }), onUpdate: onUpdate) From e1c53a4dd72f71801c957e16f267da77d9559e9d Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 10:53:43 -0800 Subject: [PATCH 11/22] refactor & review --- .../SortFilterView+Extensions.swift | 8 +- ...taType.swift => SortFilter+DataType.swift} | 564 +++++++++--------- .../Views/OptionChip+View.swift | 12 - .../SortFilter/SortFilterMenuItem+View.swift | 20 +- .../_SortFilterMenuItemContainer.swift | 10 +- 5 files changed, 302 insertions(+), 312 deletions(-) rename Sources/FioriSwiftUICore/DataTypes/{SoftFilter+DataType.swift => SortFilter+DataType.swift} (52%) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift index dccb21bc3..42b7dd3c4 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift @@ -17,19 +17,19 @@ extension View { } } - func json(item: PickerItem) -> String { + func json(item: SortFilterItem.PickerItem) -> String { "{name: \(item.name), value: \(item.value)}" } - func json(item: SliderItem) -> String { + func json(item: SortFilterItem.SliderItem) -> String { "{name: \(item.name), value: \(String(describing: item.value))}" } - func json(item: DateTimeItem) -> String { + func json(item: SortFilterItem.DateTimeItem) -> String { "{name: \(item.name), value: \(String(describing: item.value))}" } - func json(item: SwitchItem) -> String { + func json(item: SortFilterItem.SwitchItem) -> String { "{name: \(item.name), value: \(String(describing: item.value))}" } } diff --git a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift similarity index 52% rename from Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift rename to Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift index d9ee7d805..a20c126e1 100644 --- a/Sources/FioriSwiftUICore/DataTypes/SoftFilter+DataType.swift +++ b/Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift @@ -105,287 +105,6 @@ public enum SortFilterItem: Identifiable, Hashable { } } -/// Data structure for filter feedback, option list picker, -public struct PickerItem: Identifiable, Equatable { - public let id: String - public var name: String - public var value: [Int] - public var workingValue: [Int] - let originalValue: [Int] - - var valueOptions: [String] - public let allowsMultipleSelection: Bool - public let allowsEmptySelection: Bool - public let icon: String? - - public init(id: String = UUID().uuidString, name: String, value: [Int], valueOptions: [String], allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil) { - self.id = id - self.name = name - self.value = value - self.workingValue = value - self.originalValue = value - self.valueOptions = valueOptions - self.allowsMultipleSelection = allowsMultipleSelection - self.allowsEmptySelection = allowsEmptySelection - self.icon = icon - } - - mutating func onTap(option: String) { - guard let index = valueOptions.firstIndex(of: option) else { return } - if self.workingValue.contains(index) { - if self.workingValue.count > 1 { - self.workingValue = self.workingValue.filter { $0 != index } - } else { - if self.allowsEmptySelection { - self.workingValue = [] - } else { - self.workingValue = index == 1 ? [0] : [1] - } - } - } else { - if self.allowsMultipleSelection { - self.workingValue.append(index) - } else { - self.workingValue = [index] - } - } - } - - mutating func optionOnTap(_ index: Int) { - if self.workingValue.contains(index) { - if self.workingValue.count > 1 { - self.workingValue = self.workingValue.filter { $0 != index } - } else { - if self.allowsEmptySelection { - self.workingValue = [] - } else { - self.workingValue = index == 1 ? [0] : [1] - } - } - } else { - if self.allowsMultipleSelection { - self.workingValue.append(index) - } else { - self.workingValue = [index] - } - } - } - - mutating func cancel() { - self.workingValue = self.value.map { $0 } - } - - mutating func reset() { - self.workingValue = self.originalValue.map { $0 } - } - - mutating func apply() { - self.value = self.workingValue.map { $0 } - } - - func isOptionSelected(_ option: String) -> Bool { - guard let idx = valueOptions.firstIndex(of: option) else { return false } - return self.workingValue.contains(idx) - } - - func isOptionSelected(index: Int) -> Bool { - self.workingValue.contains(index) - } - - var isChecked: Bool { - !self.value.isEmpty - } - - var label: String { - if allowsMultipleSelection && self.value.count >= 1 { - if self.value.count == 1 { - return valueOptions[value[0]] - } else { - return "\(self.name) (\(self.value.count))" - } - } else { - return self.name - } - } - - var isChanged: Bool { - self.value != self.workingValue - } - - var isOriginal: Bool { - self.workingValue == self.originalValue - } -} - -/// Data structure for boolean type -public struct SwitchItem: Identifiable, Equatable { - public var id: String - public var name: String - public var value: Bool? - var workingValue: Bool? - let originalValue: Bool? - public let icon: String? - public let hint: String? - - public init(id: String = UUID().uuidString, name: String, value: Bool?, icon: String? = nil, hint: String? = nil) { - self.id = id - self.name = name - self.value = value - self.workingValue = value - self.originalValue = value - self.icon = icon - self.hint = hint - } - - mutating func reset() { - self.workingValue = self.originalValue - } - - mutating func cancel() { - self.workingValue = self.value - } - - mutating func apply() { - self.value = self.workingValue - } - - var isChecked: Bool { - self.value ?? false - } - - var isChanged: Bool { - self.value != self.workingValue - } - - var isOriginal: Bool { - self.workingValue == self.originalValue - } -} - -/// Data structure for integer type slider -public struct SliderItem: Identifiable, Equatable { - public let id: String - public var name: String - public var value: Int? - var workingValue: Int? - let originalValue: Int? - public let minimumValue: Int - public let maximumValue: Int - let formatter: String? - public let icon: String? - public let hint: String? - - public init(id: String = UUID().uuidString, name: String, value: Int? = nil, minimumValue: Int, maximumValue: Int, formatter: String? = nil, icon: String? = nil, hint: String? = nil) { - self.id = id - self.name = name - self.value = value - self.workingValue = value - self.originalValue = value - self.minimumValue = minimumValue - self.maximumValue = maximumValue - self.formatter = formatter - self.icon = icon - self.hint = hint - } - - mutating func reset() { - self.workingValue = self.originalValue - } - - mutating func cancel() { - self.workingValue = self.value - } - - mutating func apply() { - self.value = self.workingValue - } - - var isChecked: Bool { - self.value != nil - } - - var label: String { - if let value = self.value { - return "\(name): \(value)" - } - return name - } - - mutating func setValue(newValue: SliderItem) { - self.value = newValue.value - } - - var isChanged: Bool { - self.value != self.workingValue - } - - var isOriginal: Bool { - self.workingValue == self.originalValue - } -} - -/// Data structure for datetime data -public struct DateTimeItem: Equatable, Hashable { - public let id: String - public var name: String - public var value: Date? - var workingValue: Date? - let originalValue: Date? - public var icon: String? - public let formatter: String? - - public init(id: String = UUID().uuidString, name: String, value: Date?, formatter: String? = nil, icon: String? = nil) { - self.id = id - self.name = name - self.value = value - self.workingValue = value - self.originalValue = value - self.formatter = formatter - self.icon = icon - } - - mutating func reset() { - self.workingValue = self.originalValue - } - - mutating func apply() { - self.value = self.workingValue - } - - mutating func cancel() { - self.workingValue = self.value - } - - var isChecked: Bool { - self.value != nil - } - - var label: String { - if let value = self.value { - if let format = self.formatter { - let formatter = DateFormatter() - formatter.dateFormat = format - return formatter.string(from: value) - } else { - let dateFormatter: DateFormatter = DateFormatter() - dateFormatter.dateStyle = .long - dateFormatter.timeStyle = .short - return dateFormatter.string(from: value) - } - } else { - return self.name - } - } - - var isChanged: Bool { - self.value != self.workingValue - } - - var isOriginal: Bool { - self.workingValue == self.originalValue - } -} - extension SortFilterItem { var picker: PickerItem { get { @@ -577,3 +296,286 @@ extension SortFilterItem { } } } + +extension SortFilterItem { + /// Data structure for filter feedback, option list picker, + public struct PickerItem: Identifiable, Equatable { + public let id: String + public var name: String + public var value: [Int] + public var workingValue: [Int] + let originalValue: [Int] + + var valueOptions: [String] + public let allowsMultipleSelection: Bool + public let allowsEmptySelection: Bool + public let icon: String? + + public init(id: String = UUID().uuidString, name: String, value: [Int], valueOptions: [String], allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil) { + self.id = id + self.name = name + self.value = value + self.workingValue = value + self.originalValue = value + self.valueOptions = valueOptions + self.allowsMultipleSelection = allowsMultipleSelection + self.allowsEmptySelection = allowsEmptySelection + self.icon = icon + } + + mutating func onTap(option: String) { + guard let index = valueOptions.firstIndex(of: option) else { return } + if self.workingValue.contains(index) { + if self.workingValue.count > 1 { + self.workingValue = self.workingValue.filter { $0 != index } + } else { + if self.allowsEmptySelection { + self.workingValue = [] + } else { + self.workingValue = index == 1 ? [0] : [1] + } + } + } else { + if self.allowsMultipleSelection { + self.workingValue.append(index) + } else { + self.workingValue = [index] + } + } + } + + mutating func optionOnTap(_ index: Int) { + if self.workingValue.contains(index) { + if self.workingValue.count > 1 { + self.workingValue = self.workingValue.filter { $0 != index } + } else { + if self.allowsEmptySelection { + self.workingValue = [] + } else { + self.workingValue = index == 1 ? [0] : [1] + } + } + } else { + if self.allowsMultipleSelection { + self.workingValue.append(index) + } else { + self.workingValue = [index] + } + } + } + + mutating func cancel() { + self.workingValue = self.value.map { $0 } + } + + mutating func reset() { + self.workingValue = self.originalValue.map { $0 } + } + + mutating func apply() { + self.value = self.workingValue.map { $0 } + } + + func isOptionSelected(_ option: String) -> Bool { + guard let idx = valueOptions.firstIndex(of: option) else { return false } + return self.workingValue.contains(idx) + } + + func isOptionSelected(index: Int) -> Bool { + self.workingValue.contains(index) + } + + var isChecked: Bool { + !self.value.isEmpty + } + + var label: String { + if allowsMultipleSelection && self.value.count >= 1 { + if self.value.count == 1 { + return valueOptions[value[0]] + } else { + return "\(self.name) (\(self.value.count))" + } + } else { + return self.name + } + } + + var isChanged: Bool { + self.value != self.workingValue + } + + var isOriginal: Bool { + self.workingValue == self.originalValue + } + } + + /// Data structure for boolean type + public struct SwitchItem: Identifiable, Equatable { + public var id: String + public var name: String + public var value: Bool? + var workingValue: Bool? + let originalValue: Bool? + public let icon: String? + public let hint: String? + + public init(id: String = UUID().uuidString, name: String, value: Bool?, icon: String? = nil, hint: String? = nil) { + self.id = id + self.name = name + self.value = value + self.workingValue = value + self.originalValue = value + self.icon = icon + self.hint = hint + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + mutating func apply() { + self.value = self.workingValue + } + + var isChecked: Bool { + self.value ?? false + } + + var isChanged: Bool { + self.value != self.workingValue + } + + var isOriginal: Bool { + self.workingValue == self.originalValue + } + } + + /// Data structure for integer type slider + public struct SliderItem: Identifiable, Equatable { + public let id: String + public var name: String + public var value: Int? + var workingValue: Int? + let originalValue: Int? + public let minimumValue: Int + public let maximumValue: Int + let formatter: String? + public let icon: String? + public let hint: String? + + public init(id: String = UUID().uuidString, name: String, value: Int? = nil, minimumValue: Int, maximumValue: Int, formatter: String? = nil, icon: String? = nil, hint: String? = nil) { + self.id = id + self.name = name + self.value = value + self.workingValue = value + self.originalValue = value + self.minimumValue = minimumValue + self.maximumValue = maximumValue + self.formatter = formatter + self.icon = icon + self.hint = hint + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + mutating func apply() { + self.value = self.workingValue + } + + var isChecked: Bool { + self.value != nil + } + + var label: String { + if let value = self.value { + return "\(name): \(value)" + } + return name + } + + mutating func setValue(newValue: SliderItem) { + self.value = newValue.value + } + + var isChanged: Bool { + self.value != self.workingValue + } + + var isOriginal: Bool { + self.workingValue == self.originalValue + } + } + + /// Data structure for datetime data + public struct DateTimeItem: Equatable, Hashable { + public let id: String + public var name: String + public var value: Date? + var workingValue: Date? + let originalValue: Date? + public var icon: String? + public let formatter: String? + + public init(id: String = UUID().uuidString, name: String, value: Date?, formatter: String? = nil, icon: String? = nil) { + self.id = id + self.name = name + self.value = value + self.workingValue = value + self.originalValue = value + self.formatter = formatter + self.icon = icon + } + + mutating func reset() { + self.workingValue = self.originalValue + } + + mutating func apply() { + self.value = self.workingValue + } + + mutating func cancel() { + self.workingValue = self.value + } + + var isChecked: Bool { + self.value != nil + } + + var label: String { + if let value = self.value { + if let format = self.formatter { + let formatter = DateFormatter() + formatter.dateFormat = format + return formatter.string(from: value) + } else { + let dateFormatter: DateFormatter = DateFormatter() + dateFormatter.dateStyle = .long + dateFormatter.timeStyle = .short + return dateFormatter.string(from: value) + } + } else { + return self.name + } + } + + var isChanged: Bool { + self.value != self.workingValue + } + + var isOriginal: Bool { + self.workingValue == self.originalValue + } + } +} diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift index 1b8bfcf3d..842e6c8b7 100644 --- a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -9,18 +9,6 @@ extension Fiori { typealias Title = EmptyModifier typealias TitleCumulative = EmptyModifier - // TODO: - substitute type-specific ViewModifier for EmptyModifier - /* - // replace `typealias Subtitle = EmptyModifier` with: - - struct Subtitle: ViewModifier { - func body(content: Content) -> some View { - content - .font(.body) - .foregroundColor(.preferredColor(.primary3)) - } - } - */ static let leftIcon = LeftIcon() static let title = Title() static let leftIconCumulative = LeftIconCumulative() diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift index e078518ce..b378ad280 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -36,10 +36,10 @@ private extension View { } struct FilterFeedbackMenuItem: View { - @Binding var item: PickerItem + @Binding var item: SortFilterItem.PickerItem var onUpdate: () -> Void - public init(item: Binding, onUpdate: @escaping () -> Void) { + public init(item: Binding, onUpdate: @escaping () -> Void) { self._item = item self.onUpdate = onUpdate } @@ -59,7 +59,7 @@ struct FilterFeedbackMenuItem: View { } struct SliderMenuItem: View { - @Binding var item: SliderItem + @Binding var item: SortFilterItem.SliderItem @State var isSheetVisible = false @@ -67,7 +67,7 @@ struct SliderMenuItem: View { var onUpdate: () -> Void - public init(item: Binding, onUpdate: @escaping () -> Void) { + public init(item: Binding, onUpdate: @escaping () -> Void) { self._item = item self.onUpdate = onUpdate } @@ -116,14 +116,14 @@ struct SliderMenuItem: View { } struct PickerMenuItem: View { - @Binding var item: PickerItem + @Binding var item: SortFilterItem.PickerItem var onUpdate: () -> Void @State var isSheetVisible = false @State var detentHeight: CGFloat = 0 - public init(item: Binding, onUpdate: @escaping () -> Void) { + public init(item: Binding, onUpdate: @escaping () -> Void) { self._item = item self.onUpdate = onUpdate } @@ -236,7 +236,7 @@ private struct ReadHeightModifier: ViewModifier { } struct DateTimeMenuItem: View { - @Binding private var item: DateTimeItem + @Binding private var item: SortFilterItem.DateTimeItem @State private var isSheetVisible: Bool = false @@ -244,7 +244,7 @@ struct DateTimeMenuItem: View { var onUpdate: () -> Void - public init(item: Binding, onUpdate: @escaping () -> Void) { + public init(item: Binding, onUpdate: @escaping () -> Void) { self._item = item self.onUpdate = onUpdate } @@ -317,14 +317,14 @@ struct DateTimeMenuItem: View { } struct SwitchMenuItem: View { - @Binding private var item: SwitchItem + @Binding private var item: SortFilterItem.SwitchItem // @State var detentHeight: CGFloat = 0 // @State private var isSheetVisible: Bool = false var onUpdate: () -> Void - public init(item: Binding, onUpdate: @escaping () -> Void) { + public init(item: Binding, onUpdate: @escaping () -> Void) { self._item = item self.onUpdate = onUpdate } diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift index 9806d9a64..cad53f8b8 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift @@ -29,15 +29,15 @@ extension _SortFilterMenuItemContainer: View { if _items[r][c].showsOnFilterFeedbackBar { switch _items[r][c] { case .picker: - PickerMenuItem(item: Binding(get: { _items[r][c].picker }, set: { _items[r][c].picker = $0 }), onUpdate: onUpdate) + PickerMenuItem(item: Binding(get: { _items[r][c].picker }, set: { _items[r][c].picker = $0 }), onUpdate: onUpdate) case .filterfeedback: - FilterFeedbackMenuItem(item: Binding(get: { _items[r][c].filterfeedback }, set: { _items[r][c].filterfeedback = $0 }), onUpdate: onUpdate) + FilterFeedbackMenuItem(item: Binding(get: { _items[r][c].filterfeedback }, set: { _items[r][c].filterfeedback = $0 }), onUpdate: onUpdate) case .switch: - SwitchMenuItem(item: Binding(get: { _items[r][c].switch }, set: { _items[r][c].switch = $0 }), onUpdate: onUpdate) + SwitchMenuItem(item: Binding(get: { _items[r][c].switch }, set: { _items[r][c].switch = $0 }), onUpdate: onUpdate) case .slider: - SliderMenuItem(item: Binding(get: { _items[r][c].slider }, set: { _items[r][c].slider = $0 }), onUpdate: onUpdate) + SliderMenuItem(item: Binding(get: { _items[r][c].slider }, set: { _items[r][c].slider = $0 }), onUpdate: onUpdate) case .datetime: - DateTimeMenuItem(item: Binding(get: { _items[r][c].datetime }, set: { _items[r][c].datetime = $0 }), onUpdate: onUpdate) + DateTimeMenuItem(item: Binding(get: { _items[r][c].datetime }, set: { _items[r][c].datetime = $0 }), onUpdate: onUpdate) } } } From d91e089f5d7f6e0b8550feb968dd3f31bb693ce1 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 11:24:30 -0800 Subject: [PATCH 12/22] refactor & review --- .../SortFilter/SortFilterExample.swift | 6 +-- .../Views/OptionChip+View.swift | 50 +++++++++++-------- .../Views/OptionListPicker+View.swift | 2 +- .../SortFilter/SortFilterMenuItem+Style.swift | 8 ++- .../SortFilter/SortFilterMenuItem+View.swift | 15 +++--- .../API/OptionChip+API.generated.swift | 2 +- 6 files changed, 46 insertions(+), 37 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift index e730eb9e8..bbd4f5835 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -31,8 +31,8 @@ struct SortFilterExample: View { VStack { if isCustomStyle { FilterFeedbackBar(items: $items, onUpdate: performSortAndFilter) - .sortFilterMenuItemStyle(font: .subheadline, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) - .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + .filterFeedbackBarStyle(font: .subheadline, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + .optionListPickerStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) // .trailingFullConfigurationMenuItem(icon: "command") // .leadingFullConfigurationMenuItem(icon: "command") // .leadingFullConfigurationMenuItem(name: "All") @@ -71,7 +71,7 @@ struct SortFilterExample: View { items: $items, onUpdate: performSortAndFilter ) - .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + .optionListPickerStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) } else { SortFilterView( title: "Configuration", diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift index 842e6c8b7..c8df0817f 100644 --- a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -18,7 +18,7 @@ extension Fiori { extension OptionChip: View { public var body: some View { - optionChipStyle.makeBody(configuration: OptionChipConfiguration(leftIcon: AnyView(leftIcon), title: AnyView(title), isSelected: _isSelected)) + optionChipStyle.makeBody(configuration: OptionListPickerButtonConfiguration(leftIcon: AnyView(leftIcon), title: AnyView(title), isSelected: _isSelected)) } } @@ -33,7 +33,8 @@ extension OptionChip: View { } */ -public struct OptionChipConfiguration { +/// Option list picker configuration for styling +public struct OptionListPickerButtonConfiguration { let leftIcon: AnyView let title: AnyView let isSelected: Bool @@ -45,15 +46,17 @@ public struct OptionChipConfiguration { } } -public protocol OptionChipStyle { +/// Option list picker style +public protocol OptionListPickerStyle { associatedtype Body = View - typealias Configuration = OptionChipConfiguration + typealias Configuration = OptionListPickerButtonConfiguration func makeBody(configuration: Self.Configuration) -> AnyView // Self.Body } -public struct DefaultOptionChipStyle: OptionChipStyle { +/// Default option list picker style +public struct DefaultOptionListPickerStyle: OptionListPickerStyle { let font: Font let foregroundColorSelected: Color let foregroundColorUnselected: Color @@ -104,29 +107,32 @@ public struct DefaultOptionChipStyle: OptionChipStyle { } } -struct OptionChipStyleKey: EnvironmentKey { - static var defaultValue: any OptionChipStyle = DefaultOptionChipStyle() +struct OptionListPickerStyleKey: EnvironmentKey { + static var defaultValue: any OptionListPickerStyle = DefaultOptionListPickerStyle() } extension EnvironmentValues { - var optionChipStyle: any OptionChipStyle { + var optionListPickerStyle: any OptionListPickerStyle { get { - self[OptionChipStyleKey.self] + self[OptionListPickerStyleKey.self] } set { - self[OptionChipStyleKey.self] = newValue + self[OptionListPickerStyleKey.self] = newValue } } } +/// Experiemental option list picker styling public extension View { - func optionChipStyle(_ style: S) -> some View where S: OptionChipStyle { - self.environment(\.optionChipStyle, style) + /// Experiemental option list picker styling + func optionListPickerStyle(_ style: S) -> some View where S: OptionListPickerStyle { + self.environment(\.optionListPickerStyle, style) } - func optionChipStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 16, spacing: CGFloat = 6, borderWidth: CGFloat = 1, minHeight: CGFloat = 44) -> some View { - self.environment(\.optionChipStyle, - DefaultOptionChipStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, borderWidth: borderWidth, minHeight: minHeight)) + /// Experiemental option list picker styling + func optionListPickerStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 16, spacing: CGFloat = 6, borderWidth: CGFloat = 1, minHeight: CGFloat = 44) -> some View { + self.environment(\.optionListPickerStyle, + DefaultOptionListPickerStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, borderWidth: borderWidth, minHeight: minHeight)) } } @@ -144,19 +150,19 @@ public extension View { Spacer() OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: true) - .optionChipStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + .optionListPickerStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: false) - .optionChipStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + .optionListPickerStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) - .optionChipStyle(cornerRadius: 16) + .optionListPickerStyle(cornerRadius: 16) OptionChip(title: "Ship", isSelected: true) - .optionChipStyle(fillColorSelected: .yellow) + .optionListPickerStyle(fillColorSelected: .yellow) OptionChip(title: "Ship", isSelected: false) - .optionChipStyle(fillColorUnselected: .gray) + .optionListPickerStyle(fillColorUnselected: .gray) OptionChip(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) - .optionChipStyle(cornerRadius: 20) + .optionListPickerStyle(cornerRadius: 20) OptionChip(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) - .optionChipStyle(cornerRadius: 20) + .optionListPickerStyle(cornerRadius: 20) Spacer() } diff --git a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift index b7a59dfd2..6086774f8 100644 --- a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift @@ -49,7 +49,7 @@ extension OptionListPicker: View { .frame(width: 375) Spacer() OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) - .optionChipStyle(font: .title, foregroundColorSelected: Color.red, strokeColorSelected: Color.red, cornerRadius: 25) + .optionListPickerStyle(font: .title, foregroundColorSelected: Color.red, strokeColorSelected: Color.red, cornerRadius: 25) .frame(width: 375) Spacer() } diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift index b843c7982..3065bd26c 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift @@ -1,6 +1,7 @@ import FioriThemeManager import SwiftUI +/// Configuration for sort and filer menu item styling public struct SortFilterMenuItemConfiguration { let leftIcon: AnyView let title: AnyView @@ -15,6 +16,7 @@ public struct SortFilterMenuItemConfiguration { } } +/// Protocol for sort and filer menu item styling public protocol SortFilterMenuItemStyle { associatedtype Body = View @@ -23,6 +25,7 @@ public protocol SortFilterMenuItemStyle { func makeBody(configuration: Self.Configuration) -> AnyView } +/// Default style for sort and filer menu item public struct DefaultSortFilterMenuItemStyle: SortFilterMenuItemStyle { let font: Font let foregroundColorSelected: Color @@ -90,12 +93,13 @@ extension EnvironmentValues { } } +/// Experimental filter feedback bar styling public extension View { - func sortFilterMenuItemStyle(_ style: S) -> some View where S: SortFilterMenuItemStyle { + func filterFeedbackBarStyle(_ style: S) -> some View where S: SortFilterMenuItemStyle { self.environment(\.sortFilterMenuItemStyle, style) } - func sortFilterMenuItemStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { + func filterFeedbackBarStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { self.environment(\.sortFilterMenuItemStyle, DefaultSortFilterMenuItemStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) } diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift index b378ad280..af3967b08 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -443,19 +443,18 @@ struct FullCFGMenuItem: View { Spacer() FilterFeedbackBarItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: true) - .sortFilterMenuItemStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) + .filterFeedbackBarStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) FilterFeedbackBarItem(leftIcon: Image(systemName: "airplane"), title: "Air Plane", rightIcon: Image(systemName: "chevron.down"), isSelected: false) - .sortFilterMenuItemStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) - - .sortFilterMenuItemStyle(cornerRadius: 16) + .filterFeedbackBarStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) + .filterFeedbackBarStyle(cornerRadius: 16) FilterFeedbackBarItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: true) - .sortFilterMenuItemStyle(fillColorSelected: .yellow) + .filterFeedbackBarStyle(fillColorSelected: .yellow) FilterFeedbackBarItem(title: "Ship", rightIcon: Image(systemName: "chevron.down"), isSelected: false) - .sortFilterMenuItemStyle(fillColorUnselected: .gray) + .filterFeedbackBarStyle(fillColorUnselected: .gray) FilterFeedbackBarItem(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) - .sortFilterMenuItemStyle(cornerRadius: 20) + .filterFeedbackBarStyle(cornerRadius: 20) FilterFeedbackBarItem(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) - .sortFilterMenuItemStyle(cornerRadius: 20) + .filterFeedbackBarStyle(cornerRadius: 20) Spacer() } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift index b7e8c3e77..baf0c4144 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift @@ -5,7 +5,7 @@ import SwiftUI public struct OptionChip { @Environment(\.leftIconModifier) private var leftIconModifier @Environment(\.titleModifier) private var titleModifier - @Environment(\.optionChipStyle) var optionChipStyle + @Environment(\.optionListPickerStyle) var optionChipStyle let _leftIcon: LeftIcon let _title: Title From d9299a17be4964aea949d9827ad63d0a5153f77d Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 11:38:35 -0800 Subject: [PATCH 13/22] refactor & review --- Sources/FioriSwiftUICore/Views/SliderPicker+View.swift | 2 +- .../Views/SortFilter/FeedbackBar+View.swift | 2 +- .../Views/SortFilter/SortFilterMenuItem+Style.swift | 2 ++ .../Views/SortFilter/_SortFilterCFGItemContainer.swift | 1 + .../Views/SortFilter/_SortFilterMenuItemContainer.swift | 7 +++++-- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift index 603e8a936..7e7286699 100644 --- a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift @@ -76,7 +76,7 @@ private extension SliderPicker { case .footnote: uiFont = UIFont.preferredFont(forTextStyle: .footnote) case .body: - fallthrough + uiFont = UIFont.preferredFont(forTextStyle: .body) default: uiFont = UIFont.preferredFont(forTextStyle: .body) } diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift index 0ba0b5e54..ccef6b1f8 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/FeedbackBar+View.swift @@ -13,7 +13,7 @@ extension Fiori { extension FilterFeedbackBar: View { public var body: some View { items - .onModelUpdateAppCallback(_onUpdate!) + .onModelUpdateAppCallback(_onUpdate ?? {}) } } diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift index 3065bd26c..70208dded 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift @@ -95,10 +95,12 @@ extension EnvironmentValues { /// Experimental filter feedback bar styling public extension View { + /// Experimental filter feedback bar styling func filterFeedbackBarStyle(_ style: S) -> some View where S: SortFilterMenuItemStyle { self.environment(\.sortFilterMenuItemStyle, style) } + /// Experimental filter feedback bar styling func filterFeedbackBarStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { self.environment(\.sortFilterMenuItemStyle, DefaultSortFilterMenuItemStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift index 8d15dafe6..cff42cafc 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -6,6 +6,7 @@ import SwiftUI import FioriThemeManager +/// :nodoc: public struct _SortFilterCFGItemContainer { @EnvironmentObject var context: SortFilterContext diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift index cad53f8b8..d8b7529fe 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift @@ -6,6 +6,7 @@ // import SwiftUI +/// :nodoc: public struct _SortFilterMenuItemContainer { @Environment(\.onModelUpdateAppCallback) var onUpdate: () -> Void // @Environment(\.cancelActionView) var _cancelAction @@ -52,10 +53,11 @@ extension _SortFilterMenuItemContainer: View { } } -public struct SortFilterMenuItemFullConfigurationButtonKey: EnvironmentKey { +struct SortFilterMenuItemFullConfigurationButtonKey: EnvironmentKey { public static var defaultValue: SortFilterMenuItemFullConfigurationButton = .none } +/// Filter feedback bar item for displaying full configuration list public struct SortFilterMenuItemFullConfigurationButton { public let name: String? public let icon: String? @@ -98,7 +100,7 @@ public struct SortFilterMenuItemFullConfigurationButton { static var none = SortFilterMenuItemFullConfigurationButton(positon: Position.none) } -public extension EnvironmentValues { +extension EnvironmentValues { var sortFilterMenuItemFullConfigurationButton: SortFilterMenuItemFullConfigurationButton { get { self[SortFilterMenuItemFullConfigurationButtonKey.self] @@ -109,6 +111,7 @@ public extension EnvironmentValues { } } +/// Experiemental feature for adding full list of configuraiton to filter feedback bar public extension View { func leadingFullConfigurationMenuItem(name: String) -> some View { self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name)) From fd06d01f34c49e752465eec08e5941e37c5ac773 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 11:48:17 -0800 Subject: [PATCH 14/22] refactor & review --- .../FioriSwiftUICore/Views/OptionChip+View.swift | 2 ++ .../SortFilter/_SortFilterCFGItemContainer.swift | 1 + .../SortFilter/_SortFilterMenuItemContainer.swift | 14 ++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift index c8df0817f..f48ef91c5 100644 --- a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -70,6 +70,7 @@ public struct DefaultOptionListPickerStyle: OptionListPickerStyle { let minHeight: CGFloat let minTouchHeight: CGFloat + /// :nodoc: public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 16, spacing: CGFloat = 6, borderWidth: CGFloat = 1, minHeight: CGFloat = 44, minTouchHeight: CGFloat = 56) { self.font = font self.foregroundColorSelected = foregroundColorSelected @@ -85,6 +86,7 @@ public struct DefaultOptionListPickerStyle: OptionListPickerStyle { self.minTouchHeight = minTouchHeight } + /// :nodoc: public func makeBody(configuration: Configuration) -> AnyView { AnyView( HStack(spacing: self.spacing) { diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift index cff42cafc..9f16934ab 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -18,6 +18,7 @@ public struct _SortFilterCFGItemContainer { } extension _SortFilterCFGItemContainer: View { + /// :nodoc: public var body: some View { ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 30) { diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift index d8b7529fe..1271455b4 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift @@ -63,6 +63,7 @@ public struct SortFilterMenuItemFullConfigurationButton { public let icon: String? public let positon: Position + /// Location of the button public enum Position { case leading, trailing, none } @@ -73,30 +74,37 @@ public struct SortFilterMenuItemFullConfigurationButton { self.positon = positon } + /// Place the button at the beginning public static func leading(name: String) -> SortFilterMenuItemFullConfigurationButton { SortFilterMenuItemFullConfigurationButton(name: name, positon: .leading) } + /// Place the button at the beginning public static func leading(icon: String) -> SortFilterMenuItemFullConfigurationButton { SortFilterMenuItemFullConfigurationButton(icon: icon, positon: .leading) } + /// Place the button at the beginning public static func leading(name: String, icon: String) -> SortFilterMenuItemFullConfigurationButton { SortFilterMenuItemFullConfigurationButton(name: name, icon: icon, positon: .leading) } + /// Place the button at the end public static func trailing(name: String) -> SortFilterMenuItemFullConfigurationButton { SortFilterMenuItemFullConfigurationButton(name: name, positon: .trailing) } + /// Place the button at the end public static func trailing(icon: String) -> SortFilterMenuItemFullConfigurationButton { SortFilterMenuItemFullConfigurationButton(icon: icon, positon: .trailing) } + /// Place the button at the end public static func trailing(name: String, icon: String) -> SortFilterMenuItemFullConfigurationButton { SortFilterMenuItemFullConfigurationButton(name: name, icon: icon, positon: .trailing) } + /// No button for full configuration static var none = SortFilterMenuItemFullConfigurationButton(positon: Position.none) } @@ -113,26 +121,32 @@ extension EnvironmentValues { /// Experiemental feature for adding full list of configuraiton to filter feedback bar public extension View { + /// Place the button at the beginning func leadingFullConfigurationMenuItem(name: String) -> some View { self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name)) } + /// Place the button at the beginning func leadingFullConfigurationMenuItem(icon: String) -> some View { self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(icon: icon)) } + /// Place the button at the beginning func leadingFullConfigurationMenuItem(name: String, icon: String) -> some View { self.environment(\.sortFilterMenuItemFullConfigurationButton, .leading(name: name, icon: icon)) } + /// Place the button at the end func trailingFullConfigurationMenuItem(name: String) -> some View { self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(name: name)) } + /// Place the button at the end func trailingFullConfigurationMenuItem(icon: String) -> some View { self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(icon: icon)) } + /// Place the button at the end func trailingFullConfigurationMenuItem(name: String, icon: String) -> some View { self.environment(\.sortFilterMenuItemFullConfigurationButton, .trailing(name: name, icon: icon)) } From df22b5762f34dc66baa2befa00f34dd56cabf5b7 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 13:57:41 -0800 Subject: [PATCH 15/22] refactor & review --- .../Models/ModelDefinitions.swift | 16 ++++---- .../Views/OptionChip+View.swift | 8 ++-- ....swift => OptionListPickerItem+View.swift} | 6 +-- ...View.swift => SliderPickerItem+View.swift} | 10 ++--- .../SortFilter/SortFilterMenuItem+Style.swift | 30 +++++++-------- .../SortFilter/SortFilterMenuItem+View.swift | 6 +-- .../_SortFilterCFGItemContainer.swift | 8 ++-- ...View.swift => SwitchPickerItem+View.swift} | 8 ++-- .../FilterFeedbackBarItem+API.generated.swift | 2 +- .../API/KPIProgressItem+API.generated.swift | 2 +- .../API/ObjectHeader+API.generated.swift | 6 +-- .../API/OptionChip+API.generated.swift | 2 +- ... OptionListPickerItem+API.generated.swift} | 6 +-- .../SearchableListView+API.generated.swift | 4 +- .../SignatureCaptureView+API.generated.swift | 38 +++++++++---------- .../API/SingleStep+API.generated.swift | 10 ++--- ...t => SliderPickerItem+API.generated.swift} | 6 +-- .../StepProgressIndicator+API.generated.swift | 2 +- ...t => SwitchPickerItem+API.generated.swift} | 6 +-- .../API/UserConsentForm+API.generated.swift | 2 +- .../API/UserConsentView+API.generated.swift | 2 +- ...OptionListPickerItem+View.generated.swift} | 14 +++---- ... => SliderPickerItem+View.generated.swift} | 14 +++---- ... => SwitchPickerItem+View.generated.swift} | 14 +++---- .../SwitchPicker+Init.generated.swift | 3 -- ... => SwitchPickerItem+Init.generated.swift} | 0 ...ObjectItemModel+Extensions.generated.swift | 9 ----- ...ickerItemModel+Extensions.generated.swift} | 2 +- 28 files changed, 111 insertions(+), 125 deletions(-) rename Sources/FioriSwiftUICore/Views/{OptionListPicker+View.swift => OptionListPickerItem+View.swift} (79%) rename Sources/FioriSwiftUICore/Views/{SliderPicker+View.swift => SliderPickerItem+View.swift} (96%) rename Sources/FioriSwiftUICore/Views/{SwitchPicker+View.swift => SwitchPickerItem+View.swift} (95%) rename Sources/FioriSwiftUICore/_generated/ViewModels/API/{OptionListPicker+API.generated.swift => OptionListPickerItem+API.generated.swift} (80%) rename Sources/FioriSwiftUICore/_generated/ViewModels/API/{SliderPicker+API.generated.swift => SliderPickerItem+API.generated.swift} (83%) rename Sources/FioriSwiftUICore/_generated/ViewModels/API/{SwitchPicker+API.generated.swift => SwitchPickerItem+API.generated.swift} (78%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/{SwitchPicker+View.generated.swift => OptionListPickerItem+View.generated.swift} (64%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/{OptionListPicker+View.generated.swift => SliderPickerItem+View.generated.swift} (68%) rename Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/{SliderPicker+View.generated.swift => SwitchPickerItem+View.generated.swift} (66%) delete mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift rename Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/{SliderPicker+Init.generated.swift => SwitchPickerItem+Init.generated.swift} (100%) delete mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/ObjectItemModel+Extensions.generated.swift rename Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/{OptionListPickerModel+Extensions.generated.swift => OptionListPickerItemModel+Extensions.generated.swift} (80%) diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index ccc45c8a9..4bc58d8a9 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -470,7 +470,7 @@ public protocol SortFilterViewModel: AnyObject, TitleComponent { var onUpdate: (() -> Void)? { get } } -// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: add_env_props = "filterFeedbackBarStyle" // sourcery: virtualPropActionHelper = "@StateObject var context: SortFilterContext = SortFilterContext()" // sourcery: generated_component_composite public protocol FilterFeedbackBarItemModel: LeftIconComponent, TitleComponent, RightIconComponent { @@ -478,26 +478,26 @@ public protocol FilterFeedbackBarItemModel: LeftIconComponent, TitleComponent, R var isSelected: Bool { get } } -// sourcery: add_env_props = "optionChipStyle" +// sourcery: add_env_props = "optionListPickerStyle" // sourcery: generated_component_composite public protocol OptionChipModel: LeftIconComponent, TitleComponent { // sourcery: no_view var isSelected: Bool { get } } -// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: add_env_props = "filterFeedbackBarStyle" // sourcery: generated_component_not_configurable -public protocol OptionListPickerModel: OptionListPickerComponent { +public protocol OptionListPickerItemModel: OptionListPickerComponent { // sourcery: default.value = nil // sourcery: no_view var onTap: ((_ index: Int) -> Void)? { get } } -// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: add_env_props = "filterFeedbackBarStyle" // sourcery: generated_component_not_configurable // sourcery: add_env_props = "fioriToggleStyle" -public protocol SwitchPickerModel: SwitchPickerComponent {} +public protocol SwitchPickerItemModel: SwitchPickerComponent {} -// sourcery: add_env_props = "sortFilterMenuItemStyle" +// sourcery: add_env_props = "filterFeedbackBarStyle" // sourcery: generated_component_not_configurable -public protocol SliderPickerModel: SliderPickerComponent {} +public protocol SliderPickerItemModel: SliderPickerComponent {} diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift index f48ef91c5..a8ab68a71 100644 --- a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionChip+View.swift @@ -18,7 +18,7 @@ extension Fiori { extension OptionChip: View { public var body: some View { - optionChipStyle.makeBody(configuration: OptionListPickerButtonConfiguration(leftIcon: AnyView(leftIcon), title: AnyView(title), isSelected: _isSelected)) + optionListPickerStyle.makeBody(configuration: OptionListPickerButtonConfiguration(leftIcon: AnyView(leftIcon), title: AnyView(title), isSelected: _isSelected)) } } @@ -39,6 +39,7 @@ public struct OptionListPickerButtonConfiguration { let title: AnyView let isSelected: Bool + /// :nodoc: public init(leftIcon: AnyView, title: AnyView, isSelected: Bool) { self.leftIcon = leftIcon self.title = title @@ -48,11 +49,10 @@ public struct OptionListPickerButtonConfiguration { /// Option list picker style public protocol OptionListPickerStyle { - associatedtype Body = View - typealias Configuration = OptionListPickerButtonConfiguration - func makeBody(configuration: Self.Configuration) -> AnyView // Self.Body + /// :nodoc: + func makeBody(configuration: Self.Configuration) -> AnyView } /// Default option list picker style diff --git a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift similarity index 79% rename from Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift rename to Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift index 6086774f8..5e1a59d6c 100644 --- a/Sources/FioriSwiftUICore/Views/OptionListPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift @@ -2,7 +2,7 @@ import SwiftUI -extension OptionListPicker: View { +extension OptionListPickerItem: View { public var body: some View { Grid(horizontalSpacing: 16) { ForEach(0 ..< Int(ceil(Double(_valueOptions.count) / 2.0))) { rowIndex in @@ -45,10 +45,10 @@ extension OptionListPicker: View { #Preview { VStack { Spacer() - OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + OptionListPickerItem(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) .frame(width: 375) Spacer() - OptionListPicker(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + OptionListPickerItem(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) .optionListPickerStyle(font: .title, foregroundColorSelected: Color.red, strokeColorSelected: Color.red, cornerRadius: 25) .frame(width: 375) Spacer() diff --git a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift b/Sources/FioriSwiftUICore/Views/SliderPickerItem+View.swift similarity index 96% rename from Sources/FioriSwiftUICore/Views/SliderPicker+View.swift rename to Sources/FioriSwiftUICore/Views/SliderPickerItem+View.swift index 7e7286699..a50ca75ef 100644 --- a/Sources/FioriSwiftUICore/Views/SliderPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/SliderPickerItem+View.swift @@ -1,6 +1,6 @@ import SwiftUI -extension SliderPicker: View { +extension SliderPickerItem: View { public var body: some View { VStack { HStack { @@ -40,7 +40,7 @@ extension SliderPicker: View { } } -private extension SliderPicker { +private extension SliderPickerItem { func calcWidth(font: Font) -> CGFloat { var width: CGFloat = 0 for i in 0 ... 9 { @@ -145,7 +145,7 @@ private struct SliderPickeTestView: View { .foregroundColor(value1 != nil ? .blue : .gray) Spacer() } - SliderPicker(value: Binding( + SliderPickerItem(value: Binding( get: { value1 }, @@ -162,7 +162,7 @@ private struct SliderPickeTestView: View { Spacer() } - SliderPicker(value: Binding( + SliderPickerItem(value: Binding( get: { value2 }, @@ -178,7 +178,7 @@ private struct SliderPickeTestView: View { Spacer() } - SliderPicker(value: Binding( + SliderPickerItem(value: Binding( get: { value3 }, diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift index 70208dded..b9b06f9ee 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift @@ -1,8 +1,8 @@ import FioriThemeManager import SwiftUI -/// Configuration for sort and filer menu item styling -public struct SortFilterMenuItemConfiguration { +/// Configuration for filter feedback bar styling +public struct FilterFeedbackBarStyleConfiguration { let leftIcon: AnyView let title: AnyView let isSelected: Bool @@ -17,16 +17,14 @@ public struct SortFilterMenuItemConfiguration { } /// Protocol for sort and filer menu item styling -public protocol SortFilterMenuItemStyle { - associatedtype Body = View - - typealias Configuration = SortFilterMenuItemConfiguration +public protocol FilterFeedbackBarStyle { + typealias Configuration = FilterFeedbackBarStyleConfiguration func makeBody(configuration: Self.Configuration) -> AnyView } /// Default style for sort and filer menu item -public struct DefaultSortFilterMenuItemStyle: SortFilterMenuItemStyle { +public struct DefaultFilterFeedbackBarStyle: FilterFeedbackBarStyle { let font: Font let foregroundColorSelected: Color let foregroundColorUnselected: Color @@ -78,17 +76,17 @@ public struct DefaultSortFilterMenuItemStyle: SortFilterMenuItemStyle { } } -struct SortFilterMenuItemStyleKey: EnvironmentKey { - static var defaultValue: any SortFilterMenuItemStyle = DefaultSortFilterMenuItemStyle() +struct FilterFeedbackBarStyleKey: EnvironmentKey { + static var defaultValue: any FilterFeedbackBarStyle = DefaultFilterFeedbackBarStyle() } extension EnvironmentValues { - var sortFilterMenuItemStyle: any SortFilterMenuItemStyle { + var filterFeedbackBarStyle: any FilterFeedbackBarStyle { get { - self[SortFilterMenuItemStyleKey.self] + self[FilterFeedbackBarStyleKey.self] } set { - self[SortFilterMenuItemStyleKey.self] = newValue + self[FilterFeedbackBarStyleKey.self] = newValue } } } @@ -96,13 +94,13 @@ extension EnvironmentValues { /// Experimental filter feedback bar styling public extension View { /// Experimental filter feedback bar styling - func filterFeedbackBarStyle(_ style: S) -> some View where S: SortFilterMenuItemStyle { - self.environment(\.sortFilterMenuItemStyle, style) + func filterFeedbackBarStyle(_ style: S) -> some View where S: FilterFeedbackBarStyle { + self.environment(\.filterFeedbackBarStyle, style) } /// Experimental filter feedback bar styling func filterFeedbackBarStyle(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) -> some View { - self.environment(\.sortFilterMenuItemStyle, - DefaultSortFilterMenuItemStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) + self.environment(\.filterFeedbackBarStyle, + DefaultFilterFeedbackBarStyle(font: font, foregroundColorSelected: foregroundColorSelected, foregroundColorUnselected: foregroundColorUnselected, fillColorSelected: fillColorSelected, fillColorUnselected: fillColorUnselected, strokeColorSelected: strokeColorSelected, strokeColorUnselected: strokeColorUnselected, cornerRadius: cornerRadius, spacing: spacing, padding: padding, borderWidth: borderWidth, minHeight: minHeight)) } } diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift index af3967b08..3a9b747f2 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift @@ -20,7 +20,7 @@ extension Fiori { extension FilterFeedbackBarItem: View { public var body: some View { - sortFilterMenuItemStyle.makeBody(configuration: SortFilterMenuItemConfiguration(leftIcon: AnyView(_leftIcon), title: AnyView(_title), isSelected: _isSelected, rightIcon: AnyView(_rightIcon))).typeErased + filterFeedbackBarStyle.makeBody(configuration: FilterFeedbackBarStyleConfiguration(leftIcon: AnyView(_leftIcon), title: AnyView(_title), isSelected: _isSelected, rightIcon: AnyView(_rightIcon))).typeErased } } @@ -101,7 +101,7 @@ struct SliderMenuItem: View { .buttonStyle(ApplyButtonStyle()) } components: { - SliderPicker(value: Binding(get: { item.workingValue }, set: { item.workingValue = $0 }), formatter: item.formatter, minimumValue: item.minimumValue, maximumValue: item.maximumValue) + SliderPickerItem(value: Binding(get: { item.workingValue }, set: { item.workingValue = $0 }), formatter: item.formatter, minimumValue: item.minimumValue, maximumValue: item.maximumValue) .padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) } .readHeight() @@ -165,7 +165,7 @@ struct PickerMenuItem: View { }) .buttonStyle(ApplyButtonStyle()) } components: { - OptionListPicker(value: $item.workingValue, valueOptions: item.valueOptions, hint: nil) { index in + OptionListPickerItem(value: $item.workingValue, valueOptions: item.valueOptions, hint: nil) { index in item.onTap(option: item.valueOptions[index]) } .padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift index 9f16934ab..a54df5196 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterCFGItemContainer.swift @@ -106,7 +106,7 @@ extension _SortFilterCFGItemContainer: View { .foregroundColor(Color.preferredColor(.primaryLabel)) Spacer() } - OptionListPicker( + OptionListPickerItem( value: Binding<[Int]>(get: { _items[r][c].picker.workingValue }, set: { _items[r][c].picker.workingValue = $0 }), valueOptions: _items[r][c].picker.valueOptions, onTap: { index in @@ -124,7 +124,7 @@ extension _SortFilterCFGItemContainer: View { .foregroundColor(Color.preferredColor(.primaryLabel)) Spacer() } - OptionListPicker( + OptionListPickerItem( value: Binding<[Int]>(get: { _items[r][c].filterfeedback.workingValue }, set: { _items[r][c].filterfeedback.workingValue = $0 }), valueOptions: _items[r][c].filterfeedback.valueOptions, onTap: { index in @@ -136,7 +136,7 @@ extension _SortFilterCFGItemContainer: View { func switcher(row r: Int, column c: Int) -> some View { VStack { - SwitchPicker(value: Binding(get: { _items[r][c].switch.workingValue }, set: { _items[r][c].switch.workingValue = $0 }), name: _items[r][c].switch.name, hint: nil) + SwitchPickerItem(value: Binding(get: { _items[r][c].switch.workingValue }, set: { _items[r][c].switch.workingValue = $0 }), name: _items[r][c].switch.name, hint: nil) } } @@ -147,7 +147,7 @@ extension _SortFilterCFGItemContainer: View { .font(.headline) Spacer() } - SliderPicker( + SliderPickerItem( value: Binding(get: { _items[r][c].slider.workingValue }, set: { _items[r][c].slider.workingValue = $0 }), formatter: _items[r][c].slider.formatter, minimumValue: _items[r][c].slider.minimumValue, diff --git a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift b/Sources/FioriSwiftUICore/Views/SwitchPickerItem+View.swift similarity index 95% rename from Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift rename to Sources/FioriSwiftUICore/Views/SwitchPickerItem+View.swift index 998f1fee4..15a3d4dc3 100644 --- a/Sources/FioriSwiftUICore/Views/SwitchPicker+View.swift +++ b/Sources/FioriSwiftUICore/Views/SwitchPickerItem+View.swift @@ -1,7 +1,7 @@ import FioriThemeManager import SwiftUI -extension SwitchPicker: View { +extension SwitchPickerItem: View { public var body: some View { AnyView( Toggle(_name ?? "", isOn: .convert(from: _value, ifNilUse: false)) @@ -121,9 +121,9 @@ private struct TestSwitchPicker: View { var body: some View { VStack { - SwitchPicker(value: $v1, hint: nil) - SwitchPicker(value: $v2, hint: nil) - SwitchPicker(value: $v3, hint: nil) + SwitchPickerItem(value: $v1, hint: nil) + SwitchPickerItem(value: $v2, hint: nil) + SwitchPickerItem(value: $v3, hint: nil) } } } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarItem+API.generated.swift index 59ab4fe5f..0991e3c85 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarItem+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarItem+API.generated.swift @@ -6,7 +6,7 @@ public struct FilterFeedbackBarItem { let _fraction: Double? let _subtitle: Subtitle let _footnote: Footnote - @State var isPressed: Bool = false var action: (() -> Void)? = nil + @State var isPressed: Bool = false private var isModelInit: Bool = false private var isKpiNil: Bool = false private var isSubtitleNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift index 2a4d3ec70..c3e75af89 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift @@ -25,12 +25,12 @@ public struct ObjectHeader { @Environment(\.leftIconModifier) private var leftIconModifier @Environment(\.titleModifier) private var titleModifier - @Environment(\.optionListPickerStyle) var optionChipStyle + @Environment(\.optionListPickerStyle) var optionListPickerStyle let _leftIcon: LeftIcon let _title: Title diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPickerItem+API.generated.swift similarity index 80% rename from Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPickerItem+API.generated.swift index ef062bf3f..940156d64 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPicker+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPickerItem+API.generated.swift @@ -2,15 +2,15 @@ // DO NOT EDIT import SwiftUI -public struct OptionListPicker { - @Environment(\.sortFilterMenuItemStyle) var sortFilterMenuItemStyle +public struct OptionListPickerItem { + @Environment(\.filterFeedbackBarStyle) var filterFeedbackBarStyle var _value: Binding<[Int]> var _valueOptions: [String] var _hint: String? = nil var _onTap: ((_ index: Int) -> Void)? = nil - public init(model: OptionListPickerModel) { + public init(model: OptionListPickerItemModel) { self.init(value: Binding<[Int]>(get: { model.value }, set: { model.value = $0 }), valueOptions: model.valueOptions, hint: model.hint, onTap: model.onTap) } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SearchableListView+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SearchableListView+API.generated.swift index 84ddf75c6..f4e90fc12 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SearchableListView+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SearchableListView+API.generated.swift @@ -11,9 +11,9 @@ public struct SearchableListView { let _cancelAction: CancelActionView let _doneAction: DoneActionView - var dataHandler: (() -> ())? = nil - var isTopLevel: Bool = true var contentView: AnyView? = nil + var isTopLevel: Bool = true + var dataHandler: (() -> ())? = nil private var isModelInit: Bool = false private var isCancelActionNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift index bb6c2fe3e..ee8e4b882 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift @@ -19,32 +19,32 @@ public struct SignatureCaptureView Void)? let _onDelete: (() -> Void)? - var hidesXmark = false - var signatureLineColor = Color.preferredColor(.quarternaryLabel) - @State var isSaved = false + var strokeColor = Color.preferredColor(.primaryLabel) + var watermarkTextFont: UIFont = .preferredFont(forTextStyle: .caption1) @State var currentDrawing = Drawing() - @State var isEditing = false - var watermarkTextColor: Color = .preferredColor(.tertiaryLabel) + var appliesTintColorToImage = true + var _drawingViewMaxHeight: CGFloat? var cropsImage = false - var watermarkText: String? + var watermarkTextAlignment: NSTextAlignment = .natural var xmarkColor = Color.preferredColor(.quarternaryLabel) - var appliesTintColorToImage = true - var titleFont = Font.fiori(forTextStyle: .subheadline).weight(.semibold) - var timestampFormatter: DateFormatter? - var strokeColor = Color.preferredColor(.primaryLabel) - var titleColor = Color.preferredColor(.primaryLabel) var addsTimestampInImage: Bool = false - public private(set) var _heightDidChangePublisher = CurrentValueSubject(0) - var watermarkTextFont: UIFont = .preferredFont(forTextStyle: .caption1) + @State var isEditing = false + var timestampFormatter: DateFormatter? + var strokeWidth: CGFloat = 3.0 + var drawingViewBackgroundColor = Color.preferredColor(.primaryBackground) @State var isReenterTapped = false + let _drawingViewMinHeight: CGFloat = 256 + var signatureLineColor = Color.preferredColor(.quarternaryLabel) @State var drawings = [Drawing]() - var watermarkTextAlignment: NSTextAlignment = .natural - var hidesSignatureLine = false - var strokeWidth: CGFloat = 3.0 @State var fullSignatureImage: UIImage? - var _drawingViewMaxHeight: CGFloat? - let _drawingViewMinHeight: CGFloat = 256 - var drawingViewBackgroundColor = Color.preferredColor(.primaryBackground) + var watermarkText: String? + var titleFont = Font.fiori(forTextStyle: .subheadline).weight(.semibold) + public private(set) var _heightDidChangePublisher = CurrentValueSubject(0) + @State var isSaved = false + var hidesSignatureLine = false + var hidesXmark = false + var watermarkTextColor: Color = .preferredColor(.tertiaryLabel) + var titleColor = Color.preferredColor(.primaryLabel) private var isModelInit: Bool = false private var isTitleNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift index ac98d72d5..d3e64f786 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift @@ -14,15 +14,15 @@ public struct SingleStep var _formatter: String? = nil @@ -11,7 +11,7 @@ public struct SliderPicker { var _maximumValue: Int var _hint: String? = nil - public init(model: SliderPickerModel) { + public init(model: SliderPickerItemModel) { self.init(value: Binding(get: { model.value }, set: { model.value = $0 }), formatter: model.formatter, minimumValue: model.minimumValue, maximumValue: model.maximumValue, hint: model.hint) } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift index e5ced1f69..2b66761ac 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/StepProgressIndicator+API.generated.swift @@ -13,8 +13,8 @@ public struct StepProgressIndicator var _name: String? = nil var _hint: String? = nil - public init(model: SwitchPickerModel) { + public init(model: SwitchPickerItemModel) { self.init(value: Binding(get: { model.value }, set: { model.value = $0 }), name: model.name, hint: model.hint) } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/UserConsentForm+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/UserConsentForm+API.generated.swift index d6ce23e78..b78e75c11 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/UserConsentForm+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/UserConsentForm+API.generated.swift @@ -23,8 +23,8 @@ public struct UserConsentForm Void)? let _didDeny: ((Bool) -> Void)? let _didCancel: (() -> Void)? - @State var _pageIndex = 0 @State var _showAlert: (Bool, UserConsentAlertType) = (false, .deny) + @State var _pageIndex = 0 private var isModelInit: Bool = false private var isNextActionNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/UserConsentView+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/UserConsentView+API.generated.swift index 6e08092be..2870a841c 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/UserConsentView+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/UserConsentView+API.generated.swift @@ -10,8 +10,8 @@ public struct UserConsentView { let _didDeny: ((Int, Bool) -> Void)? let _didCancel: ((Int) -> Void)? let _didFinish: (([Int]) -> Void)? - @State var _allowedFormIndexes: [Int] = [] @State var _formIndex = 0 + @State var _allowedFormIndexes: [Int] = [] private var isModelInit: Bool = false private var isDidAllowNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPickerItem+View.generated.swift similarity index 64% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPickerItem+View.generated.swift index 4ac15b331..d752cf79d 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPicker+View.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPickerItem+View.generated.swift @@ -1,7 +1,7 @@ // Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT -//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SwitchPicker+View.swift` -//TODO: Implement SwitchPicker `View` body +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/OptionListPickerItem+View.swift` +//TODO: Implement OptionListPickerItem `View` body /// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible /// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` @@ -13,21 +13,21 @@ import SwiftUI // FIXME: - Implement Fiori style definitions -// FIXME: - Implement SwitchPicker View body +// FIXME: - Implement OptionListPickerItem View body -extension SwitchPicker: View { +extension OptionListPickerItem: View { public var body: some View { <# View body #> } } -// FIXME: - Implement SwitchPicker specific LibraryContentProvider +// FIXME: - Implement OptionListPickerItem specific LibraryContentProvider @available(iOS 14.0, macOS 11.0, *) -struct SwitchPickerLibraryContent: LibraryContentProvider { +struct OptionListPickerItemLibraryContent: LibraryContentProvider { @LibraryContentBuilder var views: [LibraryItem] { - LibraryItem(SwitchPicker(model: LibraryPreviewData.Person.laurelosborn), + LibraryItem(OptionListPickerItem(model: LibraryPreviewData.Person.laurelosborn), category: .control) } } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPickerItem+View.generated.swift similarity index 68% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPickerItem+View.generated.swift index 37e1d46ef..b68975e38 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/OptionListPicker+View.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPickerItem+View.generated.swift @@ -1,7 +1,7 @@ // Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT -//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/OptionListPicker+View.swift` -//TODO: Implement OptionListPicker `View` body +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SliderPickerItem+View.swift` +//TODO: Implement SliderPickerItem `View` body /// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible /// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` @@ -13,21 +13,21 @@ import SwiftUI // FIXME: - Implement Fiori style definitions -// FIXME: - Implement OptionListPicker View body +// FIXME: - Implement SliderPickerItem View body -extension OptionListPicker: View { +extension SliderPickerItem: View { public var body: some View { <# View body #> } } -// FIXME: - Implement OptionListPicker specific LibraryContentProvider +// FIXME: - Implement SliderPickerItem specific LibraryContentProvider @available(iOS 14.0, macOS 11.0, *) -struct OptionListPickerLibraryContent: LibraryContentProvider { +struct SliderPickerItemLibraryContent: LibraryContentProvider { @LibraryContentBuilder var views: [LibraryItem] { - LibraryItem(OptionListPicker(model: LibraryPreviewData.Person.laurelosborn), + LibraryItem(SliderPickerItem(model: LibraryPreviewData.Person.laurelosborn), category: .control) } } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPickerItem+View.generated.swift similarity index 66% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPickerItem+View.generated.swift index 2e6706c9b..e37cd3f8a 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SliderPicker+View.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/SwitchPickerItem+View.generated.swift @@ -1,7 +1,7 @@ // Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT -//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SliderPicker+View.swift` -//TODO: Implement SliderPicker `View` body +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/SwitchPickerItem+View.swift` +//TODO: Implement SwitchPickerItem `View` body /// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible /// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` @@ -13,21 +13,21 @@ import SwiftUI // FIXME: - Implement Fiori style definitions -// FIXME: - Implement SliderPicker View body +// FIXME: - Implement SwitchPickerItem View body -extension SliderPicker: View { +extension SwitchPickerItem: View { public var body: some View { <# View body #> } } -// FIXME: - Implement SliderPicker specific LibraryContentProvider +// FIXME: - Implement SwitchPickerItem specific LibraryContentProvider @available(iOS 14.0, macOS 11.0, *) -struct SliderPickerLibraryContent: LibraryContentProvider { +struct SwitchPickerItemLibraryContent: LibraryContentProvider { @LibraryContentBuilder var views: [LibraryItem] { - LibraryItem(SliderPicker(model: LibraryPreviewData.Person.laurelosborn), + LibraryItem(SwitchPickerItem(model: LibraryPreviewData.Person.laurelosborn), category: .control) } } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift deleted file mode 100644 index 922a0296d..000000000 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPicker+Init.generated.swift +++ /dev/null @@ -1,3 +0,0 @@ -// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery -// DO NOT EDIT -import SwiftUI diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPickerItem+Init.generated.swift similarity index 100% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SliderPicker+Init.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/SwitchPickerItem+Init.generated.swift diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/ObjectItemModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/ObjectItemModel+Extensions.generated.swift deleted file mode 100644 index e0f1b11f8..000000000 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/ObjectItemModel+Extensions.generated.swift +++ /dev/null @@ -1,9 +0,0 @@ -// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery -// DO NOT EDIT -import SwiftUI - -public extension ObjectItemModel { - var action: ActionModel? { - return nil - } -} diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerItemModel+Extensions.generated.swift similarity index 80% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerItemModel+Extensions.generated.swift index bd22eec19..5d016c78c 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerModel+Extensions.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerItemModel+Extensions.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -public extension OptionListPickerModel { +public extension OptionListPickerItemModel { var onTap: ((_ index: Int) -> Void)? { return nil } From 2bf467b8d2db3eb842841057dc61834a7405b036 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 14:34:33 -0800 Subject: [PATCH 16/22] refactor & review --- .../Models/ModelDefinitions.swift | 2 +- ...ift => FilterFeedbackBarButton+View.swift} | 29 +++++---- .../Views/OptionListPickerItem+View.swift | 4 +- ...lterFeedbackBarButton+API.generated.swift} | 14 ++--- .../API/KPIProgressItem+API.generated.swift | 2 +- .../API/ObjectHeader+API.generated.swift | 6 +- .../SearchableListView+API.generated.swift | 2 +- .../SignatureCaptureView+API.generated.swift | 40 ++++++------ .../API/SingleStep+API.generated.swift | 10 +-- .../StepProgressIndicator+API.generated.swift | 4 +- .../API/UserConsentForm+API.generated.swift | 2 +- ...lterFeedbackBarButton+View.generated.swift | 62 +++++++++++++++++++ ...terFeedbackBarButton+Init.generated.swift} | 2 +- ...ObjectItemModel+Extensions.generated.swift | 9 +++ 14 files changed, 129 insertions(+), 59 deletions(-) rename Sources/FioriSwiftUICore/Views/{OptionChip+View.swift => FilterFeedbackBarButton+View.swift} (84%) rename Sources/FioriSwiftUICore/_generated/ViewModels/API/{OptionChip+API.generated.swift => FilterFeedbackBarButton+API.generated.swift} (68%) create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/FilterFeedbackBarButton+View.generated.swift rename Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/{OptionChip+Init.generated.swift => FilterFeedbackBarButton+Init.generated.swift} (83%) create mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/ObjectItemModel+Extensions.generated.swift diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index 4bc58d8a9..ad863eb5f 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -480,7 +480,7 @@ public protocol FilterFeedbackBarItemModel: LeftIconComponent, TitleComponent, R // sourcery: add_env_props = "optionListPickerStyle" // sourcery: generated_component_composite -public protocol OptionChipModel: LeftIconComponent, TitleComponent { +public protocol FilterFeedbackBarButtonModel: LeftIconComponent, TitleComponent { // sourcery: no_view var isSelected: Bool { get } } diff --git a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift b/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift similarity index 84% rename from Sources/FioriSwiftUICore/Views/OptionChip+View.swift rename to Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift index a8ab68a71..5b9eb1df2 100644 --- a/Sources/FioriSwiftUICore/Views/OptionChip+View.swift +++ b/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift @@ -3,7 +3,7 @@ import SwiftUI extension Fiori { - enum OptionChip { + enum FilterFeedbackBarButton { typealias LeftIcon = EmptyModifier typealias LeftIconCumulative = EmptyModifier typealias Title = EmptyModifier @@ -16,7 +16,7 @@ extension Fiori { } } -extension OptionChip: View { +extension FilterFeedbackBarButton: View { public var body: some View { optionListPickerStyle.makeBody(configuration: OptionListPickerButtonConfiguration(leftIcon: AnyView(leftIcon), title: AnyView(title), isSelected: _isSelected)) } @@ -142,28 +142,27 @@ public extension View { VStack { Spacer() - OptionChip(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: true) - OptionChip(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: false) - OptionChip(title: "Ship", isSelected: true) - OptionChip(title: "Ship", isSelected: false) - OptionChip(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) - OptionChip(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) + FilterFeedbackBarButton(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: true) + FilterFeedbackBarButton(leftIcon: Image(systemName: "airplane"), title: "Airplane", isSelected: false) + FilterFeedbackBarButton(title: "Ship", isSelected: true) + FilterFeedbackBarButton(title: "Ship", isSelected: false) + FilterFeedbackBarButton(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: true) + FilterFeedbackBarButton(leftIcon: Image(systemName: "bus"), title: "Bus", isSelected: false) Spacer() - OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: true) + FilterFeedbackBarButton(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: true) .optionListPickerStyle(font: .largeTitle, foregroundColorSelected: .red, strokeColorSelected: .red, cornerRadius: 25) - OptionChip(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: false) + FilterFeedbackBarButton(leftIcon: Image(systemName: "airplane"), title: "Air Plane", isSelected: false) .optionListPickerStyle(font: .footnote, foregroundColorUnselected: .green, strokeColorSelected: .black) - .optionListPickerStyle(cornerRadius: 16) - OptionChip(title: "Ship", isSelected: true) + FilterFeedbackBarButton(title: "Ship", isSelected: true) .optionListPickerStyle(fillColorSelected: .yellow) - OptionChip(title: "Ship", isSelected: false) + FilterFeedbackBarButton(title: "Ship", isSelected: false) .optionListPickerStyle(fillColorUnselected: .gray) - OptionChip(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) + FilterFeedbackBarButton(leftIcon: Image(systemName: "bus"), title: "Blue Bus", isSelected: true) .optionListPickerStyle(cornerRadius: 20) - OptionChip(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) + FilterFeedbackBarButton(leftIcon: Image(systemName: "bus"), title: "Gray Bus", isSelected: false) .optionListPickerStyle(cornerRadius: 20) Spacer() diff --git a/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift index 5e1a59d6c..a072cfa6a 100644 --- a/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift @@ -7,7 +7,7 @@ extension OptionListPickerItem: View { Grid(horizontalSpacing: 16) { ForEach(0 ..< Int(ceil(Double(_valueOptions.count) / 2.0))) { rowIndex in GridRow { - OptionChip( + FilterFeedbackBarButton( leftIcon: _value.wrappedValue.contains(rowIndex * 2) ? Image(systemName: "checkmark") : nil, title: _valueOptions[rowIndex * 2], isSelected: _value.wrappedValue.contains(rowIndex * 2) @@ -16,7 +16,7 @@ extension OptionListPickerItem: View { _onTap?(rowIndex * 2) } if rowIndex * 2 + 1 < _valueOptions.count { - OptionChip( + FilterFeedbackBarButton( leftIcon: _value.wrappedValue.contains(rowIndex * 2 + 1) ? Image(systemName: "checkmark") : nil, title: _valueOptions[rowIndex * 2 + 1], isSelected: _value.wrappedValue.contains(rowIndex * 2 + 1) diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarButton+API.generated.swift similarity index 68% rename from Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarButton+API.generated.swift index 52f844c76..c4ecff09a 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionChip+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/FilterFeedbackBarButton+API.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -public struct OptionChip { +public struct FilterFeedbackBarButton { @Environment(\.leftIconModifier) private var leftIconModifier @Environment(\.titleModifier) private var titleModifier @Environment(\.optionListPickerStyle) var optionListPickerStyle @@ -27,16 +27,16 @@ public struct OptionChip { @ViewBuilder var leftIcon: some View { if isModelInit { - _leftIcon.modifier(leftIconModifier.concat(Fiori.OptionChip.leftIcon).concat(Fiori.OptionChip.leftIconCumulative)) + _leftIcon.modifier(leftIconModifier.concat(Fiori.FilterFeedbackBarButton.leftIcon).concat(Fiori.FilterFeedbackBarButton.leftIconCumulative)) } else { - _leftIcon.modifier(leftIconModifier.concat(Fiori.OptionChip.leftIcon)) + _leftIcon.modifier(leftIconModifier.concat(Fiori.FilterFeedbackBarButton.leftIcon)) } } @ViewBuilder var title: some View { if isModelInit { - _title.modifier(titleModifier.concat(Fiori.OptionChip.title).concat(Fiori.OptionChip.titleCumulative)) + _title.modifier(titleModifier.concat(Fiori.FilterFeedbackBarButton.title).concat(Fiori.FilterFeedbackBarButton.titleCumulative)) } else { - _title.modifier(titleModifier.concat(Fiori.OptionChip.title)) + _title.modifier(titleModifier.concat(Fiori.FilterFeedbackBarButton.title)) } } @@ -45,10 +45,10 @@ public struct OptionChip { } } -extension OptionChip where LeftIcon == _ConditionalContent, +extension FilterFeedbackBarButton where LeftIcon == _ConditionalContent, Title == Text { - public init(model: OptionChipModel) { + public init(model: FilterFeedbackBarButtonModel) { self.init(leftIcon: model.leftIcon, title: model.title, isSelected: model.isSelected) } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/KPIProgressItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/KPIProgressItem+API.generated.swift index ecfd596a2..65ed06049 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/KPIProgressItem+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/KPIProgressItem+API.generated.swift @@ -13,8 +13,8 @@ public struct KPIProgressItem { let _fraction: Double? let _subtitle: Subtitle let _footnote: Footnote - var action: (() -> Void)? = nil @State var isPressed: Bool = false + var action: (() -> Void)? = nil private var isModelInit: Bool = false private var isKpiNil: Bool = false private var isSubtitleNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift index c3e75af89..e52f7115a 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/ObjectHeader+API.generated.swift @@ -25,12 +25,12 @@ public struct ObjectHeader { let _cancelAction: CancelActionView let _doneAction: DoneActionView - var contentView: AnyView? = nil var isTopLevel: Bool = true + var contentView: AnyView? = nil var dataHandler: (() -> ())? = nil private var isModelInit: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift index ee8e4b882..c88c2e8a9 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SignatureCaptureView+API.generated.swift @@ -19,32 +19,32 @@ public struct SignatureCaptureView Void)? let _onDelete: (() -> Void)? - var strokeColor = Color.preferredColor(.primaryLabel) - var watermarkTextFont: UIFont = .preferredFont(forTextStyle: .caption1) - @State var currentDrawing = Drawing() - var appliesTintColorToImage = true - var _drawingViewMaxHeight: CGFloat? + var strokeWidth: CGFloat = 3.0 var cropsImage = false - var watermarkTextAlignment: NSTextAlignment = .natural + public private(set) var _heightDidChangePublisher = CurrentValueSubject(0) + @State var isSaved = false + var timestampFormatter: DateFormatter? var xmarkColor = Color.preferredColor(.quarternaryLabel) - var addsTimestampInImage: Bool = false @State var isEditing = false - var timestampFormatter: DateFormatter? - var strokeWidth: CGFloat = 3.0 - var drawingViewBackgroundColor = Color.preferredColor(.primaryBackground) - @State var isReenterTapped = false - let _drawingViewMinHeight: CGFloat = 256 - var signatureLineColor = Color.preferredColor(.quarternaryLabel) - @State var drawings = [Drawing]() - @State var fullSignatureImage: UIImage? var watermarkText: String? - var titleFont = Font.fiori(forTextStyle: .subheadline).weight(.semibold) - public private(set) var _heightDidChangePublisher = CurrentValueSubject(0) - @State var isSaved = false - var hidesSignatureLine = false - var hidesXmark = false var watermarkTextColor: Color = .preferredColor(.tertiaryLabel) + @State var fullSignatureImage: UIImage? + @State var drawings = [Drawing]() + @State var currentDrawing = Drawing() + var watermarkTextFont: UIFont = .preferredFont(forTextStyle: .caption1) + var signatureLineColor = Color.preferredColor(.quarternaryLabel) + var hidesXmark = false + var watermarkTextAlignment: NSTextAlignment = .natural + let _drawingViewMinHeight: CGFloat = 256 var titleColor = Color.preferredColor(.primaryLabel) + var strokeColor = Color.preferredColor(.primaryLabel) + @State var isReenterTapped = false + var _drawingViewMaxHeight: CGFloat? + var titleFont = Font.fiori(forTextStyle: .subheadline).weight(.semibold) + var drawingViewBackgroundColor = Color.preferredColor(.primaryBackground) + var addsTimestampInImage: Bool = false + var hidesSignatureLine = false + var appliesTintColorToImage = true private var isModelInit: Bool = false private var isTitleNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift index d3e64f786..7be62a842 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/SingleStep+API.generated.swift @@ -14,15 +14,15 @@ public struct SingleStep Void)? let _didDeny: ((Bool) -> Void)? let _didCancel: (() -> Void)? - @State var _showAlert: (Bool, UserConsentAlertType) = (false, .deny) @State var _pageIndex = 0 + @State var _showAlert: (Bool, UserConsentAlertType) = (false, .deny) private var isModelInit: Bool = false private var isNextActionNil: Bool = false diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/FilterFeedbackBarButton+View.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/FilterFeedbackBarButton+View.generated.swift new file mode 100644 index 000000000..4d7d4fa2c --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Boilerplate/FilterFeedbackBarButton+View.generated.swift @@ -0,0 +1,62 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +//TODO: Copy commented code to new file: `FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift` +//TODO: Implement default Fiori style definitions as `ViewModifier` +//TODO: Implement FilterFeedbackBarButton `View` body +//TODO: Implement LibraryContentProvider + +/// - Important: to make `@Environment` properties (e.g. `horizontalSizeClass`), internally accessible +/// to extensions, add as sourcery annotation in `FioriSwiftUICore/Models/ModelDefinitions.swift` +/// to declare a wrapped property +/// e.g.: `// sourcery: add_env_props = ["horizontalSizeClass"]` + +/* +import SwiftUI + +// FIXME: - Implement Fiori style definitions + +extension Fiori { + enum FilterFeedbackBarButton { + typealias LeftIcon = EmptyModifier + typealias LeftIconCumulative = EmptyModifier + typealias Title = EmptyModifier + typealias TitleCumulative = EmptyModifier + + // TODO: - substitute type-specific ViewModifier for EmptyModifier + /* + // replace `typealias Subtitle = EmptyModifier` with: + + struct Subtitle: ViewModifier { + func body(content: Content) -> some View { + content + .font(.body) + .foregroundColor(.preferredColor(.primary3)) + } + } + */ + static let leftIcon = LeftIcon() + static let title = Title() + static let leftIconCumulative = LeftIconCumulative() + static let titleCumulative = TitleCumulative() + } +} + +// FIXME: - Implement FilterFeedbackBarButton View body + +extension FilterFeedbackBarButton: View { + public var body: some View { + <# View body #> + } +} + +// FIXME: - Implement FilterFeedbackBarButton specific LibraryContentProvider + +@available(iOS 14.0, macOS 11.0, *) +struct FilterFeedbackBarButtonLibraryContent: LibraryContentProvider { + @LibraryContentBuilder + var views: [LibraryItem] { + LibraryItem(FilterFeedbackBarButton(model: LibraryPreviewData.Person.laurelosborn), + category: .control) + } +} +*/ diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/FilterFeedbackBarButton+Init.generated.swift similarity index 83% rename from Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift rename to Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/FilterFeedbackBarButton+Init.generated.swift index b50093bb2..fd9df8849 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/OptionChip+Init.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Init+Extensions/FilterFeedbackBarButton+Init.generated.swift @@ -2,7 +2,7 @@ // DO NOT EDIT import SwiftUI -extension OptionChip where LeftIcon == EmptyView { +extension FilterFeedbackBarButton where LeftIcon == EmptyView { public init( @ViewBuilder title: () -> Title, isSelected: Bool diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/ObjectItemModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/ObjectItemModel+Extensions.generated.swift new file mode 100644 index 000000000..e0f1b11f8 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/ObjectItemModel+Extensions.generated.swift @@ -0,0 +1,9 @@ +// Generated using Sourcery 1.2.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import SwiftUI + +public extension ObjectItemModel { + var action: ActionModel? { + return nil + } +} From 0bdb1d1c9fc201f7a9de6872d9310c46ca3ca3ca Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 14:42:22 -0800 Subject: [PATCH 17/22] refactor & review --- ...enuItem+Style.swift => FilterFeedbackBarItem+Style.swift} | 0 ...rMenuItem+View.swift => FilterFeedbackBarItem+View.swift} | 0 Sources/FioriSwiftUICore/Views/SwitchPickerItem+View.swift | 5 +++-- 3 files changed, 3 insertions(+), 2 deletions(-) rename Sources/FioriSwiftUICore/Views/SortFilter/{SortFilterMenuItem+Style.swift => FilterFeedbackBarItem+Style.swift} (100%) rename Sources/FioriSwiftUICore/Views/SortFilter/{SortFilterMenuItem+View.swift => FilterFeedbackBarItem+View.swift} (100%) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift similarity index 100% rename from Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+Style.swift rename to Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift similarity index 100% rename from Sources/FioriSwiftUICore/Views/SortFilter/SortFilterMenuItem+View.swift rename to Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift diff --git a/Sources/FioriSwiftUICore/Views/SwitchPickerItem+View.swift b/Sources/FioriSwiftUICore/Views/SwitchPickerItem+View.swift index 15a3d4dc3..8051abe0b 100644 --- a/Sources/FioriSwiftUICore/Views/SwitchPickerItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SwitchPickerItem+View.swift @@ -23,6 +23,7 @@ private extension Binding { } } +/// Experiemental Fiori switch/toggle style public struct FioriToggleStyle: ToggleStyle { @ScaledMetric var scale: CGFloat = 1 @@ -88,11 +89,11 @@ public struct FioriToggleStyle: ToggleStyle { } } -public struct FioriToggleStyleKey: EnvironmentKey { +struct FioriToggleStyleKey: EnvironmentKey { public static var defaultValue: any ToggleStyle = FioriToggleStyle() } -public extension EnvironmentValues { +extension EnvironmentValues { var fioriToggleStyle: any ToggleStyle { get { self[FioriToggleStyleKey.self] From 4877f63b96cdc24fc700b7227c739a8096f981f5 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 14:47:20 -0800 Subject: [PATCH 18/22] refactor & review --- .../Views/SortFilter/FilterFeedbackBarItem+Style.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift index b9b06f9ee..a2b9ce094 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift @@ -20,6 +20,7 @@ public struct FilterFeedbackBarStyleConfiguration { public protocol FilterFeedbackBarStyle { typealias Configuration = FilterFeedbackBarStyleConfiguration + /// Build view according to configuration and style func makeBody(configuration: Self.Configuration) -> AnyView } @@ -38,6 +39,7 @@ public struct DefaultFilterFeedbackBarStyle: FilterFeedbackBarStyle { let borderWidth: CGFloat let minHeight: CGFloat + /// :nodoc: public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 10, spacing: CGFloat = 6, padding: CGFloat = 8, borderWidth: CGFloat = 1, minHeight: CGFloat = 38) { self.font = font self.foregroundColorSelected = foregroundColorSelected @@ -53,6 +55,7 @@ public struct DefaultFilterFeedbackBarStyle: FilterFeedbackBarStyle { self.minHeight = minHeight } + /// Build view according to configuration and style public func makeBody(configuration: Configuration) -> AnyView { AnyView( HStack(spacing: self.spacing) { From 1ff5554778bc4634bc61380b9e02f4b2f7b17061 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 14:49:21 -0800 Subject: [PATCH 19/22] refactor & review --- .../FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift index 2c4d611c1..af923f808 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/SortFilterItemTitle.swift @@ -26,5 +26,5 @@ struct SortFilterItemTitle: TitleComponent, View { } #Preview { - SortFilterItemTitle(title: /*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + SortFilterItemTitle(title: "My Title") } From 32ec3281eb247bfa8769836b5bd0e516177a5d86 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 14:56:07 -0800 Subject: [PATCH 20/22] refactor & review --- .../SortFilter/_SortFilterMenuItemContainer.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift index 1271455b4..f36e0a0c8 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/_SortFilterMenuItemContainer.swift @@ -59,13 +59,21 @@ struct SortFilterMenuItemFullConfigurationButtonKey: EnvironmentKey { /// Filter feedback bar item for displaying full configuration list public struct SortFilterMenuItemFullConfigurationButton { + /// Name/title of the button public let name: String? + /// SF icon name of the button public let icon: String? + /// Position of the button public let positon: Position /// Location of the button public enum Position { - case leading, trailing, none + /// Disaplay the button as the first one + case leading + /// Display the button as the last one + case trailing + /// No button displayed + case none } private init(name: String? = nil, icon: String? = nil, positon: Position) { From 839db8f4f26cc6c061862703b5f21769264a499c Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 15:04:04 -0800 Subject: [PATCH 21/22] refactor & review --- .../Views/FilterFeedbackBarButton+View.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift b/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift index 5b9eb1df2..2c2e5c94b 100644 --- a/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift +++ b/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift @@ -49,10 +49,8 @@ public struct OptionListPickerButtonConfiguration { /// Option list picker style public protocol OptionListPickerStyle { - typealias Configuration = OptionListPickerButtonConfiguration - /// :nodoc: - func makeBody(configuration: Self.Configuration) -> AnyView + func makeBody(configuration: OptionListPickerButtonConfiguration) -> AnyView } /// Default option list picker style @@ -87,7 +85,7 @@ public struct DefaultOptionListPickerStyle: OptionListPickerStyle { } /// :nodoc: - public func makeBody(configuration: Configuration) -> AnyView { + public func makeBody(configuration: OptionListPickerButtonConfiguration) -> AnyView { AnyView( HStack(spacing: self.spacing) { configuration.leftIcon From 4c9caf618b5b92610159aca218efd1b3d7a830f0 Mon Sep 17 00:00:00 2001 From: Charles Xu Date: Mon, 6 Nov 2023 15:04:04 -0800 Subject: [PATCH 22/22] feat: refactor & review --- .../Views/FilterFeedbackBarButton+View.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift b/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift index 5b9eb1df2..2c2e5c94b 100644 --- a/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift +++ b/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift @@ -49,10 +49,8 @@ public struct OptionListPickerButtonConfiguration { /// Option list picker style public protocol OptionListPickerStyle { - typealias Configuration = OptionListPickerButtonConfiguration - /// :nodoc: - func makeBody(configuration: Self.Configuration) -> AnyView + func makeBody(configuration: OptionListPickerButtonConfiguration) -> AnyView } /// Default option list picker style @@ -87,7 +85,7 @@ public struct DefaultOptionListPickerStyle: OptionListPickerStyle { } /// :nodoc: - public func makeBody(configuration: Configuration) -> AnyView { + public func makeBody(configuration: OptionListPickerButtonConfiguration) -> AnyView { AnyView( HStack(spacing: self.spacing) { configuration.leftIcon