From 5c20894f21b09d5813f010b08848fba984ecf9e7 Mon Sep 17 00:00:00 2001 From: Bill Zhou Date: Mon, 12 Aug 2024 13:46:36 -0700 Subject: [PATCH 01/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[IOSSDKBUG-291]=20s?= =?UTF-8?q?et=20line=20limit=20for=20MenuSelectionItem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_FioriStyles/MenuSelectionItemStyle.fiori.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift index 694a86b93..c6f8a3f6f 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift @@ -71,10 +71,11 @@ extension MenuSelectionItemFioriStyle { // Add default style for Title .foregroundStyle(Color.preferredColor(.primaryLabel)) .font(.fiori(forTextStyle: .headline)) + .lineLimit(8) } } } #Preview(body: { - MenuSelectionItem(icon: FioriIcon.documents.attachmentTextFile, title: "1099 Form") + MenuSelectionItem(icon: FioriIcon.documents.attachmentTextFile, title: "This is a very long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long text") }) From fcbf54f4c138860a537166b17057f97ccf2dcbdd Mon Sep 17 00:00:00 2001 From: dyongxu <61523257+dyongxu@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:12:24 -0700 Subject: [PATCH 02/29] merge: from main to rel-4.1 to get the latest translation package and other fixes (#776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(release): 4.1.2 * fix: 🐛 [IOSSDKBUG-291] set line limit for MenuSelectionItem (#769) * fix: 🐛 [JIRA: IOSSDKBUG-306] toolbar layout issue (#771) * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2686] Button update (#753) Co-authored-by: Bill Zhou * fix: 🐛 step progress indicator styles (#775) * Translation Delivery (#774) * [INTERNAL] Translation delivery: commit by LX Lab Change-Id: I111a249ae01afea6ba8029630027e1807e471499 * [INTERNAL] Translation delivery: commit by LX Lab Change-Id: I00c59b0e7a3bcd6346b6fddb8a51787bc132ace4 --------- Co-authored-by: dyongxu <61523257+dyongxu@users.noreply.github.com> --------- Co-authored-by: Marco Eidinger Co-authored-by: Bill Zhou Co-authored-by: Xiaoyu Liu Co-authored-by: hengyi-zhang Co-authored-by: SAP LX Lab Service Account --- .../Examples.xcodeproj/project.pbxproj | 56 +++++ .../CardFixedWidthButtonsExample.swift | 196 +++++++++++++++++ .../CardFullWidthSingleButtonExample.swift | 199 +++++++++++++++++ .../CardTwoButtonsChangeToOneExample.swift | 202 ++++++++++++++++++ .../CardViewWithTwoButtonsExample.swift | 119 +++++++++++ .../FioriButton/FioriButtonContentView.swift | 11 +- .../FioriButtonCustomButtonExample.swift | 182 ++++++++++++++++ .../FioriButtonInCollectionExample.swift | 12 ++ .../FioriButtonInListExample.swift | 42 ++++ ...FioriButtonInListMultipleLineExample.swift | 50 +++++ .../FioriButtonStyleToggleExample.swift | 92 ++++++++ .../FioriButton/FioriButtonTestsExample.swift | 111 ++++++++++ .../InPlaceLoadingContentView.swift | 20 ++ .../InPlaceLoadingFlexibleButtonExample.swift | 55 +++++ .../LoadingButtonSingleStatusExample.swift | 113 ++++++++++ ...ultiLoadingButtonStatusChangeExample.swift | 104 +++++++++ .../StepProgressIndicatorExample.swift | 23 +- CHANGELOG.md | 19 ++ .../sr-Latn-ME.lproj/Localizable.strings | 3 + .../FioriButton/FioriButton.swift | 81 ++++++- .../FioriButton/FioriButtonStyle.swift | 97 ++++++++- .../FioriButtonStyleProvider.swift | 198 +++++++++++------ .../StepProgressIndicator+View.swift | 1 + .../StepProgressIndicator/StepsStyles.swift | 2 +- .../Views/Toolbar/FioriToolbar.swift | 2 +- .../MenuSelectionItemStyle.fiori.swift | 3 +- .../de-CH.lproj/FioriSwiftUICore.strings | 24 +++ .../it-CH.lproj/FioriSwiftUICore.strings | 24 +++ .../sr-Latn-ME.lproj/FioriSwiftUICore.strings | 186 ++++++++++++++++ .../sr-Latn-RS.lproj/FioriSwiftUICore.strings | 24 +++ .../sr.lproj/FioriSwiftUICore.strings | 24 +++ sourcery/allPhasesNoCache.sh | 0 32 files changed, 2174 insertions(+), 101 deletions(-) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardFixedWidthButtonsExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardFullWidthSingleButtonExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardTwoButtonsChangeToOneExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardViewWithTwoButtonsExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonCustomButtonExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInCollectionExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInListExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInListMultipleLineExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonStyleToggleExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonTestsExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/InPlaceLoadingContentView.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/InPlaceLoadingFlexibleButtonExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/LoadingButtonSingleStatusExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FioriButton/MultiLoadingButtonStatusChangeExample.swift create mode 100644 Sources/FioriCharts/Resources/sr-Latn-ME.lproj/Localizable.strings create mode 100644 Sources/FioriSwiftUICore/_localization/sr-Latn-ME.lproj/FioriSwiftUICore.strings mode change 100755 => 100644 sourcery/allPhasesNoCache.sh diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index e784a71c7..358ca7f77 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -34,6 +34,20 @@ 8732C2C72C3524B6002110E9 /* TimelineItemsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2C62C3524B6002110E9 /* TimelineItemsExample.swift */; }; 8732C2C92C3524C9002110E9 /* SimpleTimelineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2C82C3524C9002110E9 /* SimpleTimelineExample.swift */; }; 8732C2CB2C3524D9002110E9 /* CustomTimelineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2CA2C3524D9002110E9 /* CustomTimelineExample.swift */; }; + 6D6E86252C50D42000EDB6F4 /* FioriButtonInListExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86242C50D42000EDB6F4 /* FioriButtonInListExample.swift */; }; + 6D6E86292C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86282C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift */; }; + 6D6E86672C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86662C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift */; }; + 6D6E866B2C5238A000EDB6F4 /* MultiLoadingButtonStatusChangeExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E866A2C5238A000EDB6F4 /* MultiLoadingButtonStatusChangeExample.swift */; }; + 6D6E866D2C53969A00EDB6F4 /* CardTwoButtonsChangeToOneExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E866C2C53969A00EDB6F4 /* CardTwoButtonsChangeToOneExample.swift */; }; + 6D6E866F2C539CDE00EDB6F4 /* InPlaceLoadingFlexibleButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E866E2C539CDE00EDB6F4 /* InPlaceLoadingFlexibleButtonExample.swift */; }; + 6D6E86712C53A0D500EDB6F4 /* CardViewWithTwoButtonsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86702C53A0D500EDB6F4 /* CardViewWithTwoButtonsExample.swift */; }; + 6DEC31F42C463ED50084DD20 /* FioriButtonTestsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC31F32C463ED50084DD20 /* FioriButtonTestsExample.swift */; }; + 6DEC31F82C47B7850084DD20 /* FioriButtonStyleToggleExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC31F72C47B7850084DD20 /* FioriButtonStyleToggleExample.swift */; }; + 6DEC31FA2C48B35D0084DD20 /* FioriButtonCustomButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC31F92C48B35D0084DD20 /* FioriButtonCustomButtonExample.swift */; }; + 6DEC31FE2C48FAA50084DD20 /* InPlaceLoadingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC31FD2C48FAA50084DD20 /* InPlaceLoadingContentView.swift */; }; + 6DEC32002C48FB010084DD20 /* LoadingButtonSingleStatusExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC31FF2C48FB010084DD20 /* LoadingButtonSingleStatusExample.swift */; }; + 6DEC32022C4A4DC70084DD20 /* CardFullWidthSingleButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC32012C4A4DC70084DD20 /* CardFullWidthSingleButtonExample.swift */; }; + 6DEC32042C4E49C70084DD20 /* CardFixedWidthButtonsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC32032C4E49C70084DD20 /* CardFixedWidthButtonsExample.swift */; }; 878219C42BEE128E002FDFBC /* StepperViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878219C32BEE128E002FDFBC /* StepperViewExample.swift */; }; 8A55795724C1286E0098003A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A55795624C1286E0098003A /* AppDelegate.swift */; }; 8A55795924C1286E0098003A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A55795824C1286E0098003A /* SceneDelegate.swift */; }; @@ -222,6 +236,20 @@ 8732C2C62C3524B6002110E9 /* TimelineItemsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemsExample.swift; sourceTree = ""; }; 8732C2C82C3524C9002110E9 /* SimpleTimelineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleTimelineExample.swift; sourceTree = ""; }; 8732C2CA2C3524D9002110E9 /* CustomTimelineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimelineExample.swift; sourceTree = ""; }; + 6D6E86242C50D42000EDB6F4 /* FioriButtonInListExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInListExample.swift; sourceTree = ""; }; + 6D6E86282C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInListMultipleLineExample.swift; sourceTree = ""; }; + 6D6E86662C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInCollectionExample.swift; sourceTree = ""; }; + 6D6E866A2C5238A000EDB6F4 /* MultiLoadingButtonStatusChangeExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLoadingButtonStatusChangeExample.swift; sourceTree = ""; }; + 6D6E866C2C53969A00EDB6F4 /* CardTwoButtonsChangeToOneExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardTwoButtonsChangeToOneExample.swift; sourceTree = ""; }; + 6D6E866E2C539CDE00EDB6F4 /* InPlaceLoadingFlexibleButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPlaceLoadingFlexibleButtonExample.swift; sourceTree = ""; }; + 6D6E86702C53A0D500EDB6F4 /* CardViewWithTwoButtonsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardViewWithTwoButtonsExample.swift; sourceTree = ""; }; + 6DEC31F32C463ED50084DD20 /* FioriButtonTestsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonTestsExample.swift; sourceTree = ""; }; + 6DEC31F72C47B7850084DD20 /* FioriButtonStyleToggleExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonStyleToggleExample.swift; sourceTree = ""; }; + 6DEC31F92C48B35D0084DD20 /* FioriButtonCustomButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonCustomButtonExample.swift; sourceTree = ""; }; + 6DEC31FD2C48FAA50084DD20 /* InPlaceLoadingContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPlaceLoadingContentView.swift; sourceTree = ""; }; + 6DEC31FF2C48FB010084DD20 /* LoadingButtonSingleStatusExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingButtonSingleStatusExample.swift; sourceTree = ""; }; + 6DEC32012C4A4DC70084DD20 /* CardFullWidthSingleButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardFullWidthSingleButtonExample.swift; sourceTree = ""; }; + 6DEC32032C4E49C70084DD20 /* CardFixedWidthButtonsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardFixedWidthButtonsExample.swift; sourceTree = ""; }; 878219C32BEE128E002FDFBC /* StepperViewExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepperViewExample.swift; sourceTree = ""; }; 8A1E99AD24D59C8000ED8A39 /* cloud-sdk-ios-fiori */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "cloud-sdk-ios-fiori"; path = ../..; sourceTree = ""; }; 8A55795324C1286E0098003A /* Examples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Examples.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -392,6 +420,20 @@ children = ( 1F90888B261A59820015A84D /* FioriButtonExample.swift */, 1F26DCF9261A5CD9006C43B1 /* FioriButtonContentView.swift */, + 6DEC31F32C463ED50084DD20 /* FioriButtonTestsExample.swift */, + 6DEC31F72C47B7850084DD20 /* FioriButtonStyleToggleExample.swift */, + 6D6E86242C50D42000EDB6F4 /* FioriButtonInListExample.swift */, + 6D6E86282C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift */, + 6D6E86662C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift */, + 6DEC31F92C48B35D0084DD20 /* FioriButtonCustomButtonExample.swift */, + 6DEC31FD2C48FAA50084DD20 /* InPlaceLoadingContentView.swift */, + 6DEC31FF2C48FB010084DD20 /* LoadingButtonSingleStatusExample.swift */, + 6D6E866A2C5238A000EDB6F4 /* MultiLoadingButtonStatusChangeExample.swift */, + 6DEC32012C4A4DC70084DD20 /* CardFullWidthSingleButtonExample.swift */, + 6DEC32032C4E49C70084DD20 /* CardFixedWidthButtonsExample.swift */, + 6D6E866C2C53969A00EDB6F4 /* CardTwoButtonsChangeToOneExample.swift */, + 6D6E866E2C539CDE00EDB6F4 /* InPlaceLoadingFlexibleButtonExample.swift */, + 6D6E86702C53A0D500EDB6F4 /* CardViewWithTwoButtonsExample.swift */, ); path = FioriButton; sourceTree = ""; @@ -975,10 +1017,12 @@ B8101D52268BB84B00D32560 /* ContactItemTapStateExamples.swift in Sources */, 8A55795724C1286E0098003A /* AppDelegate.swift in Sources */, C106AD4A2B33970500FE8B35 /* SearchPromptFontAndColor.swift in Sources */, + 6DEC32022C4A4DC70084DD20 /* CardFullWidthSingleButtonExample.swift in Sources */, B1BA1F972B2167DC00E6C052 /* ToolbarExample.swift in Sources */, B18D2E9F2988B07B000A1821 /* KPIHeaderExample.swift in Sources */, 9DEC27992C3C5C620070B571 /* RatingControlExample.swift in Sources */, 8AB6C01428DF6583002F32BE /* LazyView.swift in Sources */, + 6D6E86672C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift in Sources */, 691DE21925F2A30B00094D4A /* KPIViewExample.swift in Sources */, AB988B102631270300483D87 /* DataTableExample.swift in Sources */, 99B6EF8C2672224D00515E8E /* UserConsentSample.swift in Sources */, @@ -994,6 +1038,8 @@ B80DA9BE260C1CC200C0B2E9 /* ListDataProtocol.swift in Sources */, B1DD86532B0758F000D7EDFD /* NavigationBarPopover.swift in Sources */, 3B62AB7E2C0EE257003262EB /* EditableSideBarExample.swift in Sources */, + 6DEC32002C48FB010084DD20 /* LoadingButtonSingleStatusExample.swift in Sources */, + 6DEC31FA2C48B35D0084DD20 /* FioriButtonCustomButtonExample.swift in Sources */, B1DD86552B0759DD00D7EDFD /* NavigationBarCustomItem.swift in Sources */, B84D24EE2652F343007F2373 /* ObjectHeaderViewScenarios.swift in Sources */, B1A98FF72C12EA1600FC9998 /* BannerMessageCustomInitExample.swift in Sources */, @@ -1008,6 +1054,7 @@ 8A557A2424C12F380098003A /* ChartDetailView.swift in Sources */, 8A5579D024C1293C0098003A /* SettingsLine.swift in Sources */, B88CB6122B716C0300013B37 /* MobileCardExample.swift in Sources */, + 6DEC31FE2C48FAA50084DD20 /* InPlaceLoadingContentView.swift in Sources */, 1FC30412270540FB004BEE00 /* 72-Fonts.swift in Sources */, C1A0FDB32AD893FA0001738E /* SortFilterView+Extensions.swift in Sources */, B84D24ED2652F343007F2373 /* HeaderChartExample.swift in Sources */, @@ -1015,6 +1062,7 @@ 1F1A1FFA2C0BDA54007109D8 /* MenuSelectionExample.swift in Sources */, B846F94626815CC90085044B /* ContactItemExample.swift in Sources */, 8A5579CC24C1293C0098003A /* SettingsColorForCategory.swift in Sources */, + 6D6E866B2C5238A000EDB6F4 /* MultiLoadingButtonStatusChangeExample.swift in Sources */, C18868D12B32535100F865F7 /* SearchFontAndColor.swift in Sources */, 9D0B26092B9BA5C0004278A5 /* KeyValueFormViewExample.swift in Sources */, 8732C2C52C350957002110E9 /* TimelineExample.swift in Sources */, @@ -1022,6 +1070,7 @@ 8A557A1A24C12C820098003A /* ChartsContentView.swift in Sources */, 8A5579CE24C1293C0098003A /* SettingColor.swift in Sources */, 1F55FEF32AC941FF00D7A1BE /* View+Extensions.swift in Sources */, + 6DEC31F42C463ED50084DD20 /* FioriButtonTestsExample.swift in Sources */, 8A6DE30B28DD27F9003222E3 /* Colors.swift in Sources */, 8AD9DFB225D49967007448EC /* StylingModifierExample.swift in Sources */, 9D0B260A2B9BA5C0004278A5 /* NoteFormViewExample.swift in Sources */, @@ -1033,12 +1082,14 @@ B1A98FF52C12E9A000FC9998 /* BannerMessageModifierExample.swift in Sources */, B1BA1F922B19AAEE00E6C052 /* TabViewDetailView.swift in Sources */, B1DD864F2B07441C00D7EDFD /* UIFont+Fiori.swift in Sources */, + 6D6E86712C53A0D500EDB6F4 /* CardViewWithTwoButtonsExample.swift in Sources */, B8D4376F25F980340024EE7D /* ObjectCell_Spec_Jan2018.swift in Sources */, 8A5579CF24C1293C0098003A /* SettingsAxis.swift in Sources */, B80DA9BC260BED9400C0B2E9 /* SingleActionCollectionView.swift in Sources */, 8732C2CB2C3524D9002110E9 /* CustomTimelineExample.swift in Sources */, 8A5579D924C1293C0098003A /* SettingsSelection.swift in Sources */, B1A98FF22C11592B00FC9998 /* BannerMessageExample.swift in Sources */, + 6D6E866D2C53969A00EDB6F4 /* CardTwoButtonsChangeToOneExample.swift in Sources */, B80DA9C62612A54E00C0B2E9 /* ActivationScreenSample.swift in Sources */, B8D437732609479E0024EE7D /* SingleActionFollowButton.swift in Sources */, C18868D32B32580800F865F7 /* ColorEntity.swift in Sources */, @@ -1048,6 +1099,7 @@ B84D24F12652F343007F2373 /* ObjectHeaderSpec.swift in Sources */, B846F94A26815DF30085044B /* ContactItemCompactExamples.swift in Sources */, C106AD442B33710800FE8B35 /* SearchWithScope.swift in Sources */, + 6DEC32042C4E49C70084DD20 /* CardFixedWidthButtonsExample.swift in Sources */, B1C7DC8129FBB13F00DC5EEB /* SPIModelExample.swift in Sources */, C106AD462B338D1300FE8B35 /* SearchWithToken.swift in Sources */, 99193C852B719B8800F33BAF /* InformationViewExample.swift in Sources */, @@ -1071,6 +1123,7 @@ B80DA9C72612A54E00C0B2E9 /* WelcomeScreenSample.swift in Sources */, 8A5579D724C1293C0098003A /* SettingsCategoryAxis.swift in Sources */, 9D0086692BA8F6820004BE15 /* TitleFormViewExample.swift in Sources */, + 6DEC31F82C47B7850084DD20 /* FioriButtonStyleToggleExample.swift in Sources */, B141D6BB29261F9E008A8BD6 /* SearchableListViewExample.swift in Sources */, C106AD482B33940600FE8B35 /* SearchWithBookmark.swift in Sources */, 975CB76B256C5A7400DB7A15 /* SignatureCaptureViewExample.swift in Sources */, @@ -1079,18 +1132,21 @@ 8732C2C92C3524C9002110E9 /* SimpleTimelineExample.swift in Sources */, B190065A2C201BBE000C8B10 /* ProfileHeaderExample.swift in Sources */, B86F02A82679835F0049DDA7 /* ObjectItemInitExamples.swift in Sources */, + 6D6E86292C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift in Sources */, 692F338B26556A6A009B98DA /* SideBarExample.swift in Sources */, 8A5579D324C1293C0098003A /* SettingsPoint.swift in Sources */, B80DA9BA260BBF8600C0B2E9 /* SingleActionProfiles.swift in Sources */, B18D593C2B0C52C700ABB1AD /* TabViewExample.swift in Sources */, 8A5579D424C1293C0098003A /* SettingsBaseline.swift in Sources */, 1F90888C261A59820015A84D /* FioriButtonExample.swift in Sources */, + 6D6E86252C50D42000EDB6F4 /* FioriButtonInListExample.swift in Sources */, B8D4377125F983730024EE7D /* ObjectCell_Rules_Alignment.swift in Sources */, 8A5579D524C1293C0098003A /* SettingsSeries.swift in Sources */, 6432FFA02C5164F8008ECE89 /* SegmentedControlExample.swift in Sources */, 9DEC27B52C3F3DB30070B571 /* KeyValueItemExample.swift in Sources */, 8A557A2224C12C9B0098003A /* CoreContentView.swift in Sources */, 8A5579D224C1293C0098003A /* Color+Extensions.swift in Sources */, + 6D6E866F2C539CDE00EDB6F4 /* InPlaceLoadingFlexibleButtonExample.swift in Sources */, B1BCB6E12C2EB362008AC070 /* ProfileHeaderStaticExample.swift in Sources */, C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */, B1F6FC302B22BDDA005190F9 /* ToolbarView.swift in Sources */, diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardFixedWidthButtonsExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardFixedWidthButtonsExample.swift new file mode 100644 index 000000000..4555151a5 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardFixedWidthButtonsExample.swift @@ -0,0 +1,196 @@ +import FioriSwiftUICore +import FioriThemeManager +import SwiftUI + +struct CardFixedWidthButtonsExample: View { + @State private var buttonTitle = "Check in" + + @Environment(\.dismiss) private var dismiss + + @State private var _dataSource: [CardFullWidthSingleButtonItem] = [ + CardFullWidthSingleButtonItem(title: "1", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "2", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "3", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "4", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "5", loadingState: .unspecified, id: UUID()) + ] + + private var profileHeader: some View { + ProfileHeader(detailImage: { + Image("rw").resizable() + }, title: { + Text("Harry Ford") + }, subtitle: { + Text("The boy wizard, the boy wizard") + }, description: { + Text("This is a description.") + }) { + HStack { + Spacer() + Button { + print("tap message") + } label: { + FioriIcon.callout.discussion + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap email") + } label: { + FioriIcon.actions.email + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap call") + } label: { + FioriIcon.actions.call + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap video") + } label: { + FioriIcon.actions.video + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap hint") + } label: { + FioriIcon.actions.hint + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + } + } + } + + var body: some View { + List { + Section { + Divider() + .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 0)) + + HStack { + Text("My Schedule") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundStyle(Color.preferredColor(.secondaryLabel)) + Spacer() + Button { + print("see all") + } label: { + Text("See all (\(self._dataSource.count))") + .font(.fiori(forTextStyle: .body)) + .foregroundStyle(Color.preferredColor(.tintColor)) + } + } + .padding(EdgeInsets(top: 5, leading: 20, bottom: 5, trailing: 20)) + + ScrollView(.horizontal) { + HStack(spacing: 8, content: { + ForEach(0 ..< self._dataSource.count, id: \.self) { index in + let item = self._dataSource[index] + HStack { + Card { + Text("Schedule\(item.title)") + } subtitle: { + Text("Subtitle") + } detailImage: { + Image("ProfilePic") + .resizable() + .frame(width: 45, height: 45) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } headerAction: { + FioriIcon.shopping.cart + } row1: { + Text("Body text could be really long description that requires wrapping, with suggested 2 lines from Fiori Design Guideline perspective to make the UI concise. SDK default setting of numberOfLines for body is 6. Application Developer can override it with : cell.body.numOfLines = preferredNumberOfLines.") + .lineLimit(2) + } action: { + FioriButton(isSelectionPersistent: false, action: { _ in + self.updateDataSource(id: item.id) + }, label: { _ in + self.primaryActionLabel(item.loadingState) + }, image: { _ in + EmptyView() + }, imagePosition: .leading, imageTitleSpacing: 8.0) + .fioriButtonStyle(FioriPrimaryButtonStyle(118, loadingState: item.loadingState)) + .disabled(item.loadingState != .unspecified) + } secondaryAction: { + FioriButton(isSelectionPersistent: false, title: "Decline", action: { _ in + print("tap Decline") + }) + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .negative, maxWidth: 118)) + } + .frame(width: 300, height: 192) + .background(Color.white) + } + .clipShape(RoundedRectangle(cornerRadius: 16)) + .shadow(color: .preferredColor(.cardShadow), radius: 16) // + .padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) + } + }) + .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)) + } + Spacer() + } header: { + self.profileHeader + .frame(maxWidth: .infinity) + .padding() + .background(Color.preferredColor(.secondaryGroupedBackground)) + } + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .listRowSeparator(.hidden) + } + .listStyle(.grouped) + .navigationTitle("Object Card - Full Width Single Button") + .navigationBarTitleDisplayMode(.inline) + } + + @ViewBuilder + func primaryActionLabel(_ loadingState: FioriButtonLoadingState) -> any View { + loadingState != .unspecified ? AnyView(EmptyView()) : AnyView(Text("Check in")) + } + + func updateDataSource(id: UUID) { + for i in 0 ..< self._dataSource.count { + let item = self._dataSource[i] + if item.id == id { + var timeInterval = 0.0 + if item.loadingState == .unspecified { + item.loadingState = .processing + self._dataSource[i] = item + timeInterval = 2.0 + } else if item.loadingState == .processing { + item.loadingState = .success + self._dataSource[i] = item + timeInterval = 1.0 + } else { + self._dataSource.remove(at: i) + if self._dataSource.isEmpty { + self.dismiss() + } + return + } + + _ = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false, block: { _ in + self.updateDataSource(id: id) + }) + break + } + } + } +} + +#Preview { + CardFixedWidthButtonsExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardFullWidthSingleButtonExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardFullWidthSingleButtonExample.swift new file mode 100644 index 000000000..f5ca849be --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardFullWidthSingleButtonExample.swift @@ -0,0 +1,199 @@ +import FioriSwiftUICore +import FioriThemeManager +import SwiftUI + +class CardFullWidthSingleButtonItem: Identifiable, ObservableObject { + @Published var title: String + @Published var loadingState: FioriButtonLoadingState + @Published var id: UUID + + init(title: String, loadingState: FioriButtonLoadingState, id: UUID) { + self.title = title + self.loadingState = loadingState + self.id = id + } +} + +struct CardFullWidthSingleButtonExample: View { + @State private var buttonTitle = "Check in" + @Environment(\.dismiss) private var dismiss + + @State private var _dataSource: [CardFullWidthSingleButtonItem] = [ + CardFullWidthSingleButtonItem(title: "1", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "2", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "3", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "4", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "5", loadingState: .unspecified, id: UUID()) + ] + + var profileHeader: some View { + ProfileHeader(detailImage: { + Image("rw").resizable() + }, title: { + Text("Harry Ford") + }, subtitle: { + Text("The boy wizard, the boy wizard") + }, description: { + Text("This is a description.") + }) { + HStack { + Spacer() + Button { + print("tap message") + } label: { + FioriIcon.callout.discussion + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap email") + } label: { + FioriIcon.actions.email + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap call") + } label: { + FioriIcon.actions.call + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap video") + } label: { + FioriIcon.actions.video + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap hint") + } label: { + FioriIcon.actions.hint + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + } + } + } + + var body: some View { + Section { + Divider() + HStack { + Text("My Schedule") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundStyle(Color.preferredColor(.secondaryLabel)) + Spacer() + Button { + print("see all") + } label: { + Text("See all (\(self._dataSource.count))") + .font(.fiori(forTextStyle: .body)) + .foregroundStyle(Color.preferredColor(.tintColor)) + } + } + .padding(EdgeInsets(top: 5, leading: 20, bottom: 5, trailing: 20)) + + ScrollView(.horizontal) { + HStack(spacing: 8, content: { + ForEach(0 ..< self._dataSource.count, id: \.self) { index in + let item = self._dataSource[index] + HStack { + Card { + Text("Schedule\(item.title)") + } subtitle: { + Text("Subtitle") + } detailImage: { + Image("ProfilePic") + .resizable() + .frame(width: 45, height: 45) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } headerAction: { + FioriIcon.shopping.cart + } row1: { + Text("Body text could be really long description that requires wrapping, with suggested 2 lines from Fiori Design Guideline perspective to make the UI concise. SDK default setting of numberOfLines for body is 6. Application Developer can override it with : cell.body.numOfLines = preferredNumberOfLines.") + .lineLimit(2) + } action: { + FioriButton(action: { _ in + self.updateDataSource(id: item.id) + }, label: { _ in + Text(self.titleStr(item.loadingState)) + }) + .fioriButtonStyle(FioriPrimaryButtonStyle(.infinity, loadingState: item.loadingState)) + .disabled(item.loadingState != .unspecified) + } + .frame(width: 300, height: 192) + .background(Color.white) + } + .clipShape(RoundedRectangle(cornerRadius: 16)) + .shadow(color: .preferredColor(.cardShadow), radius: 16) // + .padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) + } + }) + .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)) + } + Spacer() + } header: { + self.profileHeader + .frame(maxWidth: .infinity) + .padding() + .background(Color.preferredColor(.secondaryGroupedBackground)) + } + .navigationTitle("Object Card - Full Width Single Button") + .navigationBarTitleDisplayMode(.inline) + } + + func updateDataSource(id: UUID) { + for i in 0 ..< self._dataSource.count { + let item = self._dataSource[i] + if item.id == id { + var timeInterval = 0.0 + if item.loadingState == .unspecified { + item.loadingState = .processing + self._dataSource[i] = item + timeInterval = 2.0 + } else if item.loadingState == .processing { + item.loadingState = .success + self._dataSource[i] = item + timeInterval = 1.0 + } else { + self._dataSource.remove(at: i) + if self._dataSource.isEmpty { + self.dismiss() + } + return + } + + _ = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false, block: { _ in + self.updateDataSource(id: id) + }) + break + } + } + } + + func titleStr(_ loadingState: FioriButtonLoadingState) -> AttributedString { + switch loadingState { + case .unspecified: + "Check in" + case .processing: + "Checking in" + case .success: + "Checked in" + } + } +} + +#Preview { + CardFullWidthSingleButtonExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardTwoButtonsChangeToOneExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardTwoButtonsChangeToOneExample.swift new file mode 100644 index 000000000..63783d96f --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardTwoButtonsChangeToOneExample.swift @@ -0,0 +1,202 @@ +import FioriSwiftUICore +import FioriThemeManager +import SwiftUI + +struct CardTwoButtonsChangeToOneExample: View { + @State private var buttonTitle = "Check in" + @Environment(\.dismiss) private var dismiss + + @State private var _dataSource: [CardFullWidthSingleButtonItem] = [ + CardFullWidthSingleButtonItem(title: "1", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "2", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "3", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "4", loadingState: .unspecified, id: UUID()), + CardFullWidthSingleButtonItem(title: "5", loadingState: .unspecified, id: UUID()) + ] + + var profileHeader: some View { + ProfileHeader(detailImage: { + Image("rw").resizable() + }, title: { + Text("Harry Ford") + }, subtitle: { + Text("The boy wizard, the boy wizard") + }, description: { + Text("This is a description.") + }) { + HStack { + Spacer() + Button { + print("tap message") + } label: { + FioriIcon.callout.discussion + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap email") + } label: { + FioriIcon.actions.email + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap call") + } label: { + FioriIcon.actions.call + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap video") + } label: { + FioriIcon.actions.video + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + + Button { + print("tap hint") + } label: { + FioriIcon.actions.hint + .imageScale(.large) + .fontWeight(.light) + } + Spacer() + } + } + } + + var body: some View { + List { + Section { + Divider() + .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 0)) + + HStack { + Text("My Schedule") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundStyle(Color.preferredColor(.secondaryLabel)) + Spacer() + Button { + print("see all") + } label: { + Text("See all (\(self._dataSource.count))") + .font(.fiori(forTextStyle: .body)) + .foregroundStyle(Color.preferredColor(.tintColor)) + } + } + .padding(EdgeInsets(top: 5, leading: 20, bottom: 5, trailing: 20)) + + ScrollView(.horizontal) { + HStack(spacing: 8, content: { + ForEach(0 ..< self._dataSource.count, id: \.self) { index in + let item = self._dataSource[index] + HStack { + Card { + Text("Schedule\(item.title)") + } subtitle: { + Text("Subtitle") + } detailImage: { + Image("ProfilePic") + .resizable() + .frame(width: 45, height: 45) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } headerAction: { + FioriIcon.shopping.cart + } row1: { + Text("Body text could be really long description that requires wrapping, with suggested 2 lines from Fiori Design Guideline perspective to make the UI concise. SDK default setting of numberOfLines for body is 6. Application Developer can override it with : cell.body.numOfLines = preferredNumberOfLines.") + .lineLimit(2) + } action: { + let maxWidth: CGFloat = item.loadingState == .unspecified ? 118 : .infinity + + FioriButton(isSelectionPersistent: false, title: self.titleStr(item.loadingState)) { _ in + self.updateDataSource(id: item.id) + } + .fioriButtonStyle(FioriPrimaryButtonStyle(maxWidth, loadingState: item.loadingState)) + .disabled(item.loadingState != .unspecified) + + } secondaryAction: { + if item.loadingState == .unspecified { + FioriButton(title: "Decline", action: { _ in + print("tap Decline") + }) + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .negative, maxWidth: 118)) + } + } + .frame(width: 300, height: 192) + .background(Color.white) + } + .clipShape(RoundedRectangle(cornerRadius: 16)) + .shadow(color: .preferredColor(.cardShadow), radius: 16) // + .padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) + } + }) + .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)) + } + Spacer() + } header: { + self.profileHeader + .frame(maxWidth: .infinity) + .padding() + .background(Color.preferredColor(.secondaryGroupedBackground)) + } + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .listRowSeparator(.hidden) + } + .listStyle(.grouped) + .navigationTitle("Object Card - Full Width Single Button") + .navigationBarTitleDisplayMode(.inline) + } + + func updateDataSource(id: UUID) { + for i in 0 ..< self._dataSource.count { + let item = self._dataSource[i] + if item.id == id { + var timeInterval = 0.0 + if item.loadingState == .unspecified { + item.loadingState = .processing + self._dataSource[i] = item + timeInterval = 2.0 + } else if item.loadingState == .processing { + item.loadingState = .success + self._dataSource[i] = item + timeInterval = 1.0 + } else { + self._dataSource.remove(at: i) + if self._dataSource.isEmpty { + self.dismiss() + } + return + } + + _ = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false, block: { _ in + self.updateDataSource(id: id) + }) + break + } + } + } + + func titleStr(_ loadingState: FioriButtonLoadingState) -> AttributedString { + switch loadingState { + case .unspecified: + "Check in" + case .processing: + "Checking in" + case .success: + "Checked in" + } + } +} + +#Preview { + CardTwoButtonsChangeToOneExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardViewWithTwoButtonsExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardViewWithTwoButtonsExample.swift new file mode 100644 index 000000000..ea2ff9067 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/CardViewWithTwoButtonsExample.swift @@ -0,0 +1,119 @@ +import FioriSwiftUICore +import SwiftUI + +struct CardViewWithTwoButtonsExample: View { + @State private var _loadingState: FioriButtonLoadingState = .unspecified + @Environment(\.dismiss) private var dismiss + + var body: some View { + ScrollView { + HStack { + Card { + Image("card_image") + .resizable() + .aspectRatio(contentMode: .fill) + .frame(height: 145) + } description: { + Text("Title") + } title: { + Text("Title that goes to three lines before truncating just like that") + } subtitle: { + Text("Subtitle that goes to two lines before truncating just like that") + } detailImage: { + Image(systemName: "person.crop.circle") + .frame(width: 90, height: 90) + .background(Color.gray.opacity(0.3)) + .cornerRadius(45) + } headerAction: { + Button { + print("tapped") + } label: { + Text("Long button") + } + } counter: { + Text("1 of 3") + } row1: { + FlowLayout(spacing: 8, lineSpacing: 2, lineLimit: 3) { + LabelItem(icon: Image(systemName: "exclamationmark.triangle.fill"), title: "Negative") + .titleStyle { config in + config.title.foregroundStyle(Color.preferredColor(.negativeLabel)) + } + LabelItem(title: "Critical") + .titleStyle { config in + config.title.foregroundStyle(Color.preferredColor(.criticalLabel)) + } + LabelItem(icon: Image(systemName: "checkmark.circle"), title: "Positive") + .titleStyle { config in + config.title.foregroundStyle(Color.preferredColor(.positiveLabel)) + } + Image(systemName: "star") + LabelItem(title: "Long long long label") + Image(systemName: "star.fill") + LabelItem(title: "Multiple lines row1") + } + } row2: { + HStack(spacing: 2) { + Image(systemName: "star.fill") + Image(systemName: "star.fill") + Image(systemName: "star") + Image(systemName: "star") + Image(systemName: "star") + } + } row3: {} cardBody: {} action: { + FioriButton(title: { _ in + self.titleStr(self._loadingState) + }, action: { _ in + self.updateDataSource() + }) + .fioriButtonStyle(FioriPrimaryButtonStyle(.infinity, loadingState: self._loadingState)) + .disabled(self._loadingState != .unspecified) + + } secondaryAction: { + FioriButton(title: "Decline", action: { _ in + print("tap Decline") + }) + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .negative, maxWidth: 118)) + } + .background(Color.white) + } + .clipShape(RoundedRectangle(cornerRadius: 16)) + .shadow(color: .preferredColor(.cardShadow), radius: 16) // + .padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) + } + .padding(10) + .navigationTitle("Card View - With Two Buttons") + } + + func updateDataSource() { + var timeInterval = 0.0 + if self._loadingState == .unspecified { + self._loadingState = .processing + timeInterval = 2.0 + } else if self._loadingState == .processing { + self._loadingState = .success + timeInterval = 1.0 + } else { + self.dismiss() + return + } + + _ = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false, block: { _ in + self.updateDataSource() + }) + } + + func titleStr(_ loadingState: FioriButtonLoadingState) -> AttributedString { + switch loadingState { + case .unspecified: + "Check in" + case .processing: + "Checking in" + case .success: + "Checked in" + } + } +} + +#Preview { + CardViewWithTwoButtonsExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonContentView.swift index 5b81368a1..94bd3ded5 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonContentView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonContentView.swift @@ -4,8 +4,15 @@ import SwiftUI struct FioriButtonContentView: View { var body: some View { List { - NavigationLink("FioriButton", destination: FioriButtonExample()) - NavigationLink("StatefulButtonStyle", destination: StatefulButtonStyleExample()) + NavigationLink("FioriButton", destination: LazyView(FioriButtonExample())) + NavigationLink("StatefulButtonStyle", destination: LazyView(StatefulButtonStyleExample())) + NavigationLink("Button Tests", destination: LazyView(FioriButtonTestsExample())) + NavigationLink("Button Style Toggle", destination: LazyView(FioriButtonStyleToggleExample())) + NavigationLink("Button In List", destination: LazyView(FioriButtonInListExample())) + NavigationLink("Button In List - Multiple Lines", destination: LazyView(FioriButtonInListMultipleLineExample())) + NavigationLink("Custom Button", destination: LazyView(FioriButtonCustomButtonExample())) + NavigationLink("In-Place Loading Button", destination: LazyView(InPlaceLoadingContentView())) } + .navigationTitle("FioriButton") } } diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonCustomButtonExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonCustomButtonExample.swift new file mode 100644 index 000000000..69f48758f --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonCustomButtonExample.swift @@ -0,0 +1,182 @@ +import FioriSwiftUICore +import FioriThemeManager +import SwiftUI + +private enum FioriButtonImagePosition { + case top + case leading + case bottom + case trailing +} + +private enum FioriButtonTitleLength { + case short + case long + case extra +} + +private enum FioriButtonLineLimit { + case none + case one + case two + case three +} + +struct FioriButtonCustomButtonExample: View { + @State private var _truncationMode: Text.TruncationMode = .head + @State private var _loadingState: FioriButtonLoadingState = .unspecified + @State private var _imagePosition: FioriButtonImagePosition = .leading + @State private var _titleAlignment: TextAlignment = .center + @State private var _titleLength: FioriButtonTitleLength = .short + @State private var _lineLimit: FioriButtonLineLimit = .none + + private let spacing = 10.0 + + var body: some View { + ScrollView { + VStack(alignment: .center, spacing: self.spacing) { + FioriButton { _ in + switch self._imagePosition { + case .top: + VStack(spacing: self.spacing, content: { + self.imageView() + self.titleView() + }) + case .leading: + HStack(spacing: self.spacing, content: { + self.imageView() + self.titleView() + }) + case .bottom: + VStack(spacing: self.spacing, content: { + self.titleView() + self.imageView() + }) + case .trailing: + HStack(spacing: self.spacing, content: { + self.titleView() + self.imageView() + }) + } + } + .fioriButtonStyle(FioriPrimaryButtonStyle().eraseToAnyFioriButtonStyle()) + + HStack { + Text("Loading State:") + Spacer() + } + Picker("Loading State:", selection: self.$_loadingState) { + Text("unspecified").tag(FioriButtonLoadingState.unspecified) + Text("processing").tag(FioriButtonLoadingState.processing) + Text("success").tag(FioriButtonLoadingState.success) + } + .pickerStyle(.segmented) + + HStack { + Text("Image Position:") + Spacer() + } + Picker("Image Position:", selection: self.$_imagePosition) { + Text("top").tag(FioriButtonImagePosition.top) + Text("leading").tag(FioriButtonImagePosition.leading) + Text("bottom").tag(FioriButtonImagePosition.bottom) + Text("trailing").tag(FioriButtonImagePosition.trailing) + } + .pickerStyle(.segmented) + + HStack { + Text("Title Alignment:") + Spacer() + } + Picker("Title Alignment", selection: self.$_titleAlignment) { + Text("leading").tag(TextAlignment.leading) + Text("center").tag(TextAlignment.center) + Text("trailing").tag(TextAlignment.trailing) + } + .pickerStyle(.segmented) + + HStack { + Text("Truncation Mode:") + Spacer() + } + Picker("Truncation Mode", selection: self.$_truncationMode) { + Text("head").tag(Text.TruncationMode.head) + Text("middle").tag(Text.TruncationMode.middle) + Text("tail").tag(Text.TruncationMode.tail) + } + .pickerStyle(.segmented) + + HStack { + Text("Title Length:") + Spacer() + } + Picker("Title Length", selection: self.$_titleLength) { + Text("short").tag(FioriButtonTitleLength.short) + Text("long").tag(FioriButtonTitleLength.long) + Text("extra").tag(FioriButtonTitleLength.extra) + } + .pickerStyle(.segmented) + + HStack { + Text("Line Limit:") + Spacer() + } + Picker("Line Limit", selection: self.$_lineLimit) { + Text("none").tag(FioriButtonLineLimit.none) + Text("one").tag(FioriButtonLineLimit.one) + Text("two").tag(FioriButtonLineLimit.two) + Text("three").tag(FioriButtonLineLimit.three) + } + .pickerStyle(.segmented) + } + .padding(EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)) + } + .navigationTitle("Custom Button") + .font(.headline.bold()) + } + + func imageView() -> some View { + switch self._loadingState { + case .unspecified: + AnyView(FioriIcon.actions.actionSettingsFill.font(.fiori(forTextStyle: .subheadline))) + case .processing: + AnyView(ProgressView(value: 0) + .progressViewStyle(.circular)) + case .success: + AnyView(FioriIcon.status.sysEnter.font(.fiori(forTextStyle: .subheadline))) + } + } + + func titleView() -> some View { + var titleStr = "" + switch self._titleLength { + case .short: + titleStr = "Short Title" + case .long: + titleStr = "The title is too too too too too too too too too too too too too too too too long" + case .extra: + titleStr = "The title is too too too too too too too too too too too too too too too too extra extra extra extra extra extra extra extra extra extra extra extra extra extra extra extra extra extra extra extra extra long" + } + return Text(titleStr) + .truncationMode(self._truncationMode) + .multilineTextAlignment(self._titleAlignment) + .lineLimit(self.lineLimit()) + } + + func lineLimit() -> Int? { + switch self._lineLimit { + case .none: + return nil + case .one: + return 1 + case .two: + return 2 + case .three: + return 3 + } + } +} + +#Preview { + FioriButtonCustomButtonExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInCollectionExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInCollectionExample.swift new file mode 100644 index 000000000..c45dc4763 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInCollectionExample.swift @@ -0,0 +1,12 @@ +import SwiftUI + +struct FioriButtonInCollectionExample: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + Label("hello", image: "") + } +} + +#Preview { + FioriButtonInCollectionExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInListExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInListExample.swift new file mode 100644 index 000000000..ea7c4df3c --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInListExample.swift @@ -0,0 +1,42 @@ +import FioriSwiftUICore +import SwiftUI + +struct FioriButtonInListExample: View { + var body: some View { + List { + Section { + ForEach(0 ..< 6, id: \.self) { index in + FioriButton { _ in + HStack(spacing: 8.0, content: { + if index % 2 == 1 { // align content right + Spacer() + } + Image(fioriName: "fiori.paper.plane").fontWeight(.bold).font(.fiori(forTextStyle: .body)) + Text("Button Label test \(index)") + if index % 5 == 2 { // align content left + Spacer() + } + }) + } + .disabled(index % 2 == 1) + .fioriButtonStyle(FioriPrimaryButtonStyle(.infinity)) + } + } header: { + Text("Button Title Alignment") + .textCase(.none) + .font(.subheadline) + .fontWeight(.semibold) + } + .alignmentGuide(.listRowSeparatorLeading, computeValue: { _ in + 0 + }) + } + .listStyle(.plain) + .navigationTitle("Button In List") + .navigationBarTitleDisplayMode(.large) + } +} + +#Preview { + FioriButtonInListExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInListMultipleLineExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInListMultipleLineExample.swift new file mode 100644 index 000000000..1d31b6972 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonInListMultipleLineExample.swift @@ -0,0 +1,50 @@ +import FioriSwiftUICore +import SwiftUI + +struct FioriButtonInListMultipleLineExample: View { + var body: some View { + List { + Section { + ForEach(0 ..< 6, id: \.self) { index in + FioriButton { _ in + HStack(spacing: 8.0, content: { + if index % 2 == 1 { // align content right + Spacer() + } + Image(fioriName: "fiori.paper.plane").fontWeight(.bold).font(.fiori(forTextStyle: .body)) + Text(self.buttonTitle(for: index)) + if index % 5 == 2 { // align content left + Spacer() + } + }) + } + .disabled(index % 2 == 1) + .fioriButtonStyle(FioriPrimaryButtonStyle(.infinity)) + } + } header: { + Text("Button Title Alignment") + .textCase(.none) + .font(.subheadline) + .fontWeight(.semibold) + } + .alignmentGuide(.listRowSeparatorLeading, computeValue: { _ in + 0 + }) + } + .listStyle(.plain) + .navigationTitle("Button In List - Multiple Lines") + .navigationBarTitleDisplayMode(.large) + } + + func buttonTitle(for index: Int) -> String { + var text = "Button Label test" + for _ in 0 ..< index { + text += "\n" + "Button Label test" + } + return text + } +} + +#Preview { + FioriButtonInListMultipleLineExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonStyleToggleExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonStyleToggleExample.swift new file mode 100644 index 000000000..1c534f678 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonStyleToggleExample.swift @@ -0,0 +1,92 @@ +import FioriSwiftUICore +import SwiftUI + +enum ToggleFioriButtonStyle: Int { + case primary + case secondary + case tertiary +} + +struct FioriButtonStyleToggleExample: View { + @State private var _buttonStyle = ToggleFioriButtonStyle.primary + @State private var _colorStyle = FioriButtonColorStyle.normal + @State private var _isSelectionPersistent = false + + init(_buttonStyle: ToggleFioriButtonStyle = ToggleFioriButtonStyle.primary, _colorStyle: FioriButtonColorStyle = FioriButtonColorStyle.normal, _isSelectionPersistent: Bool = false) { + self._buttonStyle = _buttonStyle + self._colorStyle = _colorStyle + self._isSelectionPersistent = _isSelectionPersistent + } + + var body: some View { + ScrollView { + VStack(alignment: .center, spacing: 20, content: { + FioriButton(isSelectionPersistent: self._isSelectionPersistent, label: { state in + HStack(spacing: 8.0, content: { + Image(fioriName: "fiori.paper.plane").fontWeight(.bold).font(.fiori(forTextStyle: .subheadline)) + Text(self.titleForState(state: state)) + }) + }) + .fioriButtonStyle(self.fioriButtonStyle().eraseToAnyFioriButtonStyle()) + + Picker("Button Style", selection: self.$_buttonStyle) { + Text("primary").tag(ToggleFioriButtonStyle.primary) + Text("secondary").tag(ToggleFioriButtonStyle.secondary) + Text("tertiary").tag(ToggleFioriButtonStyle.tertiary) + } + .pickerStyle(.segmented) + .controlSize(.large) + .frame(height: 51) + + Picker("Color Style", selection: self.$_colorStyle) { + Text("normal").tag(FioriButtonColorStyle.normal) + Text("tint").tag(FioriButtonColorStyle.tint) + Text("negative").tag(FioriButtonColorStyle.negative) + } + .pickerStyle(.segmented) // realized by UISegmentedControl + .controlSize(.large) + .frame(height: 51) + + HStack(alignment: .center, spacing: 20, content: { + Toggle("", isOn: self.$_isSelectionPersistent).labelsHidden() + Text("isSelectionPersistent") + Spacer() + }) + }) + .padding(EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)) + } + .navigationTitle("Button Style Toggle") + } + + func fioriButtonStyle() -> any FioriButtonStyle { + switch self._buttonStyle { + case .primary: + return FioriPrimaryButtonStyle() + case .secondary: + return FioriSecondaryButtonStyle(colorStyle: self._colorStyle) + case .tertiary: + return FioriTertiaryButtonStyle(colorStyle: self._colorStyle) + } + } + + func titleForState(state: UIControl.State) -> String { + var title = "" + switch state { + case .disabled: + title = "Disabled" + case .highlighted: + title = "Highlighted" + case [.selected, .highlighted]: + title = "Selected Highlighted" + case .selected: + title = "Selected" + default: + title = "Normal" + } + return title + } +} + +#Preview { + FioriButtonStyleToggleExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonTestsExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonTestsExample.swift new file mode 100644 index 000000000..59301855b --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/FioriButtonTestsExample.swift @@ -0,0 +1,111 @@ +import FioriSwiftUICore +import Foundation +import SwiftUI + +struct FioriButtonTestsExample: View { + @State private var _showSettings = false + @State private var _isEnabled = true + @State private var _isSelectionPersistent = false + @State private var _colorStyle = FioriButtonColorStyle.normal + + var types: [any FioriButtonStyle] { + [FioriPrimaryButtonStyle(), FioriSecondaryButtonStyle(colorStyle: self._colorStyle), FioriTertiaryButtonStyle(colorStyle: self._colorStyle)] + } + + let ButtonImage: some View = Image(fioriName: "fiori.action.settings.fill").fontWeight(.bold).font(.fiori(forTextStyle: .subheadline)) + + var imageTitlePadding = 10.0 + + var body: some View { + ScrollView { + VStack(alignment: .center, spacing: 10.0, content: { + ForEach(0 ..< self.types.count, id: \.self) { index in + FioriButton(isSelectionPersistent: self._isSelectionPersistent, label: { _ in + self.ButtonImage + }) + .fioriButtonStyle(self.types[index].eraseToAnyFioriButtonStyle()) + .disabled(!self._isEnabled) + + FioriButton(isSelectionPersistent: self._isSelectionPersistent, label: { _ in + Text("Button Long Long Long Long Title Long Long Long Long Title \(self.styleName(style: self.types[index]))") + .multilineTextAlignment(.center) + }) + .fioriButtonStyle(self.types[index].eraseToAnyFioriButtonStyle()) + .disabled(!self._isEnabled) + + FioriButton(isSelectionPersistent: self._isSelectionPersistent, label: { _ in + HStack(spacing: self.imageTitlePadding, content: { + self.ButtonImage + Text("Button \(self.styleName(style: self.types[index]))") + .font(.fiori(forTextStyle: .footnote, isItalic: false)) + }) + }) + .fioriButtonStyle(self.types[index].eraseToAnyFioriButtonStyle()) + .disabled(!self._isEnabled) + + FioriButton(isSelectionPersistent: self._isSelectionPersistent, label: { _ in + VStack(spacing: self.imageTitlePadding, content: { + self.ButtonImage + Text("Button \(self.styleName(style: self.types[index]))") + }) + }) + .fioriButtonStyle(self.types[index].eraseToAnyFioriButtonStyle()) + .disabled(!self._isEnabled) + + FioriButton(isSelectionPersistent: self._isSelectionPersistent, label: { _ in + HStack(spacing: self.imageTitlePadding, content: { + Text("Button \(self.styleName(style: self.types[index]))") + self.ButtonImage + }) + }) + .fioriButtonStyle(self.types[index].eraseToAnyFioriButtonStyle()) + .disabled(!self._isEnabled) + + FioriButton(isSelectionPersistent: self._isSelectionPersistent, label: { _ in + VStack(spacing: self.imageTitlePadding, content: { + Text("Button \(self.styleName(style: self.types[index]))") + self.ButtonImage + }) + }) + .fioriButtonStyle(self.types[index].eraseToAnyFioriButtonStyle()) + .disabled(!self._isEnabled) + } + }) + .frame(maxWidth: .infinity) + } + .listStyle(.grouped) + .navigationTitle("Button Tests") + .settingsSheet(isPresented: self.$_showSettings) { + Toggle(isOn: self.$_isEnabled) { + Text("isEnabled") + } + Toggle(isOn: self.$_isSelectionPersistent) { + Text("isSelectionPersistent") + } + Picker(selection: self.$_colorStyle) { + Text("normal").tag(FioriButtonColorStyle.normal) + Text("tint").tag(FioriButtonColorStyle.tint) + Text("negative").tag(FioriButtonColorStyle.negative) + } label: { + Text("color style") + } + } + } + + func styleName(style: any FioriButtonStyle) -> String { + switch style { + case is FioriPrimaryButtonStyle: + return "Primary" + case is FioriSecondaryButtonStyle: + return "Secondary" + case is FioriTertiaryButtonStyle: + return "Tertiary" + default: + return "Plain" + } + } +} + +#Preview { + FioriButtonTestsExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/InPlaceLoadingContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/InPlaceLoadingContentView.swift new file mode 100644 index 000000000..8202a09bd --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/InPlaceLoadingContentView.swift @@ -0,0 +1,20 @@ +import SwiftUI + +struct InPlaceLoadingContentView: View { + var body: some View { + List { + NavigationLink("Loading Button Single Status", destination: LazyView(LoadingButtonSingleStatusExample())) + NavigationLink("Multi Loading Button Status Change", destination: LazyView(MultiLoadingButtonStatusChangeExample())) + NavigationLink("Card - Full Width Single Button", destination: LazyView(CardFullWidthSingleButtonExample())) + NavigationLink("Card - With Two Buttons", destination: LazyView(CardFixedWidthButtonsExample())) + NavigationLink("Card - Two Buttons Change To One", destination: LazyView(CardTwoButtonsChangeToOneExample())) + NavigationLink("Card View - With Two Buttons", destination: LazyView(CardViewWithTwoButtonsExample())) + NavigationLink("Flexible Button", destination: LazyView(InPlaceLoadingFlexibleButtonExample())) + } + .navigationTitle("In-Place Loading Button") + } +} + +#Preview { + InPlaceLoadingContentView() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/InPlaceLoadingFlexibleButtonExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/InPlaceLoadingFlexibleButtonExample.swift new file mode 100644 index 000000000..6f6b6ac2c --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/InPlaceLoadingFlexibleButtonExample.swift @@ -0,0 +1,55 @@ +import FioriSwiftUICore +import SwiftUI + +struct InPlaceLoadingFlexibleButtonExample: View { + @State private var _loadingState: FioriButtonLoadingState = .unspecified + @Environment(\.dismiss) private var dismiss + + var body: some View { + VStack { + Spacer() + + FioriButton(title: self.titleStr(self._loadingState), action: { _ in + self.updateDataSource() + }) + .fioriButtonStyle(FioriPrimaryButtonStyle(nil, loadingState: self._loadingState)) + .disabled(self._loadingState != .unspecified) + + Spacer() + } + .navigationTitle("Flexible Button") + } + + func updateDataSource() { + var timeInterval = 0.0 + if self._loadingState == .unspecified { + self._loadingState = .processing + timeInterval = 2.0 + } else if self._loadingState == .processing { + self._loadingState = .success + timeInterval = 1.0 + } else { + self.dismiss() + return + } + + _ = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false, block: { _ in + self.updateDataSource() + }) + } + + func titleStr(_ loadingState: FioriButtonLoadingState) -> AttributedString { + switch loadingState { + case .unspecified: + "Next" + case .processing: + "Loading" + case .success: + "Connected" + } + } +} + +#Preview { + InPlaceLoadingFlexibleButtonExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/LoadingButtonSingleStatusExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/LoadingButtonSingleStatusExample.swift new file mode 100644 index 000000000..39eff4e6f --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/LoadingButtonSingleStatusExample.swift @@ -0,0 +1,113 @@ +import FioriSwiftUICore +import FioriThemeManager +import SwiftUI + +struct FioriButtonProcessingStyle: FioriButtonStyle { + @State var minWidth: CGFloat = 44.0 + @State var maxWidth: CGFloat? + @State var minHeight: CGFloat = 44 + let loadingState: FioriButtonLoadingState + + func makeBody(configuration: FioriButtonStyle.Configuration) -> some View { + let foregroundColor = Color.white + let backgroundColor = Color.preferredColor(.tintColorTapState) + + return self.containerView(self.loadingState, configuration: configuration) + .font(.fiori(forTextStyle: .body, weight: .semibold)) + .foregroundColor(foregroundColor) + .tint(foregroundColor) + .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) + .frame(minWidth: self.minWidth, maxWidth: self.maxWidth, minHeight: self.minHeight) + .background(RoundedRectangle(cornerRadius: 8).fill(backgroundColor)) + .contentShape(Rectangle()) + } + + @ViewBuilder + func containerView(_ loadingState: FioriButtonLoadingState, configuration: FioriButtonStyle.Configuration) -> some View { + let showImageView = showImageView(loadingState, image: configuration.image) + let label = configuration.label + switch configuration.imagePosition { + case .top: + VStack(spacing: configuration.imageTitleSpacing, content: { + showImageView + label + }) + case .leading: + HStack(spacing: configuration.imageTitleSpacing, content: { + showImageView + label + }) + case .bottom: + VStack(spacing: configuration.imageTitleSpacing, content: { + label + showImageView + }) + case .trailing: + HStack(spacing: configuration.imageTitleSpacing, content: { + label + showImageView + }) + } + } + + @ViewBuilder + private func showImageView(_ loadingState: FioriButtonLoadingState, image: FioriButtonStyleConfiguration.Image) -> some View { + switch loadingState { + case .unspecified: + image + case .processing: + ProgressView(value: 0).progressViewStyle(.circular) + case .success: + FioriIcon.status.sysEnter + } + } +} + +struct LoadingButtonSingleStatusExample: View { + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 40) { + HStack { + FioriButton() + .fioriButtonStyle(FioriButtonProcessingStyle(minHeight: 38, loadingState: .processing).eraseToAnyFioriButtonStyle()) + .disabled(true) + + Spacer() + } + + FioriButton(label: { _ in + Text("Loading...") + }) + .fioriButtonStyle(FioriButtonProcessingStyle(minWidth: 142, minHeight: 38, loadingState: .processing).eraseToAnyFioriButtonStyle()) + .disabled(true) + + FioriButton() + .fioriButtonStyle(FioriButtonProcessingStyle(minWidth: 142, minHeight: 38, loadingState: .processing).eraseToAnyFioriButtonStyle()) + .disabled(true) + + FioriButton(label: { _ in + Text("Loading...") + }) + .fioriButtonStyle(FioriButtonProcessingStyle(maxWidth: .infinity, loadingState: .processing).eraseToAnyFioriButtonStyle()) + .disabled(true) + + FioriButton(label: { _ in + Text("Loading...") + }, imagePosition: .trailing).fioriButtonStyle(FioriButtonProcessingStyle(maxWidth: .infinity, loadingState: .processing).eraseToAnyFioriButtonStyle()) + .disabled(true) + + FioriButton() + .fioriButtonStyle(FioriButtonProcessingStyle(maxWidth: .infinity, loadingState: .processing).eraseToAnyFioriButtonStyle()) + .disabled(true) + } + .frame(maxWidth: .infinity) + .padding(EdgeInsets(top: 30, leading: 30, bottom: 30, trailing: 30)) + } + .frame(maxWidth: .infinity, alignment: .leading) + .navigationTitle("Loading Button Single Status") + } +} + +#Preview { + LoadingButtonSingleStatusExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/MultiLoadingButtonStatusChangeExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/MultiLoadingButtonStatusChangeExample.swift new file mode 100644 index 000000000..0f5772f7b --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FioriButton/MultiLoadingButtonStatusChangeExample.swift @@ -0,0 +1,104 @@ +import FioriSwiftUICore +import FioriThemeManager +import SwiftUI + +private enum FioriButtonMultiLoadingButtonsStatus: Int { + case greyInitial + case greyTap + case greyLoading + case greySuccess + case tintInitial + case tintTap + case tintLoading + case tintSuccess + case negativeInitial + case negativeTap + case negativeLoading + case negativeSuccess +} + +struct MultiLoadingButtonStatusChangeExample: View { + @State private var _loadingState: FioriButtonLoadingState = .unspecified + private let sectionTitles = [ + "Primary Button (Tint):", + "Secondary Button (Grey):", + "Secondary Button (Tint):", + "Secondary Button (Negative):", + "Tertiary Button (Grey):", + "Tertiary Button (Tint):", + "Tertiary Button (Negative):" + ] + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 20, content: { + ForEach(0 ..< self.sectionTitles.count, id: \.self) { index in + HStack { + Text(self.sectionTitles[index]) + Spacer() + } + + FioriButton(isSelectionPersistent: false, action: nil, label: { _ in + Text(self.customTitle) + }, image: { _ in + index == 2 ? AnyView(EmptyView()) : AnyView(FioriIcon.actions.add.font(.fiori(forTextStyle: .subheadline))) + }) + .fioriButtonStyle(self.fioriButtonStyle(at: index).eraseToAnyFioriButtonStyle()) + .disabled(self._loadingState != .unspecified) + } + }) + .padding(30.0) + } + .navigationTitle("Multi Loading Buttons Status Change") + .onAppear { + self.changeLoadingStatus() + } + } + + func changeLoadingStatus() { + _ = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { _ in + switch self._loadingState { + case .unspecified: + self._loadingState = .processing + case .processing: + self._loadingState = .success + case .success: + self._loadingState = .unspecified + } + }) + } + + var customTitle: AttributedString { + switch self._loadingState { + case .unspecified: + "Label" + case .processing: + "Loading..." + case .success: + "Success" + } + } + + func fioriButtonStyle(at index: Int) -> any FioriButtonStyle { + switch index { + case 0: + return FioriPrimaryButtonStyle(142, loadingState: self._loadingState) + case 1: + return FioriSecondaryButtonStyle(colorStyle: .normal, maxWidth: 142, loadingState: self._loadingState) + case 2: + return FioriSecondaryButtonStyle(colorStyle: .tint, maxWidth: 142, loadingState: self._loadingState) + case 3: + return FioriSecondaryButtonStyle(colorStyle: .negative, maxWidth: 142, loadingState: self._loadingState) + case 4: + return FioriTertiaryButtonStyle(colorStyle: .normal, maxWidth: 142, loadingState: self._loadingState) + case 5: + return FioriTertiaryButtonStyle(colorStyle: .tint, maxWidth: 142, loadingState: self._loadingState) + default: + return FioriTertiaryButtonStyle(colorStyle: .negative, maxWidth: 142, loadingState: self._loadingState) + } + } +} + +#Preview { + MultiLoadingButtonStatusChangeExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift index ff36c93e1..7e9504259 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift @@ -1,4 +1,5 @@ import FioriSwiftUICore +import FioriThemeManager import SwiftUI struct StepProgressIndicatorExample: View { @@ -121,7 +122,10 @@ struct SPIExampleWithHeader: View { Button {} label: { HStack(spacing: 2) { Text("All Steps(\(self.steps.count)") - Image(systemName: "chevron.right") + .foregroundStyle(Color.preferredColor(.tintColor)) + FioriIcon.actions.slimArrowRight + .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) + .foregroundStyle(Color.preferredColor(.separator)) } } } @@ -210,8 +214,11 @@ struct SPIExampleWithoutName: View { stepItems: self.steps) {} action: { Button {} label: { HStack(spacing: 2) { - Text("All Steps(\(self.steps.count))") - Image(systemName: "chevron.right") + Text("All Steps(\(self.steps.count)") + .foregroundStyle(Color.preferredColor(.tintColor)) + FioriIcon.actions.slimArrowRight + .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) + .foregroundStyle(Color.preferredColor(.separator)) } } } @@ -250,7 +257,10 @@ struct SPIExampleByBuilder: View { Button {} label: { HStack(spacing: 2) { Text("All Steps(2)") - Image(systemName: "chevron.right") + .foregroundStyle(Color.preferredColor(.tintColor)) + FioriIcon.actions.slimArrowRight + .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) + .foregroundStyle(Color.preferredColor(.separator)) } } }, steps: { @@ -332,7 +342,10 @@ struct SPICustomStyleExample: View { Button {} label: { HStack(spacing: 2) { Text("All Steps(\(self.steps.count)") - Image(systemName: "chevron.right") + .foregroundStyle(Color.preferredColor(.tintColor)) + FioriIcon.actions.slimArrowRight + .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) + .foregroundStyle(Color.preferredColor(.separator)) } } } diff --git a/CHANGELOG.md b/CHANGELOG.md index c87311589..2e678e488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [4.1.2](https://github.com/SAP/cloud-sdk-ios-fiori/compare/4.1.1...4.1.2) (2024-08-12) + + +### Features + +* 🎸 [JIRA:HCPSDKFIORIUIKIT-2605] Dark theme illustrations ([#760](https://github.com/SAP/cloud-sdk-ios-fiori/issues/760)) ([14eba1f](https://github.com/SAP/cloud-sdk-ios-fiori/commit/14eba1fc8777d76556c23d6c42c1f567b78e45f5)) +* 🎸 [JIRA:HCPSDKFIORIUIKIT-2690] SwiftUI SegmentedControl ([#759](https://github.com/SAP/cloud-sdk-ios-fiori/issues/759)) ([2e08afa](https://github.com/SAP/cloud-sdk-ios-fiori/commit/2e08afa45dc0ac8baf63612717ce95c2fe84861c)) + + +### Bug Fixes + +* 🐛 [JIRA: IOSSDKBUG-292] Illustrated message bold title ([#758](https://github.com/SAP/cloud-sdk-ios-fiori/issues/758)) ([30677bf](https://github.com/SAP/cloud-sdk-ios-fiori/commit/30677bf6312c038c9035f9f6ad9b07527584f9a2)) +* 🐛 [JIRA:0] Update color based on UX review feedback ([#755](https://github.com/SAP/cloud-sdk-ios-fiori/issues/755)) ([06a58d7](https://github.com/SAP/cloud-sdk-ios-fiori/commit/06a58d74df1d08b049e8ef8f8dc2cd7cac35c96f)) +* 🐛 [JIRA:HCPSDKFIORIUIKIT-2542] Revert Card semibold change ([#764](https://github.com/SAP/cloud-sdk-ios-fiori/issues/764)) ([f247f57](https://github.com/SAP/cloud-sdk-ios-fiori/commit/f247f57bbcefd50956400c069d5ed145541bb259)) +* 🐛 [JIRA:IOSSDKBUG-264] No left padding for the 1st column in DataTable ([#762](https://github.com/SAP/cloud-sdk-ios-fiori/issues/762)) ([6d67ddd](https://github.com/SAP/cloud-sdk-ios-fiori/commit/6d67ddd8f9eb6ab9c1cd1f10b29ffe1059675e31)) +* 🐛 tab bar styles ([#761](https://github.com/SAP/cloud-sdk-ios-fiori/issues/761)) ([2164f8a](https://github.com/SAP/cloud-sdk-ios-fiori/commit/2164f8a8a4adf0374f70c5181b9c69d08fd0e4b6)) +* IOSSDKBUG-282 - JMC welcome screen legal disclaimer bg color ([#757](https://github.com/SAP/cloud-sdk-ios-fiori/issues/757)) ([5e24756](https://github.com/SAP/cloud-sdk-ios-fiori/commit/5e247561008abb68fc0efb82d207093de12b07a0)) +* set the overflow button's accessibilityLabel to "More" in card's footer ([#765](https://github.com/SAP/cloud-sdk-ios-fiori/issues/765)) ([a40f14b](https://github.com/SAP/cloud-sdk-ios-fiori/commit/a40f14b212ecf4f641fc2c70a14240ca1c85ce7b)) + ### [4.1.1](https://github.com/SAP/cloud-sdk-ios-fiori/compare/4.1.0...4.1.1) (2024-07-31) diff --git a/Sources/FioriCharts/Resources/sr-Latn-ME.lproj/Localizable.strings b/Sources/FioriCharts/Resources/sr-Latn-ME.lproj/Localizable.strings new file mode 100644 index 000000000..05edd0e2e --- /dev/null +++ b/Sources/FioriCharts/Resources/sr-Latn-ME.lproj/Localizable.strings @@ -0,0 +1,3 @@ +/* XMSG: Placeholder text in chart when there is no data */ +"No Data" = "Nema podataka"; + diff --git a/Sources/FioriSwiftUICore/FioriButton/FioriButton.swift b/Sources/FioriSwiftUICore/FioriButton/FioriButton.swift index abb8f0ee5..009b25cd4 100644 --- a/Sources/FioriSwiftUICore/FioriButton/FioriButton.swift +++ b/Sources/FioriSwiftUICore/FioriButton/FioriButton.swift @@ -59,6 +59,9 @@ public struct FioriButton: View { let action: ((UIControl.State) -> Void)? let label: (UIControl.State) -> any View let isSelectionPersistent: Bool + let image: (UIControl.State) -> any View + let imagePosition: FioriButtonImagePosition + let imageTitleSpacing: CGFloat private let touchAreaInset: CGFloat = 50 @Environment(\.isEnabled) private var isEnabled @@ -82,9 +85,32 @@ public struct FioriButton: View { action: ((UIControl.State) -> Void)? = nil, @ViewBuilder label: @escaping (UIControl.State) -> any View) { + self.init(isSelectionPersistent: isSelectionPersistent, action: action, label: label, image: { _ in + EmptyView() + }, imagePosition: .leading, imageTitleSpacing: 8.0) + } + + /// Create a fiori button. + /// - Parameters: + /// - isSelectionPersistent: A boolean value determines whether the selection should be persistent or not. + /// - action: Action triggered when tap on button. + /// - label: A closure that returns a label for each state. For a button with non-persistent selection, `.normal`, `.disabled`, `.highlighted` are supported. For a button with persistent selection, use `.selected` instead of `.highlighted`. + /// - image: Image of the button. + /// - imagePosition: Place the image along the top, leading, bottom, or trailing edge of the button. + /// - imageTitleSpacing: Spacing between image and title. + public init(isSelectionPersistent: Bool = false, + action: ((UIControl.State) -> Void)? = nil, + @ViewBuilder label: @escaping (UIControl.State) -> any View = { _ in EmptyView() }, + @ViewBuilder image: @escaping (UIControl.State) -> any View = { _ in EmptyView() }, + imagePosition: FioriButtonImagePosition = .leading, + imageTitleSpacing: CGFloat = 8.0) + { + self.isSelectionPersistent = isSelectionPersistent self.action = action self.label = label - self.isSelectionPersistent = isSelectionPersistent + self.image = image + self.imagePosition = imagePosition + self.imageTitleSpacing = imageTitleSpacing } /// Create a fiori button. @@ -96,10 +122,12 @@ public struct FioriButton: View { title: @escaping (UIControl.State) -> AttributedString, action: ((UIControl.State) -> Void)? = nil) { - self.init(isSelectionPersistent: isSelectionPersistent, action: action, label: { + self.init(isSelectionPersistent: isSelectionPersistent, action: action) { let text = title($0) - return Text(text) - }) + Text(text) + } image: { _ in + EmptyView() + } } /// Create a fiori button. @@ -111,15 +139,22 @@ public struct FioriButton: View { title: AttributedString, action: ((UIControl.State) -> Void)? = nil) { - self.init(isSelectionPersistent: isSelectionPersistent, action: action, label: { _ in Text(title) }) + self.init(isSelectionPersistent: isSelectionPersistent, action: action) { _ in + Text(title) + } image: { _ in + EmptyView() + } } /// The content of the button. public var body: some View { - let config = FioriButtonStyleConfiguration(state: state) { state in + let config = FioriButtonStyleConfiguration(state: state, _label: { state in let v = self.label(state) return FioriButtonStyleConfiguration.Label(v) - } + }, _image: { state in + let v = self.image(state) + return FioriButtonStyleConfiguration.Image(v) + }, imagePosition: self.imagePosition, imageTitleSpacing: self.imageTitleSpacing) return Group { if self.isSelectionPersistent { @@ -133,7 +168,7 @@ public struct FioriButton: View { } label: { EmptyView() } - .buttonStyle(_ButtonStyleImpl(fioriButtonStyle: self.fioriButtonStyle, label: self.label, isEnabled: self.isEnabled)) + .buttonStyle(_ButtonStyleImpl(fioriButtonStyle: self.fioriButtonStyle, label: self.label, image: self.image, imagePosition: self.imagePosition, imageTitleSpacing: self.imageTitleSpacing, isEnabled: self.isEnabled)) } } } @@ -183,21 +218,33 @@ public extension FioriButton { self.label = { Text(title($0)) } + self.image = { _ in + EmptyView() + } + self.imagePosition = .leading + self.imageTitleSpacing = 8.0 } } private struct _ButtonStyleImpl: ButtonStyle { let fioriButtonStyle: AnyFioriButtonStyle let label: (UIControl.State) -> any View + let image: (UIControl.State) -> any View + let imagePosition: FioriButtonImagePosition + let imageTitleSpacing: CGFloat let isEnabled: Bool func makeBody(configuration: Configuration) -> some View { let state: UIControl.State = self.isEnabled ? (configuration.isPressed ? .highlighted : .normal) : .disabled - let config = FioriButtonStyleConfiguration(state: state) { state in + + let config = FioriButtonStyleConfiguration(state: state, _label: { state in let v = self.label(state) return FioriButtonStyleConfiguration.Label(v) - } - + }, _image: { state in + let v = self.image(state) + return FioriButtonStyleConfiguration.Image(v) + }, imagePosition: self.imagePosition, imageTitleSpacing: self.imageTitleSpacing) + return ZStack { self.fioriButtonStyle.makeBody(configuration: config) @@ -205,3 +252,15 @@ private struct _ButtonStyleImpl: ButtonStyle { } } } + +/// Place the image along the top, leading, bottom, or trailing edge of the button. +public enum FioriButtonImagePosition { + /// place the image along the top edge of the button. + case top + /// place the image along the leading edge of the button. + case leading + /// place the image along the bottom edge of the button. + case bottom + /// place the image along the trailing edge of the button. + case trailing +} diff --git a/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyle.swift b/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyle.swift index c6bf7770b..cb9403640 100644 --- a/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyle.swift +++ b/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyle.swift @@ -1,3 +1,4 @@ +import FioriThemeManager import Foundation import SwiftUI @@ -42,15 +43,39 @@ public struct FioriButtonStyleConfiguration { } } + /// A type-erased Image of a button. + public struct Image: View { + let view: AnyView + init(_ view: some View) { + self.view = AnyView(view) + } + + /// The content of the Image. + public var body: some View { + self.view + } + } + /// The current state of the button. public let state: UIControl.State let _label: (UIControl.State) -> Label + let _image: (UIControl.State) -> Image + + /// Place the image along the top, leading, bottom, or trailing edge of the button. + public let imagePosition: FioriButtonImagePosition + /// Spacing between image and title. + public let imageTitleSpacing: CGFloat /// The label for the current state. public var label: Label { label(for: self.state) } + + /// The Image for the current state. + public var image: Image { + image(for: self.state) + } /// Returns the label for the specific state. /// - Parameter state: A valid state for a button. For a Fiori button with non-persistent selection, `.normal`, `.disabled`, and `.highlighted` are supported. For a button with persistent selection, use `.selected` instead of `.highlighted`. @@ -58,6 +83,52 @@ public struct FioriButtonStyleConfiguration { public func label(for state: UIControl.State) -> Label { self._label(state) } + + /// Returns the Image for the specific state. + /// - Parameter state: A valid state for a button. For a Fiori button with non-persistent selection, `.normal`, `.disabled`, and `.highlighted` are supported. For a button with persistent selection, use `.selected` instead of `.highlighted`. + /// - Returns: The Image for the specific state. + public func image(for state: UIControl.State) -> Image { + self._image(state) + } + + @ViewBuilder + func containerView(_ loadingState: FioriButtonLoadingState) -> some View { + let showImageView = showImageView(loadingState, image: image) + switch self.imagePosition { + case .top: + VStack(spacing: self.imageTitleSpacing, content: { + showImageView + self.label + }) + case .leading: + HStack(spacing: self.imageTitleSpacing, content: { + showImageView + self.label + }) + case .bottom: + VStack(spacing: self.imageTitleSpacing, content: { + self.label + showImageView + }) + case .trailing: + HStack(spacing: self.imageTitleSpacing, content: { + self.label + showImageView + }) + } + } + + @ViewBuilder + private func showImageView(_ loadingState: FioriButtonLoadingState, image: Image) -> some View { + switch loadingState { + case .unspecified: + image + case .processing: + ProgressView(value: 0).progressViewStyle(.circular) + case .success: + FioriIcon.status.sysEnter.font(.fiori(forTextStyle: .subheadline)) + } + } } /// A Fiori button style for the plain button. @@ -76,14 +147,18 @@ public struct FioriPlainButtonStyle: FioriButtonStyle { /// A Fiori button style for the primary button. public struct FioriPrimaryButtonStyle: FioriButtonStyle { private let maxWidth: CGFloat? + private let loadingState: FioriButtonLoadingState /// Create a `FioriPrimaryButtonStyle` instance. - public init(_ maxWidth: CGFloat? = nil) { self.maxWidth = maxWidth } + public init(_ maxWidth: CGFloat? = nil, loadingState: FioriButtonLoadingState = .unspecified) { + self.maxWidth = maxWidth + self.loadingState = loadingState + } public func makeBody(configuration: Configuration) -> some View { - let config = FioriButtonStyleProvider.getPrimaryButtonStyle(state: configuration.state).withMaxWidth(self.maxWidth) + let config = FioriButtonStyleProvider.getPrimaryButtonStyle(state: configuration.state, loadingState: self.loadingState).withMaxWidth(self.maxWidth) - return configuration.label + return configuration.containerView(self.loadingState) .fioriButtonConfiguration(config) } } @@ -92,18 +167,20 @@ public struct FioriPrimaryButtonStyle: FioriButtonStyle { public struct FioriSecondaryButtonStyle: FioriButtonStyle { private let colorStyle: FioriButtonColorStyle private let maxWidth: CGFloat? + private let loadingState: FioriButtonLoadingState /// Create a `FioriSecondaryButtonStyle` instance. /// - Parameter colorStyle: The color style used for this button style. - public init(colorStyle: FioriButtonColorStyle = .tint, maxWidth: CGFloat? = nil) { + public init(colorStyle: FioriButtonColorStyle = .tint, maxWidth: CGFloat? = nil, loadingState: FioriButtonLoadingState = .unspecified) { self.colorStyle = colorStyle self.maxWidth = maxWidth + self.loadingState = loadingState } public func makeBody(configuration: Configuration) -> some View { - let config = FioriButtonStyleProvider.getSecondaryButtonStyle(colorStyle: self.colorStyle, for: configuration.state).withMaxWidth(self.maxWidth) + let config = FioriButtonStyleProvider.getSecondaryButtonStyle(colorStyle: self.colorStyle, for: configuration.state, loadingState: self.loadingState).withMaxWidth(self.maxWidth) - return configuration.label + return configuration.containerView(self.loadingState) .fioriButtonConfiguration(config) } } @@ -112,18 +189,20 @@ public struct FioriSecondaryButtonStyle: FioriButtonStyle { public struct FioriTertiaryButtonStyle: FioriButtonStyle { private let colorStyle: FioriButtonColorStyle private let maxWidth: CGFloat? + private let loadingState: FioriButtonLoadingState /// Create a `FioriTertiaryButtonStyle` instance. /// - Parameter colorStyle: The color style used for this button style. - public init(colorStyle: FioriButtonColorStyle = .tint, maxWidth: CGFloat? = nil) { + public init(colorStyle: FioriButtonColorStyle = .tint, maxWidth: CGFloat? = nil, loadingState: FioriButtonLoadingState = .unspecified) { self.colorStyle = colorStyle self.maxWidth = maxWidth + self.loadingState = loadingState } public func makeBody(configuration: Configuration) -> some View { - let config = FioriButtonStyleProvider.getTertiaryButtonStyle(colorStyle: self.colorStyle, for: configuration.state).withMaxWidth(self.maxWidth) + let config = FioriButtonStyleProvider.getTertiaryButtonStyle(colorStyle: self.colorStyle, for: configuration.state, loadingState: self.loadingState).withMaxWidth(self.maxWidth) - return configuration.label + return configuration.containerView(self.loadingState) .fioriButtonConfiguration(config) } } diff --git a/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyleProvider.swift b/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyleProvider.swift index 5aa0350a8..625add4ac 100644 --- a/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyleProvider.swift +++ b/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyleProvider.swift @@ -7,118 +7,161 @@ enum FioriButtonStyleProvider { let foregroundColor: Color switch state { case .normal: - foregroundColor = Color.preferredColor(.tintColor) + foregroundColor = .preferredColor(.tintColor) case .highlighted, .selected: - foregroundColor = Color.preferredColor(.tintColorTapState) + foregroundColor = .preferredColor(.tintColorTapState) default: - foregroundColor = Color.preferredColor(.separator) + foregroundColor = .preferredColor(.separator) } return FioriButtonConfiguration(foregroundColor: foregroundColor, backgroundColor: backgroundColor, font: .fiori(forTextStyle: .callout), padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } - static func getPrimaryButtonStyle(state: UIControl.State) -> FioriButtonConfiguration { + static func getPrimaryButtonStyle(state: UIControl.State, loadingState: FioriButtonLoadingState = .unspecified) -> FioriButtonConfiguration { let backgroundColor: Color let foregroundColor: Color - switch state { - case .normal: - foregroundColor = Color.preferredColor(.base2) - backgroundColor = Color.preferredColor(.tintColor) - case .highlighted, .selected: - foregroundColor = Color.preferredColor(.base2) - backgroundColor = Color.preferredColor(.tintColorTapState) + + switch loadingState { + case .processing, .success: + foregroundColor = .preferredColor(.baseWhite) + backgroundColor = .preferredColor(.tintColorTapState) default: - foregroundColor = Color.preferredColor(.separator) - backgroundColor = Color.preferredColor(.tertiaryFill) + switch state { + case .normal: + foregroundColor = .preferredColor(.base2) + backgroundColor = .preferredColor(.tintColor) + case .highlighted, .selected: + foregroundColor = .preferredColor(.base2) + backgroundColor = .preferredColor(.tintColorTapState) + default: + foregroundColor = .preferredColor(.separator) + backgroundColor = .preferredColor(.tertiaryFill) + } } return FioriButtonConfiguration(foregroundColor: foregroundColor, backgroundColor: backgroundColor) } - static func getSecondaryButtonStyle(colorStyle: FioriButtonColorStyle, for state: UIControl.State) -> FioriButtonConfiguration { + static func getSecondaryButtonStyle(colorStyle: FioriButtonColorStyle, for state: UIControl.State, loadingState: FioriButtonLoadingState = .unspecified) -> FioriButtonConfiguration { let backgroundColor: Color let foregroundColor: Color switch colorStyle { case .tint: - switch state { - case .normal: - foregroundColor = Color.preferredColor(.tintColor2) - backgroundColor = Color.preferredColor(.informationBackground) - case .highlighted, .selected: - foregroundColor = Color.preferredColor(.tintColorTapState) - backgroundColor = Color.preferredColor(.informationBackground) + switch loadingState { + case .processing, .success: + foregroundColor = .preferredColor(.tintColorTapState) + backgroundColor = .preferredColor(.secondaryFill) default: - foregroundColor = Color.preferredColor(.separator) - backgroundColor = Color.preferredColor(.tertiaryFill) + switch state { + case .normal: + foregroundColor = .preferredColor(.tintColor2) + backgroundColor = .preferredColor(.informationBackground) + case .highlighted, .selected: + foregroundColor = .preferredColor(.tintColorTapState) + backgroundColor = .preferredColor(.informationBackground) + default: + foregroundColor = .preferredColor(.separator) + backgroundColor = .preferredColor(.tertiaryFill) + } } case .normal: - switch state { - case .normal: - foregroundColor = Color.preferredColor(.secondaryLabel) - backgroundColor = Color.preferredColor(.secondaryFill) - case .highlighted, .selected: - foregroundColor = Color.preferredColor(.secondaryLabel) - backgroundColor = Color.preferredColor(.secondaryFill) + switch loadingState { + case .processing, .success: + foregroundColor = .preferredColor(.secondaryLabel) + backgroundColor = .preferredColor(.secondaryFill) default: - foregroundColor = Color.preferredColor(.separator) - backgroundColor = Color.preferredColor(.tertiaryFill) + switch state { + case .normal: + foregroundColor = .preferredColor(.secondaryLabel) + backgroundColor = .preferredColor(.secondaryFill) + case .highlighted, .selected: + foregroundColor = .preferredColor(.secondaryLabel) + backgroundColor = .preferredColor(.secondaryFill) + default: + foregroundColor = .preferredColor(.separator) + backgroundColor = .preferredColor(.tertiaryFill) + } } case .negative: - switch state { - case .normal: - foregroundColor = Color.preferredColor(.negativeLabel) - backgroundColor = Color.preferredColor(.negativeBackground) - case .highlighted, .selected: - foregroundColor = Color.preferredColor(.negativeLabelTapState) - backgroundColor = Color.preferredColor(.negativeBackgroundTapState) + switch loadingState { + case .processing, .success: + foregroundColor = .preferredColor(.negativeLabelTapState) + backgroundColor = .preferredColor(.secondaryFill) default: - foregroundColor = Color.preferredColor(.separator) - backgroundColor = Color.preferredColor(.tertiaryFill) + switch state { + case .normal: + foregroundColor = .preferredColor(.negativeLabel) + backgroundColor = .preferredColor(.negativeBackground) + case .highlighted, .selected: + foregroundColor = .preferredColor(.negativeLabelTapState) + backgroundColor = .preferredColor(.negativeBackgroundTapState) + default: + foregroundColor = .preferredColor(.separator) + backgroundColor = .preferredColor(.tertiaryFill) + } } } return FioriButtonConfiguration(foregroundColor: foregroundColor, backgroundColor: backgroundColor) } - static func getTertiaryButtonStyle(colorStyle: FioriButtonColorStyle, for state: UIControl.State) -> FioriButtonConfiguration { + static func getTertiaryButtonStyle(colorStyle: FioriButtonColorStyle, for state: UIControl.State, loadingState: FioriButtonLoadingState = .unspecified) -> FioriButtonConfiguration { let backgroundColor: Color let foregroundColor: Color switch colorStyle { case .tint: - switch state { - case .normal: - foregroundColor = Color.preferredColor(.tintColor2) - backgroundColor = .clear - case .highlighted, .selected: - foregroundColor = Color.preferredColor(.tintColorTapState) - backgroundColor = Color.preferredColor(.secondaryFill) + switch loadingState { + case .processing, .success: + foregroundColor = .preferredColor(.tintColorTapState) + backgroundColor = .preferredColor(.secondaryFill) default: - foregroundColor = Color.preferredColor(.separator) - backgroundColor = .clear + switch state { + case .normal: + foregroundColor = .preferredColor(.tintColor2) + backgroundColor = .clear + case .highlighted, .selected: + foregroundColor = .preferredColor(.tintColorTapState) + backgroundColor = .preferredColor(.secondaryFill) + default: + foregroundColor = .preferredColor(.separator) + backgroundColor = .clear + } } case .normal: - switch state { - case .normal: - foregroundColor = Color.preferredColor(.primaryLabel) - backgroundColor = .clear - case .highlighted, .selected: - foregroundColor = Color.preferredColor(.secondaryLabel) - backgroundColor = Color.preferredColor(.secondaryFill) + switch loadingState { + case .processing, .success: + foregroundColor = .preferredColor(.primaryLabel) + backgroundColor = .preferredColor(.secondaryFill) default: - foregroundColor = Color.preferredColor(.separator) - backgroundColor = .clear + switch state { + case .normal: + foregroundColor = .preferredColor(.primaryLabel) + backgroundColor = .clear + case .highlighted, .selected: + foregroundColor = .preferredColor(.secondaryLabel) + backgroundColor = .preferredColor(.secondaryFill) + default: + foregroundColor = .preferredColor(.separator) + backgroundColor = .clear + } } case .negative: - switch state { - case .normal: - foregroundColor = Color.preferredColor(.negativeLabel) - backgroundColor = .clear - case .highlighted, .selected: - foregroundColor = Color.preferredColor(.negativeLabel) - backgroundColor = Color.preferredColor(.secondaryFill) + switch loadingState { + case .processing, .success: + foregroundColor = .preferredColor(.negativeLabelTapState) + backgroundColor = .preferredColor(.secondaryFill) default: - foregroundColor = Color.preferredColor(.separator) - backgroundColor = Color.preferredColor(.tertiaryFill) + switch state { + case .normal: + foregroundColor = .preferredColor(.negativeLabel) + backgroundColor = .clear + case .highlighted, .selected: + foregroundColor = .preferredColor(.negativeLabel) + backgroundColor = .preferredColor(.secondaryFill) + default: + foregroundColor = .preferredColor(.separator) + backgroundColor = .preferredColor(.tertiaryFill) + } } } @@ -132,17 +175,19 @@ struct FioriButtonConfiguration { let font: Font let padding: EdgeInsets let maxWidth: CGFloat? + let loadingState: FioriButtonLoadingState - init(foregroundColor: Color, backgroundColor: Color, font: Font = .fiori(forTextStyle: .body, weight: .semibold), padding: EdgeInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16), maxWidth: CGFloat? = nil) { + init(foregroundColor: Color, backgroundColor: Color, font: Font = .fiori(forTextStyle: .body, weight: .semibold), padding: EdgeInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16), maxWidth: CGFloat? = nil, loadingState: FioriButtonLoadingState = .unspecified) { self.foregroundColor = foregroundColor self.backgroundColor = backgroundColor self.font = font self.padding = padding self.maxWidth = maxWidth + self.loadingState = loadingState } func withMaxWidth(_ maxWidth: CGFloat?) -> FioriButtonConfiguration { - FioriButtonConfiguration(foregroundColor: self.foregroundColor, backgroundColor: self.backgroundColor, font: self.font, padding: self.padding, maxWidth: maxWidth) + FioriButtonConfiguration(foregroundColor: self.foregroundColor, backgroundColor: self.backgroundColor, font: self.font, padding: self.padding, maxWidth: maxWidth, loadingState: self.loadingState) } } @@ -151,9 +196,20 @@ extension View { self .font(config.font) .foregroundColor(config.foregroundColor) + .tint(config.foregroundColor) .padding(config.padding) .frame(minWidth: 44, maxWidth: config.maxWidth, minHeight: 44) .background(RoundedRectangle(cornerRadius: 8).fill(config.backgroundColor)) .contentShape(Rectangle()) } } + +/// loading state of `FioriButton` +public enum FioriButtonLoadingState { + /// FioriButton loading state is not specified by developer + case unspecified + /// FioriButton with activity indicator + case processing + /// FioriButton with success icon + case success +} diff --git a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepProgressIndicator+View.swift b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepProgressIndicator+View.swift index 2dcfcd036..2f189848c 100644 --- a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepProgressIndicator+View.swift +++ b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepProgressIndicator+View.swift @@ -79,6 +79,7 @@ extension StepProgressIndicator: View { } else { HStack(alignment: .center) { title + .font(.fiori(forTextStyle: .headline, weight: .semibold)) Spacer() action .onSimultaneousTapGesture(perform: { diff --git a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepsStyles.swift b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepsStyles.swift index c31650384..62bd439fd 100644 --- a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepsStyles.swift +++ b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepsStyles.swift @@ -238,7 +238,7 @@ struct DefaultStepStyle: StepStyle { case (.normal, true): return Color.preferredColor(isPressed ? .tintColorTapState : .tintColor) case (.completed, _): - return Color.preferredColor(.primaryFill) + return Color.preferredColor(.quinaryLabel) case (.error, _): return Color.preferredColor(isPressed ? .negativeLabelTapState : .negativeLabel) default: diff --git a/Sources/FioriSwiftUICore/Views/Toolbar/FioriToolbar.swift b/Sources/FioriSwiftUICore/Views/Toolbar/FioriToolbar.swift index 3b407f100..2f1e4a547 100644 --- a/Sources/FioriSwiftUICore/Views/Toolbar/FioriToolbar.swift +++ b/Sources/FioriSwiftUICore/Views/Toolbar/FioriToolbar.swift @@ -7,7 +7,7 @@ struct FioriToolbar: ViewModifier { let items: Items @Environment(\.horizontalSizeClass) var horizontalSizeClass - @ObservedObject var sizeHandler = FioriToolbarHandler() + @StateObject var sizeHandler = FioriToolbarHandler() @Environment(\.helperTextStyle) var helperTextStyle @Environment(\.moreActionOverflowStyle) var moreActionOverflowStyle diff --git a/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift index 694a86b93..c6f8a3f6f 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift @@ -71,10 +71,11 @@ extension MenuSelectionItemFioriStyle { // Add default style for Title .foregroundStyle(Color.preferredColor(.primaryLabel)) .font(.fiori(forTextStyle: .headline)) + .lineLimit(8) } } } #Preview(body: { - MenuSelectionItem(icon: FioriIcon.documents.attachmentTextFile, title: "1099 Form") + MenuSelectionItem(icon: FioriIcon.documents.attachmentTextFile, title: "This is a very long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long text") }) diff --git a/Sources/FioriSwiftUICore/_localization/de-CH.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/de-CH.lproj/FioriSwiftUICore.strings index 1248c3877..d8bab929e 100644 --- a/Sources/FioriSwiftUICore/_localization/de-CH.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/de-CH.lproj/FioriSwiftUICore.strings @@ -31,6 +31,9 @@ /* XACT: The accessibility label for the signature line */ "Signature Line" = "Signaturzeile"; +/* XACT: The accessibility label for the mandatory field */ +"Required Field" = "Erforderliches Feld"; + /* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ "Canceled" = "Abgebrochen"; @@ -160,3 +163,24 @@ /* Default error text for when the character count exceeds the maximum limit for NoteFormView and TextFieldFormView */ "Reduce the number of characters" = "Reduzieren Sie die Anzahl der Zeichen"; +/* XBUT: voice over label for decrement action in stepper */ +"Stepper decrease" = "Reglerverringerung"; + +/* XBUT: voice over description for decrement action in stepper */ +"Decrease the value by %d" = "Wert um %d verringern"; + +/* XBUT: voice over label for increment action in stepper */ +"Stepper increase" = "Reglererhöhung"; + +/* XBUT: voice over description for increment action in stepper */ +"Increase the value by %d" = "Wert um %d erhöhen"; + +/* XBUT: voice over label for test field in stepper */ +"Select specific value" = "Genauen Wert auswählen"; + +/* XBUT: View all action in MenuSelection */ +"View All (%d)" = "Alle anzeigen (%d)"; + +/* XACT: RatingControl accessibility label */ +"%d out of %d stars" = "%d von %d Sternen"; + diff --git a/Sources/FioriSwiftUICore/_localization/it-CH.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/it-CH.lproj/FioriSwiftUICore.strings index 1c089ecb8..083c69b45 100644 --- a/Sources/FioriSwiftUICore/_localization/it-CH.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/it-CH.lproj/FioriSwiftUICore.strings @@ -31,6 +31,9 @@ /* XACT: The accessibility label for the signature line */ "Signature Line" = "Riga firma"; +/* XACT: The accessibility label for the mandatory field */ +"Required Field" = "Campo obbligatorio"; + /* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ "Canceled" = "Annullato"; @@ -160,3 +163,24 @@ /* Default error text for when the character count exceeds the maximum limit for NoteFormView and TextFieldFormView */ "Reduce the number of characters" = "Riduci il numero di caratteri"; +/* XBUT: voice over label for decrement action in stepper */ +"Stepper decrease" = "Riduzione barra di scorrimento per selezione valori"; + +/* XBUT: voice over description for decrement action in stepper */ +"Decrease the value by %d" = "Riduci il valore di %d"; + +/* XBUT: voice over label for increment action in stepper */ +"Stepper increase" = "Aumento barra di scorrimento per selezione valori"; + +/* XBUT: voice over description for increment action in stepper */ +"Increase the value by %d" = "Aumenta il valore di %d"; + +/* XBUT: voice over label for test field in stepper */ +"Select specific value" = "Seleziona valore specifico"; + +/* XBUT: View all action in MenuSelection */ +"View All (%d)" = "Visualizza tutto (%d)"; + +/* XACT: RatingControl accessibility label */ +"%d out of %d stars" = "%d/%d stelle"; + diff --git a/Sources/FioriSwiftUICore/_localization/sr-Latn-ME.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/sr-Latn-ME.lproj/FioriSwiftUICore.strings new file mode 100644 index 000000000..754ad3925 --- /dev/null +++ b/Sources/FioriSwiftUICore/_localization/sr-Latn-ME.lproj/FioriSwiftUICore.strings @@ -0,0 +1,186 @@ +/* XMSG: Placeholder text in chart when there is no data */ +"No Data" = "Nema podataka"; + +/* XBUT: Save action in inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Save" = "Sačuvaj"; + +/* XBUT: Re-enter action available in saved state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Re-enter Signature" = "Ponovo unesite potpis"; + +/* XBUT: Clear action of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Clear" = "Izbriši"; + +/* XBUT: Cancel action of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Cancel" = "Odustani"; + +/* XTIT: Title of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Signature" = "Potpis"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Tap to Sign" = "Dodirnite da biste potpisali"; + +/* XACT: The accessibility label for the signature area */ +"Signature Area" = "Oblast potpisa"; + +/* XACT: The accessibility hint for the signature area */ +"Double tap and drag to sign" = "Dodirnite dvaput i prevucite da potpišete"; + +/* XACT: The accessibility label for the signature image */ +"Signature Image" = "Slika potpisa"; + +/* XACT: The accessibility label for the signature line */ +"Signature Line" = "Red potpisa"; + +/* XACT: The accessibility label for the mandatory field */ +"Required Field" = "Polje obaveznog unosa"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Canceled" = "Otkazano"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Next" = "Sljedeće"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Ok" = "Ok"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Status" = "Status"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"User canceled Onboarding" = "Korisnik otkazao integraciju"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Without consent you will not be able to continue onboarding." = "Bez saglasnosti nećete moći da nastavite integraciju."; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Give Consent" = "Dajte saglasnost"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Quit" = "Izađi"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Back" = "Nazad"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Step" = "Korak"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"of" = "od"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Are you sure you want to quit the onboarding process?" = "Da li sigurno želite da napustite proces integracije?"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Want Data Privacy?" = "Da li želite privatnost podataka?"; + +/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ +"Be wise about your data" = "Budite oprezni sa vašim podacima"; + +/* XBUT: Not Now action title in UserConsentForm */ +"Not Now" = "Ne sada"; + +/* XBUT: Allow action title in UserConsentForm */ +"Allow" = "Dozvoli"; + +/* XBUT: Deny action title in UserConsentForm */ +"Deny" = "Odbij"; + +/* XBUT: Agree action title in EULAView */ +"Agree" = "Saglasan sam"; + +/* XBUT: Disagree action title in EULAView */ +"Disagree" = "Nijesam saglasan"; + +/* XMSG: Placeholder text in DataTable for loading state */ +"Loading..." = "Učitavanje..."; + +/* XMSG: Placeholder text in DataTable when there is no data */ +"There is nothing to display yet" = "Nema još ništa za prikaz"; + +/* XBUT: Default duration foramtter in DataTable */ +"%d Hrs %d Min" = "%d sata/i %d min."; + +/* XBUT: Done action in keyboard toolbar for a Textfield in DataTable */ +"Done" = "Izvršeno"; + +/* XBUT: Error messages shown to users in DataTable */ +"There are %d errors in the data table." = "%d grešaka u tabeli podataka."; + +/* XBUT: voice over label for row header in DataTable */ +"row header" = "zaglavlje reda"; + +/* XBUT: voice over label for row %d in DataTable */ +"row %d" = "red %d"; + +/* XBUT: voice over label for column %d in DataTable */ +"column %d" = "kolona %d"; + +/* XBUT: voice over label for cell type of `leading accessory` in DataTable */ +"leading accessory" = "vodeći dodatak"; + +/* XBUT: voice over label for cell type of `trailing accessory` in DataTable */ +"trailing accessory" = "prateći dodatak"; + +/* XBUT: voice over label for cell type of `text` in DataTable */ +"text" = "tekst"; + +/* XBUT: voice over label for cell type of `image` in DataTable */ +"image" = "slika"; + +/* XBUT: voice over label for cell type of `date` in DataTable */ +"date" = "datum"; + +/* XBUT: voice over label for cell type of `time` in DataTable */ +"time" = "vrijeme"; + +/* XBUT: voice over label for cell type of `duration` in DataTable */ +"duration" = "trajanje"; + +/* XBUT: voice over label for cell type of `list item` in DataTable */ +"list item" = "stavka liste"; + +/* XBUT: voice over label for row selection status in DataTable */ +"selected" = "odabrano"; + +/* XBUT: voice over label for row selection status in DataTable */ +"not selected" = "nije odabrano"; + +/* XBUT: calendar time component label */ +"Time" = "Vrijeme"; + +/* XBUT: slider default value label formatter for integer */ +"Value: %d" = "Vrijednost: %d"; + +/* XMSG: a toast message for read-only cell in DataTable */ +"Tapped cell is read-only." = "Dodirnuta ćelija je samo za čitanje."; + +/* Default hint text for Read-only Field for NoteFormView and TextFieldFormView */ +"Read-only field" = "Polje samo za čitanje"; + +/* Default error text for when the character count reaches the maximum limit for NoteFormView and TextFieldFormView */ +"No more characters remaining" = "Nema preostalih znakova"; + +/* Default error text for when the character count exceeds the maximum limit for NoteFormView and TextFieldFormView */ +"Reduce the number of characters" = "Smanji broj znakova"; + +/* XBUT: voice over label for decrement action in stepper */ +"Stepper decrease" = "Smanjenje regulatora vrijednosti"; + +/* XBUT: voice over description for decrement action in stepper */ +"Decrease the value by %d" = "Smanji vrijednost za %d"; + +/* XBUT: voice over label for increment action in stepper */ +"Stepper increase" = "Povećanje regulatora vrijednosti"; + +/* XBUT: voice over description for increment action in stepper */ +"Increase the value by %d" = "Povećaj vrijednost za %d"; + +/* XBUT: voice over label for test field in stepper */ +"Select specific value" = "Odaberi određenu vrijednost"; + +/* XBUT: View all action in MenuSelection */ +"View All (%d)" = "Prikaži sve (%d)"; + +/* XACT: RatingControl accessibility label */ +"%d out of %d stars" = "%d od %d zvjezdica"; + diff --git a/Sources/FioriSwiftUICore/_localization/sr-Latn-RS.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/sr-Latn-RS.lproj/FioriSwiftUICore.strings index 1bc42b538..fc00533b3 100644 --- a/Sources/FioriSwiftUICore/_localization/sr-Latn-RS.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/sr-Latn-RS.lproj/FioriSwiftUICore.strings @@ -31,6 +31,9 @@ /* XACT: The accessibility label for the signature line */ "Signature Line" = "Red potpisa"; +/* XACT: The accessibility label for the mandatory field */ +"Required Field" = "Polje obaveznog unosa"; + /* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ "Canceled" = "Otkazano"; @@ -160,3 +163,24 @@ /* Default error text for when the character count exceeds the maximum limit for NoteFormView and TextFieldFormView */ "Reduce the number of characters" = "Smanji broj znakova"; +/* XBUT: voice over label for decrement action in stepper */ +"Stepper decrease" = "Smanjenje regulatora vrednosti"; + +/* XBUT: voice over description for decrement action in stepper */ +"Decrease the value by %d" = "Smanji vrednost za %d"; + +/* XBUT: voice over label for increment action in stepper */ +"Stepper increase" = "Povećanje regulatora vrednosti"; + +/* XBUT: voice over description for increment action in stepper */ +"Increase the value by %d" = "Povećaj vrednost za %d"; + +/* XBUT: voice over label for test field in stepper */ +"Select specific value" = "Odaberi određenu vrednost"; + +/* XBUT: View all action in MenuSelection */ +"View All (%d)" = "Prikaži sve (%d)"; + +/* XACT: RatingControl accessibility label */ +"%d out of %d stars" = "%d od %d zvezdica"; + diff --git a/Sources/FioriSwiftUICore/_localization/sr.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/sr.lproj/FioriSwiftUICore.strings index 1cce235b0..3828ba8cf 100644 --- a/Sources/FioriSwiftUICore/_localization/sr.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/sr.lproj/FioriSwiftUICore.strings @@ -31,6 +31,9 @@ /* XACT: The accessibility label for the signature line */ "Signature Line" = "Ред потписа"; +/* XACT: The accessibility label for the mandatory field */ +"Required Field" = "Поље обавезног уноса"; + /* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ "Canceled" = "Отказано"; @@ -160,3 +163,24 @@ /* Default error text for when the character count exceeds the maximum limit for NoteFormView and TextFieldFormView */ "Reduce the number of characters" = "Смањи број знакова"; +/* XBUT: voice over label for decrement action in stepper */ +"Stepper decrease" = "Смањење регулатора вредности"; + +/* XBUT: voice over description for decrement action in stepper */ +"Decrease the value by %d" = "Смањи вредност за %d"; + +/* XBUT: voice over label for increment action in stepper */ +"Stepper increase" = "Повећање регулатора вредности"; + +/* XBUT: voice over description for increment action in stepper */ +"Increase the value by %d" = "Повећај вредност за %d"; + +/* XBUT: voice over label for test field in stepper */ +"Select specific value" = "Одабери одређену вредност"; + +/* XBUT: View all action in MenuSelection */ +"View All (%d)" = "Прикажи све (%d)"; + +/* XACT: RatingControl accessibility label */ +"%d out of %d stars" = "%d од %d звездица"; + diff --git a/sourcery/allPhasesNoCache.sh b/sourcery/allPhasesNoCache.sh old mode 100755 new mode 100644 From cfee137f31d18108015cdabee28b492c291880b8 Mon Sep 17 00:00:00 2001 From: dyongxu <61523257+dyongxu@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:50:24 -0700 Subject: [PATCH 03/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20remove=20cnr=20whic?= =?UTF-8?q?h=20is=20not=20recognized=20by=20Apple=20Store=20(#777)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cnr is invalid language id by Apple Store Co-authored-by: David Xu --- .../Resources/cnr.lproj/Localizable.strings | 3 - .../cnr.lproj/FioriSwiftUICore.strings | 162 ------------------ 2 files changed, 165 deletions(-) delete mode 100644 Sources/FioriCharts/Resources/cnr.lproj/Localizable.strings delete mode 100644 Sources/FioriSwiftUICore/_localization/cnr.lproj/FioriSwiftUICore.strings diff --git a/Sources/FioriCharts/Resources/cnr.lproj/Localizable.strings b/Sources/FioriCharts/Resources/cnr.lproj/Localizable.strings deleted file mode 100644 index 05edd0e2e..000000000 --- a/Sources/FioriCharts/Resources/cnr.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* XMSG: Placeholder text in chart when there is no data */ -"No Data" = "Nema podataka"; - diff --git a/Sources/FioriSwiftUICore/_localization/cnr.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/cnr.lproj/FioriSwiftUICore.strings deleted file mode 100644 index 6fa87f7d4..000000000 --- a/Sources/FioriSwiftUICore/_localization/cnr.lproj/FioriSwiftUICore.strings +++ /dev/null @@ -1,162 +0,0 @@ -/* XMSG: Placeholder text in chart when there is no data */ -"No Data" = "Nema podataka"; - -/* XBUT: Save action in inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Save" = "Sačuvaj"; - -/* XBUT: Re-enter action available in saved state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Re-enter Signature" = "Ponovo unesite potpis"; - -/* XBUT: Clear action of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Clear" = "Izbriši"; - -/* XBUT: Cancel action of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Cancel" = "Odustani"; - -/* XTIT: Title of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Signature" = "Potpis"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Tap to Sign" = "Dodirnite da biste potpisali"; - -/* XACT: The accessibility label for the signature area */ -"Signature Area" = "Oblast potpisa"; - -/* XACT: The accessibility hint for the signature area */ -"Double tap and drag to sign" = "Dodirnite dvaput i prevucite da potpišete"; - -/* XACT: The accessibility label for the signature image */ -"Signature Image" = "Slika potpisa"; - -/* XACT: The accessibility label for the signature line */ -"Signature Line" = "Red potpisa"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Canceled" = "Otkazano"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Next" = "Sljedeće"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Ok" = "Ok"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Status" = "Status"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"User canceled Onboarding" = "Korisnik otkazao integraciju"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Without consent you will not be able to continue onboarding." = "Bez saglasnosti nećete moći da nastavite integraciju."; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Give Consent" = "Dajte saglasnost"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Quit" = "Izađi"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Back" = "Nazad"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Step" = "Korak"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"of" = "od"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Are you sure you want to quit the onboarding process?" = "Da li sigurno želite da napustite proces integracije?"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Want Data Privacy?" = "Da li želite privatnost podataka?"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Be wise about your data" = "Budite oprezni sa vašim podacima"; - -/* XBUT: Not Now action title in UserConsentForm */ -"Not Now" = "Ne sada"; - -/* XBUT: Allow action title in UserConsentForm */ -"Allow" = "Dozvoli"; - -/* XBUT: Deny action title in UserConsentForm */ -"Deny" = "Odbij"; - -/* XBUT: Agree action title in EULAView */ -"Agree" = "Saglasan sam"; - -/* XBUT: Disagree action title in EULAView */ -"Disagree" = "Nijesam saglasan"; - -/* XMSG: Placeholder text in DataTable for loading state */ -"Loading..." = "Učitavanje..."; - -/* XMSG: Placeholder text in DataTable when there is no data */ -"There is nothing to display yet" = "Nema još ništa za prikaz"; - -/* XBUT: Default duration foramtter in DataTable */ -"%d Hrs %d Min" = "%d sata/i %d min."; - -/* XBUT: Done action in keyboard toolbar for a Textfield in DataTable */ -"Done" = "Izvršeno"; - -/* XBUT: Error messages shown to users in DataTable */ -"There are %d errors in the data table." = "%d grešaka u tabeli podataka."; - -/* XBUT: voice over label for row header in DataTable */ -"row header" = "zaglavlje reda"; - -/* XBUT: voice over label for row %d in DataTable */ -"row %d" = "red %d"; - -/* XBUT: voice over label for column %d in DataTable */ -"column %d" = "kolona %d"; - -/* XBUT: voice over label for cell type of `leading accessory` in DataTable */ -"leading accessory" = "vodeći dodatak"; - -/* XBUT: voice over label for cell type of `trailing accessory` in DataTable */ -"trailing accessory" = "prateći dodatak"; - -/* XBUT: voice over label for cell type of `text` in DataTable */ -"text" = "tekst"; - -/* XBUT: voice over label for cell type of `image` in DataTable */ -"image" = "slika"; - -/* XBUT: voice over label for cell type of `date` in DataTable */ -"date" = "datum"; - -/* XBUT: voice over label for cell type of `time` in DataTable */ -"time" = "vrijeme"; - -/* XBUT: voice over label for cell type of `duration` in DataTable */ -"duration" = "trajanje"; - -/* XBUT: voice over label for cell type of `list item` in DataTable */ -"list item" = "stavka liste"; - -/* XBUT: voice over label for row selection status in DataTable */ -"selected" = "odabrano"; - -/* XBUT: voice over label for row selection status in DataTable */ -"not selected" = "nije odabrano"; - -/* XBUT: calendar time component label */ -"Time" = "Vrijeme"; - -/* XBUT: slider default value label formatter for integer */ -"Value: %d" = "Vrijednost: %d"; - -/* XMSG: a toast message for read-only cell in DataTable */ -"Tapped cell is read-only." = "Dodirnuta ćelija je samo za čitanje."; - -/* Default hint text for Read-only Field for NoteFormView and TextFieldFormView */ -"Read-only field" = "Polje samo za čitanje"; - -/* Default error text for when the character count reaches the maximum limit for NoteFormView and TextFieldFormView */ -"No more characters remaining" = "Nema preostalih znakova"; - -/* Default error text for when the character count exceeds the maximum limit for NoteFormView and TextFieldFormView */ -"Reduce the number of characters" = "Smanji broj znakova"; - From 702f7b7c735b3dc7c47198b45f2ba2c608cf2398 Mon Sep 17 00:00:00 2001 From: dyongxu <61523257+dyongxu@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:28:37 -0700 Subject: [PATCH 04/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20remove=20cnr=20whic?= =?UTF-8?q?h=20is=20not=20recognized=20by=20Apple=20Store=20(#778)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cnr is not valid language id as per Apple --- .../Resources/cnr.lproj/Localizable.strings | 3 - .../cnr.lproj/FioriSwiftUICore.strings | 162 ------------------ 2 files changed, 165 deletions(-) delete mode 100644 Sources/FioriCharts/Resources/cnr.lproj/Localizable.strings delete mode 100644 Sources/FioriSwiftUICore/_localization/cnr.lproj/FioriSwiftUICore.strings diff --git a/Sources/FioriCharts/Resources/cnr.lproj/Localizable.strings b/Sources/FioriCharts/Resources/cnr.lproj/Localizable.strings deleted file mode 100644 index 05edd0e2e..000000000 --- a/Sources/FioriCharts/Resources/cnr.lproj/Localizable.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* XMSG: Placeholder text in chart when there is no data */ -"No Data" = "Nema podataka"; - diff --git a/Sources/FioriSwiftUICore/_localization/cnr.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/cnr.lproj/FioriSwiftUICore.strings deleted file mode 100644 index 6fa87f7d4..000000000 --- a/Sources/FioriSwiftUICore/_localization/cnr.lproj/FioriSwiftUICore.strings +++ /dev/null @@ -1,162 +0,0 @@ -/* XMSG: Placeholder text in chart when there is no data */ -"No Data" = "Nema podataka"; - -/* XBUT: Save action in inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Save" = "Sačuvaj"; - -/* XBUT: Re-enter action available in saved state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Re-enter Signature" = "Ponovo unesite potpis"; - -/* XBUT: Clear action of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Clear" = "Izbriši"; - -/* XBUT: Cancel action of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Cancel" = "Odustani"; - -/* XTIT: Title of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Signature" = "Potpis"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Tap to Sign" = "Dodirnite da biste potpisali"; - -/* XACT: The accessibility label for the signature area */ -"Signature Area" = "Oblast potpisa"; - -/* XACT: The accessibility hint for the signature area */ -"Double tap and drag to sign" = "Dodirnite dvaput i prevucite da potpišete"; - -/* XACT: The accessibility label for the signature image */ -"Signature Image" = "Slika potpisa"; - -/* XACT: The accessibility label for the signature line */ -"Signature Line" = "Red potpisa"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Canceled" = "Otkazano"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Next" = "Sljedeće"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Ok" = "Ok"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Status" = "Status"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"User canceled Onboarding" = "Korisnik otkazao integraciju"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Without consent you will not be able to continue onboarding." = "Bez saglasnosti nećete moći da nastavite integraciju."; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Give Consent" = "Dajte saglasnost"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Quit" = "Izađi"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Back" = "Nazad"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Step" = "Korak"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"of" = "od"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Are you sure you want to quit the onboarding process?" = "Da li sigurno želite da napustite proces integracije?"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Want Data Privacy?" = "Da li želite privatnost podataka?"; - -/* XBUT: Action available in inactive state of inline signature form cell, see https://experience.sap.com/fiori-design-ios/article/in-line-signature-form-cell/#behavior-and-interaction */ -"Be wise about your data" = "Budite oprezni sa vašim podacima"; - -/* XBUT: Not Now action title in UserConsentForm */ -"Not Now" = "Ne sada"; - -/* XBUT: Allow action title in UserConsentForm */ -"Allow" = "Dozvoli"; - -/* XBUT: Deny action title in UserConsentForm */ -"Deny" = "Odbij"; - -/* XBUT: Agree action title in EULAView */ -"Agree" = "Saglasan sam"; - -/* XBUT: Disagree action title in EULAView */ -"Disagree" = "Nijesam saglasan"; - -/* XMSG: Placeholder text in DataTable for loading state */ -"Loading..." = "Učitavanje..."; - -/* XMSG: Placeholder text in DataTable when there is no data */ -"There is nothing to display yet" = "Nema još ništa za prikaz"; - -/* XBUT: Default duration foramtter in DataTable */ -"%d Hrs %d Min" = "%d sata/i %d min."; - -/* XBUT: Done action in keyboard toolbar for a Textfield in DataTable */ -"Done" = "Izvršeno"; - -/* XBUT: Error messages shown to users in DataTable */ -"There are %d errors in the data table." = "%d grešaka u tabeli podataka."; - -/* XBUT: voice over label for row header in DataTable */ -"row header" = "zaglavlje reda"; - -/* XBUT: voice over label for row %d in DataTable */ -"row %d" = "red %d"; - -/* XBUT: voice over label for column %d in DataTable */ -"column %d" = "kolona %d"; - -/* XBUT: voice over label for cell type of `leading accessory` in DataTable */ -"leading accessory" = "vodeći dodatak"; - -/* XBUT: voice over label for cell type of `trailing accessory` in DataTable */ -"trailing accessory" = "prateći dodatak"; - -/* XBUT: voice over label for cell type of `text` in DataTable */ -"text" = "tekst"; - -/* XBUT: voice over label for cell type of `image` in DataTable */ -"image" = "slika"; - -/* XBUT: voice over label for cell type of `date` in DataTable */ -"date" = "datum"; - -/* XBUT: voice over label for cell type of `time` in DataTable */ -"time" = "vrijeme"; - -/* XBUT: voice over label for cell type of `duration` in DataTable */ -"duration" = "trajanje"; - -/* XBUT: voice over label for cell type of `list item` in DataTable */ -"list item" = "stavka liste"; - -/* XBUT: voice over label for row selection status in DataTable */ -"selected" = "odabrano"; - -/* XBUT: voice over label for row selection status in DataTable */ -"not selected" = "nije odabrano"; - -/* XBUT: calendar time component label */ -"Time" = "Vrijeme"; - -/* XBUT: slider default value label formatter for integer */ -"Value: %d" = "Vrijednost: %d"; - -/* XMSG: a toast message for read-only cell in DataTable */ -"Tapped cell is read-only." = "Dodirnuta ćelija je samo za čitanje."; - -/* Default hint text for Read-only Field for NoteFormView and TextFieldFormView */ -"Read-only field" = "Polje samo za čitanje"; - -/* Default error text for when the character count reaches the maximum limit for NoteFormView and TextFieldFormView */ -"No more characters remaining" = "Nema preostalih znakova"; - -/* Default error text for when the character count exceeds the maximum limit for NoteFormView and TextFieldFormView */ -"Reduce the number of characters" = "Smanji broj znakova"; - From 9f3d48d1f8fd899d1c702746e2756eaa21c9c522 Mon Sep 17 00:00:00 2001 From: angiexyang <60192269+angiexyang@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:23:23 -0700 Subject: [PATCH 05/29] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20update=20REA?= =?UTF-8?q?DME.md=20(#779)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6150a382c..8e2bb49c5 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ We plan to progressively bring more Fiori UI components into this module in the | Menu Selection | :white_check_mark: | | ProfileHeader | :white_check_mark: | | TimelineItem | :white_check_mark: | -| Segmented Control | :x: | +| SegmentedControlPicker | :white_check_mark: | | TimelinePreviewItem | :x: | | ChartFloorplan | :x: | | CollectionItem | :x: | From 4a7d6ed73e8cdb485d737f1fd04fa4b692ca894d Mon Sep 17 00:00:00 2001 From: Bill Zhou Date: Mon, 19 Aug 2024 13:47:06 -0700 Subject: [PATCH 06/29] chore(release): 4.1.3 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e678e488..d7371ee26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [4.1.3](https://github.com/SAP/cloud-sdk-ios-fiori/compare/4.1.2...4.1.3) (2024-08-19) + +### Features + +* 🎸 [JIRA:HCPSDKFIORIUIKIT-2686] Button update ([#753](https://github.com/SAP/cloud-sdk-ios-fiori/issues/753)) ([dda6540](https://github.com/SAP/cloud-sdk-ios-fiori/commit/dda65406614490508a93512beb83be872a20bc00)) + + +### Bug Fixes + +* 🐛 [IOSSDKBUG-291] set line limit for MenuSelectionItem ([#769](https://github.com/SAP/cloud-sdk-ios-fiori/issues/769)) ([2e60eac](https://github.com/SAP/cloud-sdk-ios-fiori/commit/2e60eac32e587c92e33f04c91cf37813b45f78b5)) +* 🐛 [JIRA: IOSSDKBUG-306] toolbar layout issue ([#771](https://github.com/SAP/cloud-sdk-ios-fiori/issues/771)) ([295201e](https://github.com/SAP/cloud-sdk-ios-fiori/commit/295201e5f5adabb4457a1ad39b208df390839055)) +* 🐛 remove cnr which is not recognized by Apple Store ([#777](https://github.com/SAP/cloud-sdk-ios-fiori/issues/777)) ([cfee137](https://github.com/SAP/cloud-sdk-ios-fiori/commit/cfee137f31d18108015cdabee28b492c291880b8)) +* 🐛 step progress indicator styles ([#775](https://github.com/SAP/cloud-sdk-ios-fiori/issues/775)) ([01b43e4](https://github.com/SAP/cloud-sdk-ios-fiori/commit/01b43e407a9cf44fd8de68d2113de3b775421578)) + ### [4.1.2](https://github.com/SAP/cloud-sdk-ios-fiori/compare/4.1.1...4.1.2) (2024-08-12) From 3539228dd024f48d674b714eb2313d1e88108ebf Mon Sep 17 00:00:00 2001 From: xiaoqinggrace <52239714+xiaoqinggrace@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:47:47 -0700 Subject: [PATCH 07/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:HCPSDKFIORIU?= =?UTF-8?q?IKIT-2702]Illustrated=20Message=20Enhancement=20(#772)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2702]Illustrated Message Illustrated Message Enhancement * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2702]Illustrated Message enhancement * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2702]Illustrated Message Update the API name * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2702]Illustrated Message --------- Co-authored-by: I824136 --- .../IllustratedMessageExample.swift | 77 ++++-- .../CompositeComponentProtocols.swift | 9 +- .../IllustratedMessageStyle.fiori.swift | 243 ++++++++++++++++-- .../IllustratedMessage.generated.swift | 28 +- .../IllustratedMessageStyle.generated.swift | 5 + ...entStyleProtocol+Extension.generated.swift | 21 ++ ...iewEmptyChecking+Extension.generated.swift | 3 +- 7 files changed, 342 insertions(+), 44 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/IllustratedMessage/IllustratedMessageExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/IllustratedMessage/IllustratedMessageExample.swift index 5ab3e9547..bf5d19563 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/IllustratedMessage/IllustratedMessageExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/IllustratedMessage/IllustratedMessageExample.swift @@ -15,19 +15,48 @@ let sizeOptions: [SizeOption] = [.init(100), .init(200), .init(300), .init(500), enum LayoutAxis { case vertical case horizontal + case mixed +} + +enum ButtonWidthMode { + case fixed + case fullWidth + case flexible +} + +enum AlignmentMode: String, CaseIterable, Identifiable { + case center = "Center" + case leading = "Leading" + case trailing = "Trailing" + + var id: String { self.rawValue } + + var alignment: HorizontalAlignment { + switch self { + case .center: + return .center + case .leading: + return .leading + case .trailing: + return .trailing + } + } } struct IllustratedMessageExample: View { @State var selectedDetailImageSize: IllustratedMessage.DetailImageSize? @State var selectedLayoutAxis: LayoutAxis? - @State var selectedWidth: CGFloat = sizeOptions[3].value + @State var selectedWidth: CGFloat = sizeOptions[2].value @State var selectedHeight: CGFloat = sizeOptions[2].value + @State var showSecondButton: Bool = false + @State var isActionButtonVertical: Bool = false + @State var actionButtonWidth: ButtonWidthMode? + @State var contentStackAlignment: AlignmentMode = .leading var body: some View { - VStack(spacing: 0) { - HStack(spacing: 0) { - Text("Img Size:") - Picker("Img Size", selection: self.$selectedDetailImageSize) { + List { + VStack(spacing: 0) { + Picker("Imgage Size", selection: self.$selectedDetailImageSize) { Text("Unset").tag(IllustratedMessage.DetailImageSize?(nil)) Text("XS").tag(IllustratedMessage.DetailImageSize?(.extraSmall)) Text("S").tag(IllustratedMessage.DetailImageSize?(.small)) @@ -35,34 +64,42 @@ struct IllustratedMessageExample: View { Text("L").tag(IllustratedMessage.DetailImageSize?(.large)) Text("XL").tag(IllustratedMessage.DetailImageSize?(.extraLarge)) } - Text("Layout Axis:") Picker("Layout Axis", selection: self.$selectedLayoutAxis) { Text("Unset").tag(LayoutAxis?(nil)) Text("Vertical").tag(LayoutAxis?(.vertical)) Text("Horizontal").tag(LayoutAxis?(.horizontal)) + Text("Mixed").tag(LayoutAxis?(.mixed)) } - } - HStack(spacing: 0) { - Text("Size:") Picker("Width", selection: self.$selectedWidth) { ForEach(sizeOptions) { option in Text("\(Int(option.value))").tag(option.value) } } - Text("by") Picker("Height", selection: self.$selectedHeight) { ForEach(sizeOptions) { option in Text("\(Int(option.value))").tag(option.value) } } + Picker("Content Alignment in Horizontal Style", selection: self.$contentStackAlignment) { + ForEach(AlignmentMode.allCases) { alignment in + Text(alignment.rawValue).tag(alignment) + } + } + Picker("Button Width (vertical)", selection: self.$actionButtonWidth) { + Text("Unset").tag(ButtonWidthMode?(nil)) + Text("Fixed").tag(ButtonWidthMode?(.fixed)) + Text("FullWidth").tag(ButtonWidthMode?(.fullWidth)) + Text("Flexible").tag(ButtonWidthMode?(.flexible)) + } + Toggle("Shows the 2nd Action Button", isOn: self.$showSecondButton) + Toggle("Action Button vertical Aligned", isOn: self.$isActionButtonVertical) } - } - List { + ForEach(0 ... 7, id: \.self) { subcomponentConfiguration in let hasImage = subcomponentConfiguration & 0b100 == 4 let hasDescription = subcomponentConfiguration & 0b010 == 2 let hasAction = subcomponentConfiguration & 0b001 == 1 - + HStack(spacing: 0) { Spacer() VStack(spacing: 10) { @@ -78,16 +115,22 @@ struct IllustratedMessageExample: View { }, description: { hasDescription ? Text("This is description text for the Illustrated Message component") : nil }, action: { - hasAction ? FioriButton(title: "Button", action: { _ in print("Tapped Action") }) : nil - }, detailImageSize: self.selectedDetailImageSize) + hasAction ? FioriButton(title: "Action", action: { _ in print("Tapped Action") }) : nil + }, secondaryAction: { + hasAction && self.showSecondButton ? FioriButton(title: "Secondary Action", action: { _ in print("Tapped Secondary Action") }) : nil + }, detailImageSize: self.selectedDetailImageSize, isActionVerticallyAligned: self.isActionButtonVertical, contentAlignment: self.contentStackAlignment.alignment) .frame(width: self.selectedWidth, height: self.selectedHeight) .background(Color.preferredColor(.secondaryBackground)) .ifApply(self.selectedLayoutAxis == .vertical) { $0.illustratedMessageStyle(.vertical) } .ifApply(self.selectedLayoutAxis == .horizontal) { $0.illustratedMessageStyle(.horizontal) } + .ifApply(self.selectedLayoutAxis == .mixed) { $0.illustratedMessageStyle(.mixed) } + .ifApply(self.actionButtonWidth == .fixed) { $0.illustratedMessageStyle(.fixedWidthButton) } + .ifApply(self.actionButtonWidth == .flexible) { $0.illustratedMessageStyle(.flexibleButton) } + .ifApply(self.actionButtonWidth == .fullWidth) { $0.illustratedMessageStyle(.fullWidthButton) } } Spacer() } - .listRowSeparator(.hidden) + .border(/*@START_MENU_TOKEN@*/Color.black/*@END_MENU_TOKEN@*/, width: /*@START_MENU_TOKEN@*/1/*@END_MENU_TOKEN@*/) .padding(20) } } @@ -95,7 +138,7 @@ struct IllustratedMessageExample: View { func generateCaptionText(_ hasImage: Bool, _ hasDescription: Bool, _ hasAction: Bool) -> String { var subcomponentConfigurationText = "" - + if hasImage { subcomponentConfigurationText = subcomponentConfigurationText + "Image, " } diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index 380d30361..94987fa3c 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -33,8 +33,15 @@ protocol _CardHeaderComponent: _CardMediaComponent, _CardMainHeaderComponent, _C protocol _CardComponent: _CardHeaderComponent, _CardBodyComponent, _CardFooterComponent {} // sourcery: CompositeComponent -protocol _IllustratedMessageComponent: _DetailImageComponent, _TitleComponent, _DescriptionComponent, _ActionComponent { +protocol _IllustratedMessageComponent: _DetailImageComponent, _TitleComponent, _DescriptionComponent, _ActionComponent, _SecondaryActionComponent { + /// Specifies the size of the detailImage. This value should be an enum from the DetailImageSize set. var detailImageSize: IllustratedMessage.DetailImageSize? { get } + // sourcery: defaultValue = false + /// Determines the layout of the action buttons. If set to true, the buttons will be arranged vertically. If set to false, they will be arranged horizontally. The default value is false. + var isActionVerticallyAligned: Bool { get } + // sourcery: defaultValue = .leading + /// Determines the alignment of the title, description and the action buttons in the horizontal mode. The default valu e is `.leading`. + var contentAlignment: HorizontalAlignment { get } } // sourcery: CompositeComponent diff --git a/Sources/FioriSwiftUICore/_FioriStyles/IllustratedMessageStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/IllustratedMessageStyle.fiori.swift index 6ba07f34f..8f27ee298 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/IllustratedMessageStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/IllustratedMessageStyle.fiori.swift @@ -2,15 +2,6 @@ import FioriThemeManager import Foundation import SwiftUI -/** - This file provides default fiori style for the component. - - 1. Uncomment fhe following code. - 2. Implement layout and style in corresponding places. - 3. Delete `.generated` from file name. - 4. Move this file to `_FioriStyles` folder under `FioriSwiftUICore`. - */ - public extension IllustratedMessage { enum DetailImageSize { case extraSmall @@ -31,9 +22,26 @@ public struct IllustratedMessageBaseStyle: IllustratedMessageStyle { .multilineTextAlignment(.center) configuration.description .multilineTextAlignment(.center) - configuration.action - .padding(.top, 24) + + if configuration.isActionVerticallyAligned { + VStack(spacing: 8) { + configuration.action + .fioriButtonStyle(FioriPrimaryButtonStyle()) + configuration.secondaryAction + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint)) + } + .padding(.top, (!configuration.action.isEmpty || !configuration.secondaryAction.isEmpty) ? 16 : 0) + } else { + HStack { + configuration.secondaryAction + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint, maxWidth: .infinity)) + configuration.action + .fioriButtonStyle(FioriPrimaryButtonStyle()) // .infinity)) + } + .padding(.top, (!configuration.action.isEmpty || !configuration.secondaryAction.isEmpty) ? 16 : 0) + } } + .padding(.horizontal, 16) } } @@ -60,7 +68,6 @@ extension IllustratedMessageFioriStyle { func makeBody(_ configuration: TitleConfiguration) -> some View { Title(configuration) .font(.fiori(forTextStyle: .headline, weight: .bold)) - .padding(.horizontal, 16) .padding(.bottom, 4) } } @@ -70,7 +77,6 @@ extension IllustratedMessageFioriStyle { func makeBody(_ configuration: DescriptionConfiguration) -> some View { Description(configuration) - .padding(.horizontal, 16) } } @@ -79,27 +85,192 @@ extension IllustratedMessageFioriStyle { func makeBody(_ configuration: ActionConfiguration) -> some View { Action(configuration) + .font(.fiori(forTextStyle: .body, weight: .bold)) + .fioriButtonStyle(FioriPrimaryButtonStyle(.infinity)) + } + } + + struct SecondaryActionFioriStyle: SecondaryActionStyle { + let illustratedMessageConfiguration: IllustratedMessageConfiguration + + func makeBody(_ configuration: SecondaryActionConfiguration) -> some View { + SecondaryAction(configuration) + .font(.fiori(forTextStyle: .body, weight: .bold)) + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint, maxWidth: .infinity)) } } } /// Vertical layout style of the Illustrated Message. All content is displayed in one column. This is the default layout style -public typealias IllustratedMessageVerticalLayoutStyle = IllustratedMessageBaseStyle +public struct IllustratedMessageVerticalLayoutStyle: IllustratedMessageStyle { + public func makeBody(_ configuration: IllustratedMessageConfiguration) -> some View { + IllustratedMessage(configuration) + .illustratedMessageStyle(.fixedWidthButton) + } +} /// Horizontal layout style of the Illustrated Message. Shows content in two columns, with an image on the left and a vertical stack of the other content on the right public struct IllustratedMessageHorizontalLayoutStyle: IllustratedMessageStyle { public func makeBody(_ configuration: IllustratedMessageConfiguration) -> some View { - HStack(spacing: 0) { + HStack(spacing: 16) { getResizedDetailImage(detailImage: configuration.detailImage, size: configuration.detailImageSize ?? IllustratedMessage.DetailImageSize.medium) - .padding(.leading, 16) - VStack(alignment: .leading, spacing: 0) { + VStack(alignment: configuration.contentAlignment, spacing: 4) { configuration.title + .multilineTextAlignment(getTextAlignment(configuration.contentAlignment)) configuration.description - configuration.action - .padding(.horizontal, 16) - .padding(.top, 16) + .multilineTextAlignment(getTextAlignment(configuration.contentAlignment)) + if configuration.isActionVerticallyAligned { + VStack(alignment: configuration.contentAlignment) { + configuration.action + .fioriButtonStyle(FioriPrimaryButtonStyle()) + configuration.secondaryAction + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint)) + } + .padding(.top, (!configuration.action.isEmpty || !configuration.secondaryAction.isEmpty) ? 16 : 0) + } else { + HStack(spacing: 8) { + configuration.secondaryAction + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint, maxWidth: .infinity)) + configuration.action + .fioriButtonStyle(FioriPrimaryButtonStyle()) + } + .padding(.top, (!configuration.action.isEmpty || !configuration.secondaryAction.isEmpty) ? 16 : 0) + } + } + } + .padding(.horizontal, 16) + } +} + +/// Mixed layout style of the Illustrated Message. Shows content in two columns, with an image on the left, a vertical stack of the title and descrition contents on the right, and the action button stack on the bottom. +public struct IllustratedMessageMixedLayoutStyle: IllustratedMessageStyle { + public func makeBody(_ configuration: IllustratedMessageConfiguration) -> some View { + VStack(spacing: 0) { + HStack(spacing: 16) { + getResizedDetailImage(detailImage: configuration.detailImage, size: configuration.detailImageSize ?? IllustratedMessage.DetailImageSize.medium) + VStack(alignment: configuration.contentAlignment, spacing: 4) { + configuration.title + .multilineTextAlignment(getTextAlignment(configuration.contentAlignment)) + configuration.description + .multilineTextAlignment(getTextAlignment(configuration.contentAlignment)) + } } + + if configuration.isActionVerticallyAligned { + VStack(alignment: .center) { + configuration.action + .fioriButtonStyle(FioriPrimaryButtonStyle()) + configuration.secondaryAction + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint)) + } + .padding(.top, (!configuration.action.isEmpty || !configuration.secondaryAction.isEmpty) ? 16 : 0) + } else { + HStack { + configuration.secondaryAction + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint, maxWidth: .infinity)) + configuration.action + .fioriButtonStyle(FioriPrimaryButtonStyle()) + } + .padding(.top, (!configuration.action.isEmpty || !configuration.secondaryAction.isEmpty) ? 16 : 0) + } + } + .padding(.horizontal, 16) + } +} + +extension IllustratedMessageStyle { + func getTextAlignment(_ alignment: HorizontalAlignment) -> TextAlignment { + switch alignment { + case .leading: + return .leading + case .center: + return .center + case .trailing: + return .trailing + default: + return .leading + } + } +} + +/// Flexible action button style of the Illustrated Message. The width of the primary action button is hugged and the secondary action button will take the rest of the container width. +public struct IllustratedMessageFlexibleButtonStyle: IllustratedMessageStyle { + public func makeBody(_ configuration: IllustratedMessageConfiguration) -> some View { + IllustratedMessage(configuration) + .actionStyle(content: { actionConfiguration in + actionConfiguration.action + .fioriButtonStyle(FioriPrimaryButtonStyle().eraseToAnyFioriButtonStyle()) + }) + .secondaryActionStyle(content: { secondaryActionConfiguration in + secondaryActionConfiguration.secondaryAction + .fioriButtonStyle((configuration.isActionVerticallyAligned ? FioriSecondaryButtonStyle(colorStyle: .tint) : FioriSecondaryButtonStyle(colorStyle: .tint, maxWidth: .infinity)).eraseToAnyFioriButtonStyle()) + }) + } +} + +/// Full width action button style of the Illustrated Message. The width of both action buttons will take the width of the container. +public struct IllustratedMessageFullWidthButtonStyle: IllustratedMessageStyle { + @State private var buttonSize: CGSize = .zero + public func makeBody(_ configuration: IllustratedMessageConfiguration) -> some View { + IllustratedMessage(configuration) + .overlay( + GeometryReader { geometry in + Color.clear + .onAppear { + self.buttonSize = geometry.size + } + } + ) + .actionStyle(content: { actionConfiguration in + actionConfiguration.action + .fioriButtonStyle(FioriPrimaryButtonStyle(self.getButtonWidth(configuration)).eraseToAnyFioriButtonStyle()) + }) + .secondaryActionStyle(content: { secondaryActionConfiguration in + secondaryActionConfiguration.secondaryAction + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint, maxWidth: self.getButtonWidth(configuration)).eraseToAnyFioriButtonStyle()) + }) + } + + func getButtonWidth(_ configuration: IllustratedMessageConfiguration) -> CGFloat { + let splitMode = !configuration.isActionVerticallyAligned && !configuration.secondaryAction.isEmpty + var buttonWidth: CGFloat = .infinity + if self.buttonSize.width > 0 { + buttonWidth = splitMode ? (self.buttonSize.width - 16) / 2 : self.buttonSize.width + } + return buttonWidth + } +} + +/// Fixed width action button style of the Illustrated Message. The width of both action buttons will take the width of the container. +public struct IllustratedMessageFixedWidthButtonStyle: IllustratedMessageStyle { + @State private var buttonSize: CGSize = .zero + public func makeBody(_ configuration: IllustratedMessageConfiguration) -> some View { + IllustratedMessage(configuration) + .overlay( + GeometryReader { geometry in + Color.clear + .onAppear { + self.buttonSize = geometry.size + } + } + ) + .actionStyle(content: { actionConfiguration in + actionConfiguration.action + .fioriButtonStyle(FioriPrimaryButtonStyle(self.getButtonWidth(configuration)).eraseToAnyFioriButtonStyle()) + }) + .secondaryActionStyle(content: { secondaryActionConfiguration in + secondaryActionConfiguration.secondaryAction + .fioriButtonStyle(FioriSecondaryButtonStyle(colorStyle: .tint, maxWidth: self.getButtonWidth(configuration)).eraseToAnyFioriButtonStyle()) + }) + } + + func getButtonWidth(_ configuration: IllustratedMessageConfiguration) -> CGFloat { + let splitMode = !configuration.isActionVerticallyAligned && !configuration.secondaryAction.isEmpty + var buttonWidth: CGFloat = .infinity + if self.buttonSize.width > 0 { + buttonWidth = splitMode ? (self.buttonSize.width - 16) / 2 : self.buttonSize.width * 0.6 } + return buttonWidth } } @@ -117,6 +288,38 @@ public extension IllustratedMessageStyle where Self == IllustratedMessageHorizon } } +/// Mixed layout style of the Illustrated Message. Shows content in two columns, with an image on the left, a vertical stack of the title and descrition contents on the right, and the action button stack on the bottom. +public extension IllustratedMessageStyle where Self == IllustratedMessageMixedLayoutStyle { + /// Mixed layout style of the Illustrated Message. Shows content in two columns, with an image on the left, a vertical stack of the title and descrition contents on the right, and the action button stack on the bottom. + static var mixed: some IllustratedMessageStyle { + IllustratedMessageMixedLayoutStyle().concat(.fiori) + } +} + +/// Flexible action button style of the Illustrated Message. The width of the primary action button is hugged and the secondary action button will take the rest of the container width. +public extension IllustratedMessageStyle where Self == IllustratedMessageFlexibleButtonStyle { + /// Flexible action button style of the Illustrated Message. The width of the primary action button is hugged and the secondary action button will take the rest of the container width. + static var flexibleButton: some IllustratedMessageStyle { + IllustratedMessageFlexibleButtonStyle().concat(.fiori) + } +} + +/// Full width action button style of the Illustrated Message. The width of both action buttons will take the width of the container. +public extension IllustratedMessageStyle where Self == IllustratedMessageFullWidthButtonStyle { + /// Full width action button style of the Illustrated Message. The width of both action buttons will take the width of the container. + static var fullWidthButton: some IllustratedMessageStyle { + IllustratedMessageFullWidthButtonStyle().concat(.fiori) + } +} + +/// Fixed width action button style of the Illustrated Message. The width of both action buttons is fixed. +public extension IllustratedMessageStyle where Self == IllustratedMessageFixedWidthButtonStyle { + /// Full width action button style of the Illustrated Message. The width of both action buttons is fixed + static var fixedWidthButton: some IllustratedMessageStyle { + IllustratedMessageFixedWidthButtonStyle().concat(.fiori) + } +} + func getResizedDetailImage(detailImage: IllustratedMessageConfiguration.DetailImage, size: IllustratedMessage.DetailImageSize) -> some View { switch size { case .extraSmall: diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessage.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessage.generated.swift index 12d84ccbc..64b766559 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessage.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessage.generated.swift @@ -8,7 +8,13 @@ public struct IllustratedMessage { let title: any View let description: any View let action: any View + let secondaryAction: any View + /// Specifies the size of the detailImage. This value should be an enum from the DetailImageSize set. let detailImageSize: IllustratedMessage.DetailImageSize? + /// Determines the layout of the action buttons. If set to true, the buttons will be arranged vertically. If set to false, they will be arranged horizontally. The default value is false. + let isActionVerticallyAligned: Bool + /// Determines the alignment of the title, description and the action buttons in the horizontal mode. The default valu e is `.leading`. + let contentAlignment: HorizontalAlignment @Environment(\.illustratedMessageStyle) var style @@ -18,13 +24,19 @@ public struct IllustratedMessage { @ViewBuilder title: () -> any View, @ViewBuilder description: () -> any View = { EmptyView() }, @ViewBuilder action: () -> any View = { EmptyView() }, - detailImageSize: IllustratedMessage.DetailImageSize? = nil) + @ViewBuilder secondaryAction: () -> any View = { EmptyView() }, + detailImageSize: IllustratedMessage.DetailImageSize? = nil, + isActionVerticallyAligned: Bool = false, + contentAlignment: HorizontalAlignment = .leading) { self.detailImage = DetailImage { detailImage() } self.title = Title { title() } self.description = Description { description() } self.action = Action { action() } + self.secondaryAction = SecondaryAction { secondaryAction() } self.detailImageSize = detailImageSize + self.isActionVerticallyAligned = isActionVerticallyAligned + self.contentAlignment = contentAlignment } } @@ -33,9 +45,12 @@ public extension IllustratedMessage { title: AttributedString, description: AttributedString? = nil, action: FioriButton? = nil, - detailImageSize: IllustratedMessage.DetailImageSize? = nil) + secondaryAction: FioriButton? = nil, + detailImageSize: IllustratedMessage.DetailImageSize? = nil, + isActionVerticallyAligned: Bool = false, + contentAlignment: HorizontalAlignment = .leading) { - self.init(detailImage: { detailImage }, title: { Text(title) }, description: { OptionalText(description) }, action: { action }, detailImageSize: detailImageSize) + self.init(detailImage: { detailImage }, title: { Text(title) }, description: { OptionalText(description) }, action: { action }, secondaryAction: { secondaryAction }, detailImageSize: detailImageSize, isActionVerticallyAligned: isActionVerticallyAligned, contentAlignment: contentAlignment) } } @@ -49,7 +64,10 @@ public extension IllustratedMessage { self.title = configuration.title self.description = configuration.description self.action = configuration.action + self.secondaryAction = configuration.secondaryAction self.detailImageSize = configuration.detailImageSize + self.isActionVerticallyAligned = configuration.isActionVerticallyAligned + self.contentAlignment = configuration.contentAlignment self._shouldApplyDefaultStyle = shouldApplyDefaultStyle } } @@ -59,7 +77,7 @@ extension IllustratedMessage: View { if self._shouldApplyDefaultStyle { self.defaultStyle() } else { - self.style.resolve(configuration: .init(detailImage: .init(self.detailImage), title: .init(self.title), description: .init(self.description), action: .init(self.action), detailImageSize: self.detailImageSize)).typeErased + self.style.resolve(configuration: .init(detailImage: .init(self.detailImage), title: .init(self.title), description: .init(self.description), action: .init(self.action), secondaryAction: .init(self.secondaryAction), detailImageSize: self.detailImageSize, isActionVerticallyAligned: self.isActionVerticallyAligned, contentAlignment: self.contentAlignment)).typeErased .transformEnvironment(\.illustratedMessageStyleStack) { stack in if !stack.isEmpty { stack.removeLast() @@ -77,7 +95,7 @@ private extension IllustratedMessage { } func defaultStyle() -> some View { - IllustratedMessage(.init(detailImage: .init(self.detailImage), title: .init(self.title), description: .init(self.description), action: .init(self.action), detailImageSize: self.detailImageSize)) + IllustratedMessage(.init(detailImage: .init(self.detailImage), title: .init(self.title), description: .init(self.description), action: .init(self.action), secondaryAction: .init(self.secondaryAction), detailImageSize: self.detailImageSize, isActionVerticallyAligned: self.isActionVerticallyAligned, contentAlignment: self.contentAlignment)) .shouldApplyDefaultStyle(false) .illustratedMessageStyle(IllustratedMessageFioriStyle.ContentFioriStyle()) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessageStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessageStyle.generated.swift index 0cc1fa5cf..768b04afe 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessageStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessageStyle.generated.swift @@ -26,12 +26,16 @@ public struct IllustratedMessageConfiguration { public let title: Title public let description: Description public let action: Action + public let secondaryAction: SecondaryAction public let detailImageSize: IllustratedMessage.DetailImageSize? + public let isActionVerticallyAligned: Bool + public let contentAlignment: HorizontalAlignment public typealias DetailImage = ConfigurationViewWrapper public typealias Title = ConfigurationViewWrapper public typealias Description = ConfigurationViewWrapper public typealias Action = ConfigurationViewWrapper + public typealias SecondaryAction = ConfigurationViewWrapper } public struct IllustratedMessageFioriStyle: IllustratedMessageStyle { @@ -41,5 +45,6 @@ public struct IllustratedMessageFioriStyle: IllustratedMessageStyle { .titleStyle(TitleFioriStyle(illustratedMessageConfiguration: configuration)) .descriptionStyle(DescriptionFioriStyle(illustratedMessageConfiguration: configuration)) .actionStyle(ActionFioriStyle(illustratedMessageConfiguration: configuration)) + .secondaryActionStyle(SecondaryActionFioriStyle(illustratedMessageConfiguration: configuration)) } } diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift index dc4a8856d..bbe5866f6 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift @@ -1781,6 +1781,27 @@ public extension IllustratedMessageStyle where Self == IllustratedMessageActionS } } +public struct IllustratedMessageSecondaryActionStyle: IllustratedMessageStyle { + let style: any SecondaryActionStyle + + public func makeBody(_ configuration: IllustratedMessageConfiguration) -> some View { + IllustratedMessage(configuration) + .secondaryActionStyle(self.style) + .typeErased + } +} + +public extension IllustratedMessageStyle where Self == IllustratedMessageSecondaryActionStyle { + static func secondaryActionStyle(_ style: some SecondaryActionStyle) -> IllustratedMessageSecondaryActionStyle { + IllustratedMessageSecondaryActionStyle(style: style) + } + + static func secondaryActionStyle(@ViewBuilder content: @escaping (SecondaryActionConfiguration) -> some View) -> IllustratedMessageSecondaryActionStyle { + let style = AnySecondaryActionStyle(content) + return IllustratedMessageSecondaryActionStyle(style: style) + } +} + // MARK: IncrementActionStyle public extension IncrementActionStyle where Self == IncrementActionBaseStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift index 44fa6080a..1293778fb 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift @@ -218,7 +218,8 @@ extension IllustratedMessage: _ViewEmptyChecking { detailImage.isEmpty && title.isEmpty && description.isEmpty && - action.isEmpty + action.isEmpty && + secondaryAction.isEmpty } } From 03eda287dc96504afe4d0f22660c1c577c284f71 Mon Sep 17 00:00:00 2001 From: angiexyang <60192269+angiexyang@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:41:40 -0700 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:HCPSDKFIORIU?= =?UTF-8?q?IKIT-2689]=20SwiftUI=20Switch=20=20(#781)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2689] SwiftUI Switch ✅ Closes: HCPSDKFIORIUIKIT-2689 * chore: 🤖 merge --- .../Examples.xcodeproj/project.pbxproj | 4 + .../FioriSwiftUICore/CoreContentView.swift | 7 ++ .../FormCells/SwitchExample.swift | 41 ++++++++++ .../CompositeComponentProtocols.swift | 11 +++ .../_FioriStyles/SwitchViewStyle.fiori.swift | 66 ++++++++++++++++ .../SwitchView/SwitchView.generated.swift | 78 +++++++++++++++++++ .../SwitchViewStyle.generated.swift | 37 +++++++++ ...entStyleProtocol+Extension.generated.swift | 56 +++++++++++++ .../EnvironmentVariables.generated.swift | 21 +++++ .../ModifiedStyle.generated.swift | 28 +++++++ .../ResolvedStyle.generated.swift | 16 ++++ ...yleConfiguration+Extension.generated.swift | 8 ++ .../View+Extension_.generated.swift | 17 ++++ ...iewEmptyChecking+Extension.generated.swift | 6 ++ 14 files changed, 396 insertions(+) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/FormCells/SwitchExample.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/SwitchViewStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchView.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchViewStyle.generated.swift diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 358ca7f77..1ca14cab4 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 3B62AB7E2C0EE257003262EB /* EditableSideBarExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B62AB7C2C0EE257003262EB /* EditableSideBarExample.swift */; }; 3C180C282B858CF6007CE79A /* IllustratedMessageExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C180C272B858CF6007CE79A /* IllustratedMessageExample.swift */; }; 6432FFA02C5164F8008ECE89 /* SegmentedControlExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432FF9F2C5164F8008ECE89 /* SegmentedControlExample.swift */; }; + 64905D072C6D13E20062AAD4 /* SwitchExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64905D062C6D13E20062AAD4 /* SwitchExample.swift */; }; 691DE21925F2A30B00094D4A /* KPIViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691DE21825F2A30B00094D4A /* KPIViewExample.swift */; }; 692F338B26556A6A009B98DA /* SideBarExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692F338A26556A6A009B98DA /* SideBarExample.swift */; }; 69B2B5D9268A333C009AC6B3 /* KPIProgressViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B2B5D8268A333C009AC6B3 /* KPIProgressViewExample.swift */; }; @@ -229,6 +230,7 @@ 3B62AB7C2C0EE257003262EB /* EditableSideBarExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditableSideBarExample.swift; sourceTree = ""; }; 3C180C272B858CF6007CE79A /* IllustratedMessageExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IllustratedMessageExample.swift; sourceTree = ""; }; 6432FF9F2C5164F8008ECE89 /* SegmentedControlExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlExample.swift; sourceTree = ""; }; + 64905D062C6D13E20062AAD4 /* SwitchExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchExample.swift; sourceTree = ""; }; 691DE21825F2A30B00094D4A /* KPIViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KPIViewExample.swift; sourceTree = ""; }; 692F338A26556A6A009B98DA /* SideBarExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarExample.swift; sourceTree = ""; }; 69B2B5D8268A333C009AC6B3 /* KPIProgressViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KPIProgressViewExample.swift; sourceTree = ""; }; @@ -411,6 +413,7 @@ 1F3C92F025DF12C100A99A07 /* ListPicker.swift */, B141D6BA29261F9E008A8BD6 /* SearchableListViewExample.swift */, 99193C842B719B8800F33BAF /* InformationViewExample.swift */, + 64905D062C6D13E20062AAD4 /* SwitchExample.swift */, ); path = FormCells; sourceTree = ""; @@ -1149,6 +1152,7 @@ 6D6E866F2C539CDE00EDB6F4 /* InPlaceLoadingFlexibleButtonExample.swift in Sources */, B1BCB6E12C2EB362008AC070 /* ProfileHeaderStaticExample.swift in Sources */, C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */, + 64905D072C6D13E20062AAD4 /* SwitchExample.swift in Sources */, B1F6FC302B22BDDA005190F9 /* ToolbarView.swift in Sources */, B84D24EF2652F343007F2373 /* ObjectHeaderTestApp.swift in Sources */, B84D24EC2652F343007F2373 /* ObjectHeaderSpecCompact.swift in Sources */, diff --git a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift index ff556ef15..d4890d60f 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift @@ -168,6 +168,13 @@ struct CoreContentView: View { } ) + NavigationLink( + destination: SwitchExample(), + label: { + Text("Switch") + } + ) + NavigationLink( destination: StepProgressIndicatorExample()) { diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FormCells/SwitchExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FormCells/SwitchExample.swift new file mode 100644 index 000000000..ca1889f0e --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/FormCells/SwitchExample.swift @@ -0,0 +1,41 @@ +import FioriSwiftUICore +import Foundation +import SwiftUI + +struct SwitchExample: View { + @State var v1: Bool = false + @State var v2: Bool = true + @State var v3: Bool = true + + struct CustomTitleStyle: TitleStyle { + @Environment(\.isEnabled) var isEnabled + func makeBody(_ configuration: TitleConfiguration) -> some View { + Title(configuration) + .font(.fiori(forTextStyle: .title3)) + .foregroundStyle(Color.preferredColor(self.isEnabled ? .indigo7 : .grey5)) + } + } + + struct CustomSwitchStyle: SwitchStyle { + func makeBody(_ configuration: SwitchConfiguration) -> some View { + Switch(configuration) + .tint(Color.preferredColor(.mango5)) + } + } + + var body: some View { + VStack { + SwitchView(title: "Switch", isOn: self.$v1) + SwitchView(title: "Disabled Switch", isOn: self.$v2).disabled(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) + SwitchView(title: "Custom Style", isOn: self.$v3) + .titleStyle(CustomTitleStyle()) + .switchStyle(CustomSwitchStyle()) + } + } +} + +struct SwitchExample_Previews: PreviewProvider { + static var previews: some View { + SwitchExample() + } +} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index 94987fa3c..5e43a7d97 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -423,3 +423,14 @@ protocol _SegmentedControlPickerComponent: _OptionsComponent { /// The selected value index of the Picker var selectedIndex: Int { get } } + +/// `SwitchView`provides a Fiori style title and`Toggle`. +/// +/// ## Usage +/// ```swift +/// @State var isOn: Bool = true +/// +/// SwitchView(title: "Switch", isOn: self.$isOn) +/// ``` +// sourcery: CompositeComponent +protocol _SwitchViewComponent: _TitleComponent, _SwitchComponent {} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/SwitchViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/SwitchViewStyle.fiori.swift new file mode 100644 index 000000000..8a7d7c332 --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/SwitchViewStyle.fiori.swift @@ -0,0 +1,66 @@ +import FioriThemeManager + +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +/** + This file provides default fiori style for the component. + + 1. Uncomment fhe following code. + 2. Implement layout and style in corresponding places. + 3. Delete `.generated` from file name. + 4. Move this file to `_FioriStyles` folder under `FioriSwiftUICore`. + */ + +// Base Layout style +public struct SwitchViewBaseStyle: SwitchViewStyle { + public func makeBody(_ configuration: SwitchViewConfiguration) -> some View { + // Add default layout here + HStack { + configuration.title + Spacer() + configuration._switch + } + } +} + +// Default fiori styles +extension SwitchViewFioriStyle { + struct ContentFioriStyle: SwitchViewStyle { + func makeBody(_ configuration: SwitchViewConfiguration) -> some View { + SwitchView(configuration) + // Add default style for its content + .padding(EdgeInsets(top: 9, leading: 20, bottom: 9, trailing: 20)) + } + } + + struct TitleFioriStyle: TitleStyle { + let switchViewConfiguration: SwitchViewConfiguration + @Environment(\.isEnabled) var isEnabled + + func makeBody(_ configuration: TitleConfiguration) -> some View { + Title(configuration) + // Add default style for Title + .foregroundStyle(Color.preferredColor(self.isEnabled ? .primaryLabel : .quaternaryLabel)) + .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) + } + } + + struct SwitchFioriStyle: SwitchStyle { + let switchViewConfiguration: SwitchViewConfiguration + @Environment(\.isEnabled) var isEnabled + + func makeBody(_ configuration: SwitchConfiguration) -> some View { + Switch(configuration) + // Add default style for Switch + .tint(Color.preferredColor(.tintColor)) + .frame(width: 50, height: 30, alignment: .trailing) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color.preferredColor(configuration.isOn ? .separatorOpaque : .separator), lineWidth: 0.5) + ) + } + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchView.generated.swift new file mode 100644 index 000000000..5c0582391 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchView.generated.swift @@ -0,0 +1,78 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +/// `SwitchView`provides a Fiori style title and`Toggle`. +/// +/// ## Usage +/// ```swift +/// @State var isOn: Bool = true +/// +/// SwitchView(title: "Switch", isOn: self.$isOn) +/// ``` +public struct SwitchView { + let title: any View + @Binding var isOn: Bool + + @Environment(\.switchViewStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder title: () -> any View, + isOn: Binding) + { + self.title = Title { title() } + self._isOn = isOn + } +} + +public extension SwitchView { + init(title: AttributedString, + isOn: Binding) + { + self.init(title: { Text(title) }, isOn: isOn) + } +} + +public extension SwitchView { + init(_ configuration: SwitchViewConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: SwitchViewConfiguration, shouldApplyDefaultStyle: Bool) { + self.title = configuration.title + self._isOn = configuration.$isOn + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension SwitchView: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(title: .init(self.title), isOn: self.$isOn)).typeErased + .transformEnvironment(\.switchViewStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension SwitchView { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + SwitchView(.init(title: .init(self.title), isOn: self.$isOn)) + .shouldApplyDefaultStyle(false) + .switchViewStyle(SwitchViewFioriStyle.ContentFioriStyle()) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchViewStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchViewStyle.generated.swift new file mode 100644 index 000000000..1d96c7aab --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchViewStyle.generated.swift @@ -0,0 +1,37 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol SwitchViewStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: SwitchViewConfiguration) -> Body +} + +struct AnySwitchViewStyle: SwitchViewStyle { + let content: (SwitchViewConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (SwitchViewConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: SwitchViewConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct SwitchViewConfiguration { + public let title: Title + @Binding public var isOn: Bool + + public typealias Title = ConfigurationViewWrapper +} + +public struct SwitchViewFioriStyle: SwitchViewStyle { + public func makeBody(_ configuration: SwitchViewConfiguration) -> some View { + SwitchView(configuration) + .titleStyle(TitleFioriStyle(switchViewConfiguration: configuration)) + .switchStyle(SwitchFioriStyle(switchViewConfiguration: configuration)) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift index bbe5866f6..365a5c36a 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift @@ -3762,6 +3762,62 @@ public extension SwitchStyle where Self == SwitchFioriStyle { } } +// MARK: SwitchViewStyle + +public extension SwitchViewStyle where Self == SwitchViewBaseStyle { + static var base: SwitchViewBaseStyle { + SwitchViewBaseStyle() + } +} + +public extension SwitchViewStyle where Self == SwitchViewFioriStyle { + static var fiori: SwitchViewFioriStyle { + SwitchViewFioriStyle() + } +} + +public struct SwitchViewTitleStyle: SwitchViewStyle { + let style: any TitleStyle + + public func makeBody(_ configuration: SwitchViewConfiguration) -> some View { + SwitchView(configuration) + .titleStyle(self.style) + .typeErased + } +} + +public extension SwitchViewStyle where Self == SwitchViewTitleStyle { + static func titleStyle(_ style: some TitleStyle) -> SwitchViewTitleStyle { + SwitchViewTitleStyle(style: style) + } + + static func titleStyle(@ViewBuilder content: @escaping (TitleConfiguration) -> some View) -> SwitchViewTitleStyle { + let style = AnyTitleStyle(content) + return SwitchViewTitleStyle(style: style) + } +} + +public struct SwitchViewSwitchStyle: SwitchViewStyle { + let style: any SwitchStyle + + public func makeBody(_ configuration: SwitchViewConfiguration) -> some View { + SwitchView(configuration) + .switchStyle(self.style) + .typeErased + } +} + +public extension SwitchViewStyle where Self == SwitchViewSwitchStyle { + static func switchStyle(_ style: some SwitchStyle) -> SwitchViewSwitchStyle { + SwitchViewSwitchStyle(style: style) + } + + static func switchStyle(@ViewBuilder content: @escaping (SwitchConfiguration) -> some View) -> SwitchViewSwitchStyle { + let style = AnySwitchStyle(content) + return SwitchViewSwitchStyle(style: style) + } +} + // MARK: TagsStyle public extension TagsStyle where Self == TagsBaseStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift index 37bc7aa7a..9dc0501e5 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift @@ -1452,6 +1452,27 @@ extension EnvironmentValues { } } +// MARK: SwitchViewStyle + +struct SwitchViewStyleStackKey: EnvironmentKey { + static let defaultValue: [any SwitchViewStyle] = [] +} + +extension EnvironmentValues { + var switchViewStyle: any SwitchViewStyle { + self.switchViewStyleStack.last ?? .base.concat(.fiori) + } + + var switchViewStyleStack: [any SwitchViewStyle] { + get { + self[SwitchViewStyleStackKey.self] + } + set { + self[SwitchViewStyleStackKey.self] = newValue + } + } +} + // MARK: TagsStyle struct TagsStyleStackKey: EnvironmentKey { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift index e1e888891..389528480 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift @@ -1940,6 +1940,34 @@ public extension SwitchStyle { } } +// MARK: SwitchViewStyle + +extension ModifiedStyle: SwitchViewStyle where Style: SwitchViewStyle { + public func makeBody(_ configuration: SwitchViewConfiguration) -> some View { + SwitchView(configuration) + .switchViewStyle(self.style) + .modifier(self.modifier) + } +} + +public struct SwitchViewStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.switchViewStyle(self.style) + } +} + +public extension SwitchViewStyle { + func modifier(_ modifier: some ViewModifier) -> some SwitchViewStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some SwitchViewStyle) -> some SwitchViewStyle { + style.modifier(SwitchViewStyleModifier(style: self)) + } +} + // MARK: TagsStyle extension ModifiedStyle: TagsStyle where Style: TagsStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift index 71c4e17cb..d573b5308 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift @@ -1107,6 +1107,22 @@ extension SwitchStyle { } } +// MARK: SwitchViewStyle + +struct ResolvedSwitchViewStyle: View { + let style: Style + let configuration: SwitchViewConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension SwitchViewStyle { + func resolve(configuration: SwitchViewConfiguration) -> some View { + ResolvedSwitchViewStyle(style: self, configuration: configuration) + } +} + // MARK: TagsStyle struct ResolvedTagsStyle: View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift index 692361fe9..84a5420fe 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift @@ -133,6 +133,14 @@ extension StepperViewConfiguration { } } +// MARK: SwitchViewConfiguration + +extension SwitchViewConfiguration { + var _switch: Switch { + Switch(.init(isOn: self.$isOn), shouldApplyDefaultStyle: true) + } +} + // MARK: TextFieldFormViewConfiguration extension TextFieldFormViewConfiguration { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift index dec1aaed3..8cca73e2f 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift @@ -1176,6 +1176,23 @@ public extension View { } } +// MARK: SwitchViewStyle + +public extension View { + func switchViewStyle(_ style: some SwitchViewStyle) -> some View { + self.transformEnvironment(\.switchViewStyleStack) { stack in + stack.append(style) + } + } + + func switchViewStyle(@ViewBuilder content: @escaping (SwitchViewConfiguration) -> some View) -> some View { + self.transformEnvironment(\.switchViewStyleStack) { stack in + let style = AnySwitchViewStyle(content) + stack.append(style) + } + } +} + // MARK: TagsStyle public extension View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift index 1293778fb..98d73c594 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift @@ -506,6 +506,12 @@ extension Switch: _ViewEmptyChecking { } } +extension SwitchView: _ViewEmptyChecking { + public var isEmpty: Bool { + title.isEmpty + } +} + extension Tags: _ViewEmptyChecking { public var isEmpty: Bool { tags.isEmpty From 39c1c4be0c99c41322fd01984a51368ad220de9d Mon Sep 17 00:00:00 2001 From: Xiaoyu Liu Date: Fri, 23 Aug 2024 03:39:12 +0800 Subject: [PATCH 09/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:=20HCPSDKFIO?= =?UTF-8?q?RIUIKIT-2708]=20avatars=20enhancement=20(#773)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA: HCPSDKFIORIUIKIT-2708] avatars enhancement * feat: 🎸 [JIRA: HCPSDKFIORIUIKIT-2708] update avatars layout * fix: 🐛 default lines limit for footnote icons text --- .../ObjectItem/ObjectItemAvatarsExample.swift | 70 +++++++- .../Models/ModelDefinitions.swift | 2 + .../Views/CustomBuilder/AvatarsBuilder.swift | 40 +++-- .../CustomBuilder/FootnoteIconsBuilder.swift | 72 +++++--- .../CustomBuilder/FootnoteIconsListView.swift | 126 +++++++++++++ .../BaseComponentProtocols.swift | 6 + .../CompositeComponentProtocols.swift | 2 +- .../FootnoteIconsTextStyle.fiori.swift | 20 +++ .../_FioriStyles/ObjectItemStyle.fiori.swift | 168 +++++++++++++++++- .../FootnoteIconsText.generated.swift | 63 +++++++ .../FootnoteIconsTextStyle.generated.swift | 28 +++ .../ObjectItem/ObjectItem.generated.swift | 11 +- .../ObjectItemStyle.generated.swift | 3 + ...entStyleProtocol+Extension.generated.swift | 35 ++++ .../EnvironmentVariables.generated.swift | 21 +++ .../ModifiedStyle.generated.swift | 28 +++ .../ResolvedStyle.generated.swift | 16 ++ .../View+Extension_.generated.swift | 17 ++ ...iewEmptyChecking+Extension.generated.swift | 7 + 19 files changed, 680 insertions(+), 55 deletions(-) create mode 100644 Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/FootnoteIconsTextStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsText.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsTextStyle.generated.swift diff --git a/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/ObjectItemAvatarsExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/ObjectItemAvatarsExample.swift index b90ddf72c..3bd9ea55a 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/ObjectItemAvatarsExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/ObjectItemAvatarsExample.swift @@ -11,7 +11,7 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { } func numberOfRowsInSection(_ section: Int) -> Int { - 4 + self.isNewObjectItem ? 8 : 4 } func titleForHeaderInSection(_ section: Int) -> String { @@ -168,7 +168,7 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { Image(systemName: "person") .resizable() Text("XY") - .frame(width: 40, height: 40) + .frame(width: 30, height: 30) .background(Color.red) .foregroundColor(Color.white) }, footnoteIcons: { @@ -191,6 +191,72 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { .footnoteIconsSize(CGSize(width: 20, height: 20)) .isFootnoteIconsCircular(false) return AnyView(oi) + case (0, 4): + let oi = ObjectItem { + Text("Title: This is a case for for long text with icons") + } subtitle: { + Text("Subtitle: this is a subtitle") + } footnoteIcons: { + Color.random + Color.random + Color.random + Color.random + Color.random + Color.random + } footnoteIconsText: { + Text("This is a very very very very very very very very very very very very very very very very very long text layout with footnote icons") + } + return AnyView(oi) + case (0, 5): + let oi = ObjectItem { + Text("This is a case for for short text with icons") + } subtitle: { + Text("Subtitle: this is a subtitle") + } footnoteIcons: { + Color.random + Color.random + Color.random + Color.random + Color.random + Color.random + } footnoteIconsText: { + Text("This is a short one.") + } + return AnyView(oi) + case (0, 6): + let oi = ObjectItem { + Text("This is a case for for long leading text with icons") + } subtitle: { + Text("Subtitle: this is a subtitle") + } footnoteIcons: { + Color.random + Color.random + Color.random + Color.random + Color.random + Color.random + } footnoteIconsText: { + Text("This is a very very very very very very very very very very very very very very very very very long text layout with footnote icons") + }.footnoteIconsTextPosition(.leading) + return AnyView(oi) + case (0, 7): + let oi = ObjectItem { + Text("This is a case for for short leading text with icons") + } subtitle: { + Text("Subtitle: this is a subtitle") + } footnoteIcons: { + Color.random + Color.random + Color.random + Color.random + Color.random + Color.random + } footnoteIconsText: { + Text("This is text with custom style.") + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(Color.random) + }.footnoteIconsTextPosition(.leading) + return AnyView(oi) default: return AnyView(_ObjectItem(title: "Lorem ipseum dolor")) } diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index e9e921007..e4584351a 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -19,6 +19,8 @@ public protocol AvatarStackModel: AvatarsComponent {} // sourcery: add_env_props = "footnoteIconsSpacing" // sourcery: add_env_props = "isFootnoteIconsCircular" // sourcery: add_env_props = "footnoteIconsMaxCount" +// sourcery: add_env_props = "footnoteIconsTextPosition" +// sourcery: add_env_props = "footnoteIconsText" public protocol FootnoteIconStackModel: FootnoteIconsComponent {} // sourcery: add_env_props = "horizontalSizeClass" diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarsBuilder.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarsBuilder.swift index a5289b286..b70241a86 100644 --- a/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarsBuilder.swift +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarsBuilder.swift @@ -14,29 +14,37 @@ public protocol AvatarList: View, _ViewEmptyChecking { public extension AvatarList { /// :nodoc: @ViewBuilder func buildAvatar(_ avatar: V) -> some View { - Group { - if isCircular { - avatar - .frame(width: size.width, height: size.height) - .clipShape(Capsule()) - .overlay { - Capsule() - .inset(by: borderWidth / 2.0) - .stroke(borderColor, lineWidth: borderWidth) - } - } else { - avatar - .frame(width: size.width, height: size.height) - .border(borderColor, width: borderWidth) - } + if isCircular { + avatar + .frame(width: size.width, height: size.height) + .clipShape(Capsule()) + .overlay { + Capsule() + .inset(by: borderWidth / 2.0) + .stroke(borderColor, lineWidth: borderWidth) + } + } else { + avatar + .frame(width: size.width, height: size.height) + .border(borderColor, width: borderWidth) } } + // This condition check if for handle recursive builder issue. + private func checkIsNestingAvatars() -> Bool { + let typeString = String(describing: V.self) + return typeString.contains("SingleAvatar= 2 { ZStack(alignment: .topLeading) { self.buildAvatar(view(at: 0)) diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsBuilder.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsBuilder.swift index de7ab8e6d..a42f6b483 100644 --- a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsBuilder.swift +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsBuilder.swift @@ -15,30 +15,7 @@ public protocol FootnoteIconList: View, _ViewEmptyChecking { public extension FootnoteIconList { /// :nodoc: var body: some View { - HStack(spacing: spacing) { - let itemsCount = maxCount <= 0 ? count : min(count, maxCount) - ForEach(0 ..< itemsCount, id: \.self) { index in - view(at: index) - .frame(width: size.width, height: size.height) - .ifApply(isCircular) { - $0.clipShape(Capsule()) - } - .overlay { - Group { - if isCircular { - Capsule() - .inset(by: 0.33 / 2.0) - .stroke(Color.preferredColor(.separator), lineWidth: 0.33) - } else { - Rectangle() - .inset(by: 0.33 / 2.0) - .stroke(Color.preferredColor(.separator), lineWidth: 0.33) - } - } - } - } - } - .clipped() + FootnoteIconsListView(icons: self) } } @@ -150,6 +127,7 @@ public struct PairFootnoteIcon: FootnoteI @Environment(\.isFootnoteIconsCircular) var isFootnoteIconsCircular @Environment(\.footnoteIconsSpacing) var footnoteIconsSpacing @Environment(\.footnoteIconsSize) var footnoteIconsSize + public var maxCount: Int { self.footnoteIconsMaxCount } @@ -333,7 +311,26 @@ public extension EnvironmentValues { } } +struct FootnoteIconsTextPosition: EnvironmentKey { + static let defaultValue: TextPosition = .trailing +} + +public extension EnvironmentValues { + /// Text position for footnote icons. + var footnoteIconsTextPosition: TextPosition { + get { self[FootnoteIconsTextPosition.self] } + set { self[FootnoteIconsTextPosition.self] = newValue } + } +} + public extension View { + /// Specific the position of the text that drawn for footnote icons. Default value is `.trailing`. + /// - Parameter position: Text position. + /// - Returns: A view that footnote icons text with specific position. + func footnoteIconsTextPosition(_ position: TextPosition) -> some View { + environment(\.footnoteIconsTextPosition, position) + } + /// Maximum number of the footnote icons. Default value is 0. When the count is less or equal to 0, means the number is unlimited. /// ```swift /// _ObjectItem(title: "Object Item", @@ -359,7 +356,7 @@ public extension View { /// .isFootnoteIconsCircular(false) /// ``` /// - Parameter isCircular: Boolean denoting whether the footnote icons are circular. - /// - Returns: A view that footnote icons are cirlcular or not. + /// - Returns: A view that footnote icons are circular or not. func isFootnoteIconsCircular(_ isCircular: Bool) -> some View { environment(\.isFootnoteIconsCircular, isCircular) } @@ -394,3 +391,28 @@ public extension View { environment(\.footnoteIconsSize, size) } } + +/// Text position for icons. +public enum TextPosition { + /// Top position for text. + case top + /// Bottom position for text. + case bottom + /// Leading position for text. + case leading + /// Trailing position for text. + case trailing + + var alignment: Alignment { + switch self { + case .top: + return .top + case .bottom: + return .bottom + case .leading: + return .leading + case .trailing: + return .trailing + } + } +} diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift new file mode 100644 index 000000000..9b771b4e2 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift @@ -0,0 +1,126 @@ +import SwiftUI + +struct FootnoteIconsListView: View { + let icons: T + + var count: Int { + self.icons.count + } + + var maxCount: Int { + self.icons.maxCount + } + + var size: CGSize { + self.icons.size + } + + var isCircular: Bool { + self.icons.isCircular + } + + var spacing: CGFloat { + self.icons.spacing + } + + // This condition check if for handle recursive builder issue. + func checkIsNestingIcons() -> Bool { + let typeString = String(describing: T.self) + return typeString.contains("SingleFootnoteIcon some View { + FootnoteIconsHStack(spacing: self.spacing) { + let itemsCount = self.maxCount <= 0 ? self.count : min(self.count, self.maxCount) + ForEach(0 ..< itemsCount, id: \.self) { index in + self.icons.view(at: index) + .frame(width: self.size.width, height: self.size.height) + .ifApply(self.isCircular) { + $0.clipShape(Capsule()) + } + .overlay { + Group { + if self.isCircular { + Capsule() + .inset(by: 0.33 / 2.0) + .stroke(Color.preferredColor(.separator), lineWidth: 0.33) + } else { + Rectangle() + .inset(by: 0.33 / 2.0) + .stroke(Color.preferredColor(.separator), lineWidth: 0.33) + } + } + } + } + } + } +} + +struct FootnoteIconsHStack: Layout { + struct CacheData { + var width: CGFloat + var count: Int + var size: CGSize + } + + let spacing: CGFloat + + func makeCache(subviews: Subviews) -> CacheData { + CacheData(width: 0, count: 0, size: .zero) + } + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize { + self.calculateSizeAndCount(proposal: proposal, subviews: subviews, cache: &cache) + return cache.size + } + + func calculateSizeAndCount(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { + guard let contentWidth = proposal.width, cache.width != contentWidth else { + return + } + cache.width = contentWidth + + var totalWidth: CGFloat = 0 + var maxHeight: CGFloat = 0 + + for (index, subview) in subviews.enumerated() { + let subviewSize = subview.sizeThatFits(proposal) + maxHeight = max(maxHeight, subviewSize.height) + if subviewSize.width + totalWidth <= contentWidth { + totalWidth += subviewSize.width + totalWidth += self.spacing + } else { + cache.count = index + cache.size = CGSize(width: totalWidth, height: maxHeight) + break + } + } + totalWidth -= self.spacing + cache.count = subviews.count + cache.size = CGSize(width: totalWidth, height: maxHeight) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { + var xOffset: CGFloat = bounds.minX + self.calculateSizeAndCount(proposal: proposal, subviews: subviews, cache: &cache) + for (index, subview) in subviews.enumerated() { + if index < cache.count { + let subviewSize = subview.sizeThatFits(proposal) + subview.place(at: CGPoint(x: xOffset, y: bounds.minY), + proposal: ProposedViewSize(CGSize(width: subviewSize.width, height: subviewSize.height))) + xOffset += (subviewSize.width + self.spacing) + } else { + break + } + } + } +} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift index 6a7610ff3..bb422e118 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift @@ -89,6 +89,12 @@ protocol _FootnoteIconsComponent { var footnoteIcons: [TextOrIcon] { get } } +// sourcery: BaseComponent +protocol _FootnoteIconsTextComponent { + // sourcery: @ViewBuilder + var footnoteIconsText: AttributedString? { get } +} + // sourcery: BaseComponent protocol _AvatarsComponent { // sourcery: resultBuilder.name = @AvatarsBuilder, resultBuilder.backingComponent = AvatarStack diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index 5e43a7d97..5e9e9df23 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -3,7 +3,7 @@ import SwiftUI /// A view that displays information of an object. // sourcery: CompositeComponent -protocol _ObjectItemComponent: _TitleComponent, _SubtitleComponent, _FootnoteComponent, _DescriptionComponent, _StatusComponent, _SubstatusComponent, _DetailImageComponent, _IconsComponent, _AvatarsComponent, _FootnoteIconsComponent, _TagsComponent, _ActionComponent {} +protocol _ObjectItemComponent: _TitleComponent, _SubtitleComponent, _FootnoteComponent, _DescriptionComponent, _StatusComponent, _SubstatusComponent, _DetailImageComponent, _IconsComponent, _AvatarsComponent, _FootnoteIconsComponent, _FootnoteIconsTextComponent, _TagsComponent, _ActionComponent {} // sourcery: CompositeComponent, InternalComponent protocol _DemoViewComponent: _TitleComponent, _SubtitleComponent, _StatusComponent, _ActionComponent, _SwitchComponent {} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/FootnoteIconsTextStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/FootnoteIconsTextStyle.fiori.swift new file mode 100644 index 000000000..6ffd2a405 --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/FootnoteIconsTextStyle.fiori.swift @@ -0,0 +1,20 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct FootnoteIconsTextBaseStyle: FootnoteIconsTextStyle { + @ViewBuilder + public func makeBody(_ configuration: FootnoteIconsTextConfiguration) -> some View { + // Add default layout here + configuration.footnoteIconsText + } +} + +// Default fiori styles +public struct FootnoteIconsTextFioriStyle: FootnoteIconsTextStyle { + @ViewBuilder + public func makeBody(_ configuration: FootnoteIconsTextConfiguration) -> some View { + FootnoteIconsText(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift index d857e2744..796cff2b3 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift @@ -2,6 +2,8 @@ import FioriThemeManager import Foundation import SwiftUI +// swiftlint:disable file_length + /** This file provides default fiori style for the component. @@ -16,7 +18,8 @@ public struct ObjectItemBaseStyle: ObjectItemStyle { @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.splitPercent) var splitPercent @Environment(\.dynamicTypeSize) var dynamicTypeSize - + @Environment(\.footnoteIconsTextPosition) var footnoteIconsTextPosition + @State var mainViewSize: CGSize = .zero public func makeBody(_ configuration: ObjectItemConfiguration) -> some View { @@ -157,7 +160,7 @@ extension ObjectItemBaseStyle { context.configuration.subtitle context.configuration.footnote context.configuration.tags - context.configuration.footnoteIcons + self.footnoteIconsView(context) } Spacer(minLength: 16) @@ -188,7 +191,7 @@ extension ObjectItemBaseStyle { context.configuration.subtitle context.configuration.footnote context.configuration.tags - context.configuration.footnoteIcons + self.footnoteIconsView(context) } Spacer(minLength: 16) @@ -274,7 +277,7 @@ extension ObjectItemBaseStyle { context.configuration.subtitle context.configuration.footnote context.configuration.tags - context.configuration.footnoteIcons + self.footnoteIconsView(context) } Spacer(minLength: 0) } @@ -302,7 +305,7 @@ extension ObjectItemBaseStyle { context.configuration.subtitle context.configuration.footnote context.configuration.tags - context.configuration.footnoteIcons + self.footnoteIconsView(context) } Spacer(minLength: 8) @@ -383,7 +386,7 @@ extension ObjectItemBaseStyle { context.configuration.subtitle context.configuration.footnote context.configuration.tags - context.configuration.footnoteIcons + self.footnoteIconsView(context) } Spacer(minLength: 16) } @@ -491,6 +494,14 @@ extension ObjectItemBaseStyle { return 3 } } + + @ViewBuilder + func footnoteIconsView(_ context: Context) -> some View { + FootnoteIconsAndTextLayout(textPosition: self.footnoteIconsTextPosition) { + context.configuration.footnoteIconsText + context.configuration.footnoteIcons + } + } } // Default fiori styles @@ -600,6 +611,17 @@ extension ObjectItemFioriStyle { // Add default style here } } + + struct FootnoteIconsTextFioriStyle: FootnoteIconsTextStyle { + let objectItemConfiguration: ObjectItemConfiguration + + func makeBody(_ configuration: FootnoteIconsTextConfiguration) -> some View { + FootnoteIconsText(configuration) + .font(.fiori(forTextStyle: .subheadline)) + .foregroundStyle(Color.preferredColor(.secondaryLabel)) + .lineLimit(1) + } + } struct TagsFioriStyle: TagsStyle { let objectItemConfiguration: ObjectItemConfiguration @@ -651,7 +673,7 @@ public struct ObjectItemBorderedAction: ActionStyle { } } -#Preview(body: { +#Preview { List { ObjectItem(title: { Text("Title") @@ -671,10 +693,140 @@ public struct ObjectItemBorderedAction: ActionStyle { Text("1") Circle().fill(Color.preferredColor(.tintColor)).frame(width: 14, height: 14) Image(systemName: "paperclip").font(.system(size: 14)) + }, footnoteIcons: { + Color.red + Color.green + Color.blue + Color.red + Color.green + Color.blue + Color.red + Color.green + Color.blue + }, footnoteIconsText: { + Text("Footnote icons text.") }) .titleStyle { config in config.title .foregroundStyle(.blue) // take effect } } -}) +} + +struct FootnoteIconsAndTextLayout: Layout { + let textPosition: TextPosition + let margin = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0) + let textAndIconsSpacing: CGFloat = 6 + let textMinimumWidth: CGFloat = 60 + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize { + guard let containerWidth = proposal.width, containerWidth > 0 else { + return .zero + } + if subviews.count == 2, + let textView = subviews.first, + let iconsView = subviews.last, + textView.sizeThatFits(.infinity) != .zero, + iconsView.sizeThatFits(.infinity) != .zero + { + switch self.textPosition { + case .top, .bottom: + let availableWidth = containerWidth - self.margin.leading - self.margin.trailing + let textViewSize = textView.sizeThatFits(ProposedViewSize(width: availableWidth, + height: .infinity)) + let iconsSize = iconsView.sizeThatFits(ProposedViewSize(width: availableWidth, + height: .infinity)) + let maxHeight = textViewSize.height + iconsSize.height + self.textAndIconsSpacing + self.margin.top + self.margin.bottom + return CGSize(width: containerWidth, + height: maxHeight) + case .leading, .trailing: + let textActualSize = textView.sizeThatFits(.unspecified) + let iconsAvailableWidth = containerWidth - min(textActualSize.width, self.textMinimumWidth) - self.margin.leading - self.margin.trailing + let iconsSize = iconsView.sizeThatFits(ProposedViewSize(width: iconsAvailableWidth, + height: .infinity)) + let textWidth = containerWidth - iconsSize.width - self.margin.leading - self.margin.trailing - self.textAndIconsSpacing + let textSize = textView.sizeThatFits(ProposedViewSize(width: textWidth, height: .infinity)) + let maxHeight = max(iconsSize.height, textSize.height) + self.margin.top + self.margin.bottom + let size = CGSize(width: containerWidth, height: maxHeight) + return size + } + } else { + var maxHeight: CGFloat = 0 + let contentWidth = containerWidth - self.margin.leading - self.margin.trailing + for subview in subviews { + let height = subview.sizeThatFits(ProposedViewSize(width: contentWidth, height: .infinity)).height + maxHeight = max(maxHeight, height) + } + return CGSize(width: containerWidth, height: maxHeight + self.margin.top + self.margin.bottom) + } + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) { + guard let containerWidth = proposal.width, containerWidth > 0 else { + return + } + if subviews.count == 2, + let textView = subviews.first, + let iconsView = subviews.last, + textView.sizeThatFits(.infinity) != .zero, + iconsView.sizeThatFits(.infinity) != .zero + { + switch self.textPosition { + case .top: + textView.place(at: bounds.origin, proposal: .unspecified) + let nextOrigin = CGPoint(x: bounds.minX, + y: bounds.minY + textView.sizeThatFits(.unspecified).height + self.textAndIconsSpacing) + iconsView.place(at: nextOrigin, proposal: .unspecified) + case .bottom: + iconsView.place(at: bounds.origin, proposal: .unspecified) + let nextOrigin = CGPoint(x: bounds.minX, + y: bounds.minY + textView.sizeThatFits(.unspecified).height + self.textAndIconsSpacing) + textView.place(at: nextOrigin, proposal: .unspecified) + case .leading: + let textActualSize = textView.sizeThatFits(.unspecified) + let iconsAvailableWidth = containerWidth - min(textActualSize.width, self.textMinimumWidth) - self.textAndIconsSpacing - self.margin.leading - self.margin.trailing + let iconsSize = iconsView.sizeThatFits(ProposedViewSize(width: iconsAvailableWidth, height: .infinity)) + let textMaxWidth = containerWidth - iconsSize.width - self.textAndIconsSpacing - self.margin.leading - self.margin.trailing + let textSize: CGSize + if textActualSize.width < textMaxWidth { + textSize = textActualSize + } else { + textSize = textView.sizeThatFits(ProposedViewSize(CGSize(width: textMaxWidth, height: .infinity))) + } + let maxHeight = max(textSize.height, iconsSize.height) + let iconsY = bounds.minY + (maxHeight - iconsSize.height) / 2 + self.margin.top + let textY = bounds.minY + (maxHeight - textSize.height) / 2 + self.margin.top + + textView.place(at: CGPoint(x: bounds.minX + self.margin.leading, y: textY), proposal: ProposedViewSize(textSize)) + let iconsX = bounds.origin.x + textSize.width + self.textAndIconsSpacing + self.margin.leading + iconsView.place(at: CGPoint(x: iconsX, y: iconsY), + proposal: ProposedViewSize(iconsSize)) + case .trailing: + let textActualSize = textView.sizeThatFits(.unspecified) + let iconsAvailableWidth = containerWidth - min(textActualSize.width, self.textMinimumWidth) - self.textAndIconsSpacing - self.margin.leading - self.margin.trailing + let iconsSize = iconsView.sizeThatFits(ProposedViewSize(width: iconsAvailableWidth, height: .infinity)) + let textMaxWidth = containerWidth - iconsSize.width - self.textAndIconsSpacing - self.margin.leading - self.margin.trailing + + let textX = bounds.minX + iconsSize.width + self.textAndIconsSpacing + self.margin.leading + let textSize: CGSize + if textActualSize.width < textMaxWidth { + textSize = textActualSize + } else { + textSize = textView.sizeThatFits(ProposedViewSize(CGSize(width: textMaxWidth, height: .infinity))) + } + let maxHeight = max(textSize.height, iconsSize.height) + let iconsY = bounds.minY + (maxHeight - iconsSize.height) / 2 + self.margin.top + let textY = bounds.minY + (maxHeight - textSize.height) / 2 + self.margin.top + iconsView.place(at: CGPoint(x: bounds.minX + self.margin.leading, y: iconsY), + proposal: ProposedViewSize(iconsSize)) + textView.place(at: CGPoint(x: textX, y: textY), proposal: ProposedViewSize(textSize)) + } + } else { + for subview in subviews { + subview.place(at: CGPoint(x: bounds.origin.x + self.margin.leading, + y: bounds.origin.y + self.margin.top), + proposal: ProposedViewSize(CGSize(width: containerWidth - self.margin.leading - self.margin.trailing, height: .infinity))) + } + } + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsText.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsText.generated.swift new file mode 100644 index 000000000..4fc6b540a --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsText.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct FootnoteIconsText { + let footnoteIconsText: any View + + @Environment(\.footnoteIconsTextStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder footnoteIconsText: () -> any View = { EmptyView() }) { + self.footnoteIconsText = footnoteIconsText() + } +} + +public extension FootnoteIconsText { + init(footnoteIconsText: AttributedString? = nil) { + self.init(footnoteIconsText: { OptionalText(footnoteIconsText) }) + } +} + +public extension FootnoteIconsText { + init(_ configuration: FootnoteIconsTextConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: FootnoteIconsTextConfiguration, shouldApplyDefaultStyle: Bool) { + self.footnoteIconsText = configuration.footnoteIconsText + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension FootnoteIconsText: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(footnoteIconsText: .init(self.footnoteIconsText))).typeErased + .transformEnvironment(\.footnoteIconsTextStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension FootnoteIconsText { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + FootnoteIconsText(footnoteIconsText: { self.footnoteIconsText }) + .shouldApplyDefaultStyle(false) + .footnoteIconsTextStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsTextStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsTextStyle.generated.swift new file mode 100644 index 000000000..6079ae4ae --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsTextStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol FootnoteIconsTextStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: FootnoteIconsTextConfiguration) -> Body +} + +struct AnyFootnoteIconsTextStyle: FootnoteIconsTextStyle { + let content: (FootnoteIconsTextConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (FootnoteIconsTextConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: FootnoteIconsTextConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct FootnoteIconsTextConfiguration { + public let footnoteIconsText: FootnoteIconsText + + public typealias FootnoteIconsText = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift index 5b3e590b7..1b319d574 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift @@ -15,6 +15,7 @@ public struct ObjectItem { let icons: any View let avatars: any View let footnoteIcons: any View + let footnoteIconsText: any View let tags: any View let action: any View @@ -32,6 +33,7 @@ public struct ObjectItem { @IconBuilder icons: () -> any View = { EmptyView() }, @AvatarsBuilder avatars: () -> any View = { EmptyView() }, @FootnoteIconsBuilder footnoteIcons: () -> any View = { EmptyView() }, + @ViewBuilder footnoteIconsText: () -> any View = { EmptyView() }, @TagBuilder tags: () -> any View = { EmptyView() }, @ViewBuilder action: () -> any View = { EmptyView() }) { @@ -45,6 +47,7 @@ public struct ObjectItem { self.icons = Icons { icons() } self.avatars = Avatars { avatars() } self.footnoteIcons = FootnoteIcons { footnoteIcons() } + self.footnoteIconsText = FootnoteIconsText { footnoteIconsText() } self.tags = Tags { tags() } self.action = Action { action() } } @@ -61,10 +64,11 @@ public extension ObjectItem { icons: [TextOrIcon] = [], avatars: [TextOrIcon] = [], footnoteIcons: [TextOrIcon] = [], + footnoteIconsText: AttributedString? = nil, tags: [AttributedString] = [], action: FioriButton? = nil) { - self.init(title: { Text(title) }, subtitle: { OptionalText(subtitle) }, footnote: { OptionalText(footnote) }, description: { OptionalText(description) }, status: { TextOrIconView(status) }, substatus: { TextOrIconView(substatus) }, detailImage: { detailImage }, icons: { IconStack(icons) }, avatars: { AvatarStack(avatars) }, footnoteIcons: { FootnoteIconStack(footnoteIcons) }, tags: { TagStack(tags) }, action: { action }) + self.init(title: { Text(title) }, subtitle: { OptionalText(subtitle) }, footnote: { OptionalText(footnote) }, description: { OptionalText(description) }, status: { TextOrIconView(status) }, substatus: { TextOrIconView(substatus) }, detailImage: { detailImage }, icons: { IconStack(icons) }, avatars: { AvatarStack(avatars) }, footnoteIcons: { FootnoteIconStack(footnoteIcons) }, footnoteIconsText: { OptionalText(footnoteIconsText) }, tags: { TagStack(tags) }, action: { action }) } } @@ -84,6 +88,7 @@ public extension ObjectItem { self.icons = configuration.icons self.avatars = configuration.avatars self.footnoteIcons = configuration.footnoteIcons + self.footnoteIconsText = configuration.footnoteIconsText self.tags = configuration.tags self.action = configuration.action self._shouldApplyDefaultStyle = shouldApplyDefaultStyle @@ -95,7 +100,7 @@ extension ObjectItem: View { if self._shouldApplyDefaultStyle { self.defaultStyle() } else { - self.style.resolve(configuration: .init(title: .init(self.title), subtitle: .init(self.subtitle), footnote: .init(self.footnote), description: .init(self.description), status: .init(self.status), substatus: .init(self.substatus), detailImage: .init(self.detailImage), icons: .init(self.icons), avatars: .init(self.avatars), footnoteIcons: .init(self.footnoteIcons), tags: .init(self.tags), action: .init(self.action))).typeErased + self.style.resolve(configuration: .init(title: .init(self.title), subtitle: .init(self.subtitle), footnote: .init(self.footnote), description: .init(self.description), status: .init(self.status), substatus: .init(self.substatus), detailImage: .init(self.detailImage), icons: .init(self.icons), avatars: .init(self.avatars), footnoteIcons: .init(self.footnoteIcons), footnoteIconsText: .init(self.footnoteIconsText), tags: .init(self.tags), action: .init(self.action))).typeErased .transformEnvironment(\.objectItemStyleStack) { stack in if !stack.isEmpty { stack.removeLast() @@ -113,7 +118,7 @@ private extension ObjectItem { } func defaultStyle() -> some View { - ObjectItem(.init(title: .init(self.title), subtitle: .init(self.subtitle), footnote: .init(self.footnote), description: .init(self.description), status: .init(self.status), substatus: .init(self.substatus), detailImage: .init(self.detailImage), icons: .init(self.icons), avatars: .init(self.avatars), footnoteIcons: .init(self.footnoteIcons), tags: .init(self.tags), action: .init(self.action))) + ObjectItem(.init(title: .init(self.title), subtitle: .init(self.subtitle), footnote: .init(self.footnote), description: .init(self.description), status: .init(self.status), substatus: .init(self.substatus), detailImage: .init(self.detailImage), icons: .init(self.icons), avatars: .init(self.avatars), footnoteIcons: .init(self.footnoteIcons), footnoteIconsText: .init(self.footnoteIconsText), tags: .init(self.tags), action: .init(self.action))) .shouldApplyDefaultStyle(false) .objectItemStyle(ObjectItemFioriStyle.ContentFioriStyle()) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItemStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItemStyle.generated.swift index e23e25064..03804f1ca 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItemStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItemStyle.generated.swift @@ -32,6 +32,7 @@ public struct ObjectItemConfiguration { public let icons: Icons public let avatars: Avatars public let footnoteIcons: FootnoteIcons + public let footnoteIconsText: FootnoteIconsText public let tags: Tags public let action: Action @@ -45,6 +46,7 @@ public struct ObjectItemConfiguration { public typealias Icons = ConfigurationViewWrapper public typealias Avatars = ConfigurationViewWrapper public typealias FootnoteIcons = ConfigurationViewWrapper + public typealias FootnoteIconsText = ConfigurationViewWrapper public typealias Tags = ConfigurationViewWrapper public typealias Action = ConfigurationViewWrapper } @@ -62,6 +64,7 @@ public struct ObjectItemFioriStyle: ObjectItemStyle { .iconsStyle(IconsFioriStyle(objectItemConfiguration: configuration)) .avatarsStyle(AvatarsFioriStyle(objectItemConfiguration: configuration)) .footnoteIconsStyle(FootnoteIconsFioriStyle(objectItemConfiguration: configuration)) + .footnoteIconsTextStyle(FootnoteIconsTextFioriStyle(objectItemConfiguration: configuration)) .tagsStyle(TagsFioriStyle(objectItemConfiguration: configuration)) .actionStyle(ActionFioriStyle(objectItemConfiguration: configuration)) } diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift index 365a5c36a..628f967ee 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift @@ -1599,6 +1599,20 @@ public extension FootnoteIconsStyle where Self == FootnoteIconsFioriStyle { } } +// MARK: FootnoteIconsTextStyle + +public extension FootnoteIconsTextStyle where Self == FootnoteIconsTextBaseStyle { + static var base: FootnoteIconsTextBaseStyle { + FootnoteIconsTextBaseStyle() + } +} + +public extension FootnoteIconsTextStyle where Self == FootnoteIconsTextFioriStyle { + static var fiori: FootnoteIconsTextFioriStyle { + FootnoteIconsTextFioriStyle() + } +} + // MARK: FormViewStyle public extension FormViewStyle where Self == FormViewBaseStyle { @@ -2768,6 +2782,27 @@ public extension ObjectItemStyle where Self == ObjectItemFootnoteIconsStyle { } } +public struct ObjectItemFootnoteIconsTextStyle: ObjectItemStyle { + let style: any FootnoteIconsTextStyle + + public func makeBody(_ configuration: ObjectItemConfiguration) -> some View { + ObjectItem(configuration) + .footnoteIconsTextStyle(self.style) + .typeErased + } +} + +public extension ObjectItemStyle where Self == ObjectItemFootnoteIconsTextStyle { + static func footnoteIconsTextStyle(_ style: some FootnoteIconsTextStyle) -> ObjectItemFootnoteIconsTextStyle { + ObjectItemFootnoteIconsTextStyle(style: style) + } + + static func footnoteIconsTextStyle(@ViewBuilder content: @escaping (FootnoteIconsTextConfiguration) -> some View) -> ObjectItemFootnoteIconsTextStyle { + let style = AnyFootnoteIconsTextStyle(content) + return ObjectItemFootnoteIconsTextStyle(style: style) + } +} + public struct ObjectItemTagsStyle: ObjectItemStyle { let style: any TagsStyle diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift index 9dc0501e5..93458187f 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift @@ -444,6 +444,27 @@ extension EnvironmentValues { } } +// MARK: FootnoteIconsTextStyle + +struct FootnoteIconsTextStyleStackKey: EnvironmentKey { + static let defaultValue: [any FootnoteIconsTextStyle] = [] +} + +extension EnvironmentValues { + var footnoteIconsTextStyle: any FootnoteIconsTextStyle { + self.footnoteIconsTextStyleStack.last ?? .base + } + + var footnoteIconsTextStyleStack: [any FootnoteIconsTextStyle] { + get { + self[FootnoteIconsTextStyleStackKey.self] + } + set { + self[FootnoteIconsTextStyleStackKey.self] = newValue + } + } +} + // MARK: FormViewStyle struct FormViewStyleStackKey: EnvironmentKey { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift index 389528480..6e3e6cf6e 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift @@ -596,6 +596,34 @@ public extension FootnoteIconsStyle { } } +// MARK: FootnoteIconsTextStyle + +extension ModifiedStyle: FootnoteIconsTextStyle where Style: FootnoteIconsTextStyle { + public func makeBody(_ configuration: FootnoteIconsTextConfiguration) -> some View { + FootnoteIconsText(configuration) + .footnoteIconsTextStyle(self.style) + .modifier(self.modifier) + } +} + +public struct FootnoteIconsTextStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.footnoteIconsTextStyle(self.style) + } +} + +public extension FootnoteIconsTextStyle { + func modifier(_ modifier: some ViewModifier) -> some FootnoteIconsTextStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some FootnoteIconsTextStyle) -> some FootnoteIconsTextStyle { + style.modifier(FootnoteIconsTextStyleModifier(style: self)) + } +} + // MARK: FormViewStyle extension ModifiedStyle: FormViewStyle where Style: FormViewStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift index d573b5308..ce2a30855 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift @@ -339,6 +339,22 @@ extension FootnoteIconsStyle { } } +// MARK: FootnoteIconsTextStyle + +struct ResolvedFootnoteIconsTextStyle: View { + let style: Style + let configuration: FootnoteIconsTextConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension FootnoteIconsTextStyle { + func resolve(configuration: FootnoteIconsTextConfiguration) -> some View { + ResolvedFootnoteIconsTextStyle(style: self, configuration: configuration) + } +} + // MARK: FormViewStyle struct ResolvedFormViewStyle: View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift index 8cca73e2f..be8fd660a 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift @@ -360,6 +360,23 @@ public extension View { } } +// MARK: FootnoteIconsTextStyle + +public extension View { + func footnoteIconsTextStyle(_ style: some FootnoteIconsTextStyle) -> some View { + self.transformEnvironment(\.footnoteIconsTextStyleStack) { stack in + stack.append(style) + } + } + + func footnoteIconsTextStyle(@ViewBuilder content: @escaping (FootnoteIconsTextConfiguration) -> some View) -> some View { + self.transformEnvironment(\.footnoteIconsTextStyleStack) { stack in + let style = AnyFootnoteIconsTextStyle(content) + stack.append(style) + } + } +} + // MARK: FormViewStyle public extension View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift index 98d73c594..b9c915603 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift @@ -177,6 +177,12 @@ extension FootnoteIcons: _ViewEmptyChecking { } } +extension FootnoteIconsText: _ViewEmptyChecking { + public var isEmpty: Bool { + footnoteIconsText.isEmpty + } +} + extension FormView: _ViewEmptyChecking { public var isEmpty: Bool { false @@ -348,6 +354,7 @@ extension ObjectItem: _ViewEmptyChecking { icons.isEmpty && avatars.isEmpty && footnoteIcons.isEmpty && + footnoteIconsText.isEmpty && tags.isEmpty && action.isEmpty } From 2a533e068421be08fd81750871218086bfb7f8a2 Mon Sep 17 00:00:00 2001 From: shengxu7 Date: Mon, 26 Aug 2024 21:19:42 -0700 Subject: [PATCH 10/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:=200]=20add?= =?UTF-8?q?=20isSameHeight=20in=20Carousel=20and=20update=20some=20cards?= =?UTF-8?q?=20based=20on=20reviews=20(#783)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add isSameHeight in Carousel and update some cards based on reviews * docs: add a doc for intrinsicHeightCard style --- .../Card/MobileCardExample.swift | 45 ++-- Sources/FioriSwiftUICore/Views/Carousel.swift | 35 ++- .../_FioriStyles/CardHeaderStyle.fiori.swift | 2 +- .../_FioriStyles/CardStyle.fiori.swift | 210 ++++++++++++------ 4 files changed, 187 insertions(+), 105 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Card/MobileCardExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Card/MobileCardExample.swift index a664cdcce..56ec00575 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/Card/MobileCardExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/Card/MobileCardExample.swift @@ -13,6 +13,7 @@ struct MobileCardExample: View { ForEach(0 ..< CardTests.cardSamples.count, id: \.self) { i in CardTests.cardSamples[i] } + .listRowBackground(Color.preferredColor(.primaryGroupedBackground)) } .cardStyle(.card) .listStyle(.plain) @@ -26,6 +27,7 @@ struct MobileCardExample: View { ForEach(0 ..< CardTests.cardFooterSamples.count, id: \.self) { i in CardTests.cardFooterSamples[i] } + .listRowBackground(Color.preferredColor(.primaryGroupedBackground)) } .cardStyle(.card) .listStyle(.plain) @@ -77,6 +79,7 @@ struct MasonryTestView: View { .cardStyle(.card) } .padding() + .background(Color.preferredColor(.primaryGroupedBackground)) } } @@ -90,12 +93,12 @@ struct CarouselTestView: View { let defaultNumberOfColumns: Double @State var isPresented: Bool = false + @State var isSameHeight: Bool = true @State var isSnapping: Bool = true @State var numberOfColumns: Double @State var spacing = 16.0 @State var padding = 16.0 @State var alignment = 0 - @State var contentType = 0 init(_ n: Double = 1) { self.defaultNumberOfColumns = n @@ -104,35 +107,18 @@ struct CarouselTestView: View { var body: some View { ScrollView(.vertical) { - Carousel(numberOfColumns: Int(self.numberOfColumns), spacing: self.spacing, alignment: self.alignment == 0 ? .top : (self.alignment == 1 ? .center : .bottom), isSnapping: self.isSnapping) { - if self.contentType == 0 { - ForEach(0 ..< CardTests.cardSamples.count, id: \.self) { i in - CardTests.cardSamples[i] - } - } else { - ForEach(0 ..< 20, id: \.self) { i in - Text("Text \(i)") - .font(.title) - .padding() - .frame(height: 100) - .background(Color.gray) - } + Carousel(numberOfColumns: Int(self.numberOfColumns), spacing: self.spacing, alignment: self.alignment == 0 ? .top : (self.alignment == 1 ? .center : .bottom), isSnapping: self.isSnapping, isSameHeight: self.isSameHeight) { + ForEach(0 ..< CardTests.cardSamples.count, id: \.self) { i in + CardTests.cardSamples[i] } } .cardStyle(.card) .padding(self.padding) - .border(Color.gray) } + .background(Color.preferredColor(.primaryGroupedBackground)) .sheet(isPresented: self.$isPresented, content: { VStack { - HStack { - Text("Content Type:") - Spacer() - Picker("Content Type", selection: self.$contentType) { - Text("Card").tag(0) - Text("Text").tag(1) - } - } + Toggle("Same card height", isOn: self.$isSameHeight) HStack { Text("numberOfColumns: \(Int(self.numberOfColumns))") @@ -142,11 +128,6 @@ struct CarouselTestView: View { Text("spacing: \(Int(self.spacing))") Slider(value: self.$spacing, in: 0 ... 20, step: 4) } - HStack { - Text("padding: \(Int(self.padding))") - Slider(value: self.$padding, in: 0 ... 20, step: 4) - } - HStack { Text("Alignment:") Spacer() @@ -156,8 +137,14 @@ struct CarouselTestView: View { Text("Bottom").tag(2) } } - Toggle("isSnapping", isOn: self.$isSnapping) + + Divider() + + HStack { + Text("Padding around Carousel: \(Int(self.padding))") + Slider(value: self.$padding, in: 0 ... 20, step: 4) + } } .padding() .presentationDetents([.medium]) diff --git a/Sources/FioriSwiftUICore/Views/Carousel.swift b/Sources/FioriSwiftUICore/Views/Carousel.swift index 9d3783014..b6e1d9242 100644 --- a/Sources/FioriSwiftUICore/Views/Carousel.swift +++ b/Sources/FioriSwiftUICore/Views/Carousel.swift @@ -55,10 +55,14 @@ private struct CarouselLayout: Layout { /// Vertical alignment in each column let alignment: VerticalAlignment - init(numberOfColumns: Int = 1, spacing: CGFloat = 8, alignment: VerticalAlignment = .top) { + /// Whether all subviews have same height which is the maximum height of all subviews + let isSameHeight: Bool + + init(numberOfColumns: Int = 1, spacing: CGFloat = 8, alignment: VerticalAlignment = .top, isSameHeight: Bool = false) { self.numberOfColumns = max(1, numberOfColumns) self.spacing = spacing self.alignment = alignment + self.isSameHeight = isSameHeight } func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize { @@ -66,7 +70,6 @@ private struct CarouselLayout: Layout { return .zero } self.calculateLayout(for: subviews, containerWidth: containerWidth, cache: &cache) - return CGSize(width: cache.columns.last?.maxX ?? containerWidth, height: cache.height) } @@ -78,18 +81,23 @@ private struct CarouselLayout: Layout { for (i, column) in cache.columns.enumerated() { let y: CGFloat - switch self.alignment { - case .top: + + if self.isSameHeight { y = 0 - case .bottom: - y = cache.height - column.size.height - default: - y = (cache.height - column.size.height) / 2 + } else { + switch self.alignment { + case .top: + y = 0 + case .bottom: + y = cache.height - column.size.height + default: + y = (cache.height - column.size.height) / 2 + } } let pt = CGPoint(x: column.origin.x + bounds.origin.x, y: y + bounds.origin.y) - subviews[i].place(at: pt, proposal: ProposedViewSize(width: column.size.width, height: nil)) + subviews[i].place(at: pt, proposal: ProposedViewSize(width: column.size.width, height: self.isSameHeight ? cache.height : nil)) } } @@ -199,6 +207,9 @@ public struct Carousel: View where Content: View { /// Whether it stops at a right position that the first visible subview can be displayed fully after scrolling. let isSnapping: Bool + /// Whether all subviews have same height which is the maximum height of all subviews + let isSameHeight: Bool + /// The views representing the content of the Carousel var content: () -> Content @@ -222,19 +233,21 @@ public struct Carousel: View where Content: View { /// - spacing: Horizontal spacing between views. The default is 8. /// - alignment: Vertical alignment in the carousel. The default is `.top`. /// - isSnapping: Whether it stops at a right position that the first visible subview can be displayed fully after scrolling. The default is `true`. + /// - isSameHeight: Whether all subviews have same height which is the maximum height of all subviews /// - content: The views representing the content of the Carousel - public init(numberOfColumns: Int = 1, spacing: CGFloat = 8, alignment: VerticalAlignment = .top, isSnapping: Bool = true, @ViewBuilder content: @escaping () -> Content) { + public init(numberOfColumns: Int = 1, spacing: CGFloat = 8, alignment: VerticalAlignment = .top, isSnapping: Bool = true, isSameHeight: Bool = false, @ViewBuilder content: @escaping () -> Content) { self.numberOfColumns = numberOfColumns self.spacing = spacing self.alignment = alignment self.isSnapping = isSnapping + self.isSameHeight = isSameHeight self.content = content } public var body: some View { CarouselViewLayout { HStack { - CarouselLayout(numberOfColumns: self.numberOfColumns, spacing: self.spacing, alignment: self.alignment) { + CarouselLayout(numberOfColumns: self.numberOfColumns, spacing: self.spacing, alignment: self.alignment, isSameHeight: self.isSameHeight) { self.content() } } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/CardHeaderStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/CardHeaderStyle.fiori.swift index f604b2d21..b32f4bb9b 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/CardHeaderStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/CardHeaderStyle.fiori.swift @@ -15,7 +15,7 @@ import SwiftUI public struct CardHeaderBaseStyle: CardHeaderStyle { public func makeBody(_ configuration: CardHeaderConfiguration) -> some View { // Add default layout here - CardLayout(lineSpacing: 0) { + CardLayout(lineSpacing: 0, useProposedHeight: false) { configuration._cardMedia .clipped() diff --git a/Sources/FioriSwiftUICore/_FioriStyles/CardStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/CardStyle.fiori.swift index 35c55dd4b..73805b26b 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/CardStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/CardStyle.fiori.swift @@ -14,9 +14,17 @@ import SwiftUI */ // Base Layout style public struct CardBaseStyle: CardStyle { + /// It means a Card's height use the proposal's height over its intrinsic height if it is true. + /// `CarouselLayout` with `isSameHeight` `true` requires `useProposedHeight` to be `true`. + let useProposedHeight: Bool + + init(useProposedHeight: Bool = true) { + self.useProposedHeight = useProposedHeight + } + public func makeBody(_ configuration: CardConfiguration) -> some View { // Add default layout here - CardLayout(lineSpacing: 0) { + CardLayout(lineSpacing: 0, useProposedHeight: self.useProposedHeight) { if !configuration._cardHeader.isEmpty { configuration._cardHeader .padding(EdgeInsets(top: 0, leading: 0, bottom: 6, trailing: 0)) @@ -34,6 +42,7 @@ public struct CardBaseStyle: CardStyle { { configuration._cardFooter .padding(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16)) + .layoutPriority(3) // Mark this as the footer in CardLayout } } .clipped() @@ -45,54 +54,74 @@ struct CardLayout: Layout { public struct CacheData { var width: CGFloat? var maxWidth: CGFloat + var height: CGFloat var rows: [CGRect] mutating func clear() { self.width = nil self.maxWidth = 0 + self.height = 0 self.rows.removeAll() } } let lineSpacing: CGFloat - public init(lineSpacing: CGFloat = 8) { + /// It means a Card's height use the proposal's height over its intrinsic height if it is true. + let useProposedHeight: Bool + + init(lineSpacing: CGFloat = 8, useProposedHeight: Bool = false) { self.lineSpacing = lineSpacing + self.useProposedHeight = useProposedHeight } - public func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize { - self.calculateLayout(for: subviews, containerWidth: proposal.width, cache: &cache) + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize { + self.calculateLayout(proposal: proposal, subviews: subviews, cache: &cache) let finalWidth = max(proposal.width ?? 0, cache.maxWidth) - let height: CGFloat = cache.rows.last?.maxY ?? 0 - return CGSize(width: finalWidth, height: height) + return CGSize(width: finalWidth, height: cache.height) } - public func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { - self.calculateLayout(for: subviews, containerWidth: proposal.width, cache: &cache) - + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { + self.calculateLayout(proposal: proposal, subviews: subviews, cache: &cache) + for (i, subview) in subviews.enumerated() { let item = cache.rows[i] - let pt = CGPoint(x: item.origin.x + bounds.origin.x, y: item.origin.y + bounds.origin.y) + + var pt = CGPoint(x: item.origin.x + bounds.origin.x, y: item.origin.y + bounds.origin.y) + /// If it is the footer and there is exessive height for the card then the footer is moved to the bottom + if self.useProposedHeight, subview.priority == 3, item.origin.y + item.size.height < cache.height { + pt.y = cache.height - item.size.height + bounds.origin.y + } subview.place(at: pt, proposal: ProposedViewSize(width: item.size.width, height: nil)) } } - public func makeCache(subviews: Subviews) -> CacheData { - CacheData(width: nil, maxWidth: 0, rows: []) + func makeCache(subviews: Subviews) -> CacheData { + CacheData(width: nil, maxWidth: 0, height: 0, rows: []) } - func calculateLayout(for subviews: Subviews, containerWidth: CGFloat?, cache: inout CacheData) { - if subviews.isEmpty || (cache.width == containerWidth && !cache.rows.isEmpty) { + func calculateLayout(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) { + let containerWidth = proposal.width + + let height: CGFloat + + if self.useProposedHeight { + height = proposal.height ?? (cache.rows.last?.maxY ?? 0) + } else { + height = cache.rows.last?.maxY ?? 0 + } + + if subviews.isEmpty || (cache.width == containerWidth && !cache.rows.isEmpty && cache.height == height) { return } cache.clear() cache.width = containerWidth let maxContainerWidth = containerWidth ?? CGFloat.greatestFiniteMagnitude - let proposal = containerWidth == nil ? ProposedViewSize.unspecified : ProposedViewSize(width: containerWidth, height: nil) + let newProposal = containerWidth == nil ? ProposedViewSize.unspecified : ProposedViewSize(width: containerWidth, height: nil) let sizes = subviews.map { - $0.sizeThatFits(proposal) + $0.sizeThatFits(newProposal) }.map { if $0.width > maxContainerWidth { return CGSize(width: maxContainerWidth, height: $0.height) @@ -108,6 +137,11 @@ struct CardLayout: Layout { pt.y += size.height + self.lineSpacing cache.maxWidth = max(cache.maxWidth, size.width) } + + cache.height = cache.rows.last?.maxY ?? 0 + if self.useProposedHeight, let value = proposal.height { + cache.height = value + } } } @@ -361,6 +395,20 @@ public extension CardStyle where Self == CardCardStyle { } } +/// Intrinsic Height card style. When a card is put into a HStack/VStack then this style is recommeded to use. +public struct CardIntrinsicHeightStyle: CardStyle { + public func makeBody(_ configuration: CardConfiguration) -> some View { + CardBaseStyle(useProposedHeight: false).makeBody(configuration) + } +} + +public extension CardStyle where Self == CardIntrinsicHeightStyle { + /// Intrinsic Height card style. When a card is put into a HStack/VStack then this style is recommeded to use. + static var intrinsicHeightCard: Self { + CardIntrinsicHeightStyle() + } +} + struct ColorTagStyle: TagStyle { /// text color var textColor: Color = .preferredColor(.secondaryLabel) @@ -426,18 +474,6 @@ struct TagExample: View { } } -struct RattingViewExample: View { - var body: some View { - HStack(spacing: 2) { - Image(systemName: "star.fill") - Image(systemName: "star.fill") - Image(systemName: "star") - Image(systemName: "star") - Image(systemName: "star") - } - } -} - /// Card Tests public enum CardTests { static let region = MKCoordinateRegion( @@ -506,15 +542,26 @@ public enum CardTests { Text("Gbt") } row1: { HStack { - RattingViewExample() + RatingControl(rating: .constant(3), ratingControlStyle: .standard) LabelItem(title: "Free Breakfast") } } row2: { Tag("Business Rate") .tagStyle(ColorTagStyle(textColor: .preferredColor(.grey9), fillColor: .preferredColor(.grey2))) } kpi: { - KPIItem(data: .components([.unit("$"), .metric("90")]), subtitle: "avg. per night") - .frame(height: 20) + VStack(alignment: .trailing) { + HStack(alignment: .bottom, spacing: 0) { + Text("$") + .font(.fiori(forTextStyle: .body, weight: .bold)) + Text("90") + .font(.fiori(forTextStyle: .title2, weight: .bold)) + } + .foregroundStyle(Color.preferredColor(.primaryLabel)) + + Text("avg. per night") + .font(.fiori(forTextStyle: .footnote)) + .foregroundStyle(Color.preferredColor(.secondaryLabel)) + } } action: { FioriButton(title: "Reserve") } secondaryAction: { @@ -588,14 +635,31 @@ public enum CardTests { .contentShape(Rectangle()) } - static let sampleCard5 = Card(title: "Title", - subtitle: "Subtitle that goes to multiple lines before truncating just like that", - icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], - detailImage: Image("ProfilePic"), - headerAction: FioriButton(title: "..."), - counter: "1 of 3", - action: FioriButton(title: "Primary"), - secondaryAction: FioriButton(title: "Secondary")) + static let sampleCard5 = Card { + Text("Title") + } subtitle: { + Text("Subtitle that goes to multiple lines before truncating just like that") + } icons: { + Image(systemName: "exclamationmark.triangle.fill") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(.preferredColor(.negativeLabel)) + Image(systemName: "paperclip") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(.preferredColor(.quaternaryLabel)) + Text("1") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(.preferredColor(.quaternaryLabel)) + } detailImage: { + Image("ProfilePic") + } headerAction: { + FioriButton(title: "...") + } counter: { + Text("1 of 3") + } action: { + FioriButton(title: "Primary") + } secondaryAction: { + FioriButton(title: "Secondary") + } static let sampleCard6 = Card(title: "Title", subtitle: "Subtitle that goes to multiple lines before truncating just like that", headerAction: FioriButton(title: "..."), counter: "1 of 3", action: FioriButton(title: "Primary")) @@ -686,7 +750,7 @@ public enum CardTests { } static let sampleCard9 = Card { - Text("Coyote Hill Rd").font(.fiori(forTextStyle: .title1, weight: .bold)) + Text("Coyote Hill Rd") } headerAction: { Button { print("tapped") @@ -780,7 +844,7 @@ public enum CardTests { } } } row2: { - RattingViewExample() + RatingControl(rating: .constant(2), ratingControlStyle: .standard) } row3: { TagExample(num: 3) } kpi: { @@ -899,13 +963,7 @@ public enum CardTests { LabelItem(title: "Multiple lines row1") } } row2: { - HStack(spacing: 2) { - Image(systemName: "star.fill") - Image(systemName: "star.fill") - Image(systemName: "star") - Image(systemName: "star") - Image(systemName: "star") - } + RatingControl(rating: .constant(2), ratingControlStyle: .standard) } row3: { TagExample(num: 8, withDot: true) } cardBody: { @@ -939,13 +997,31 @@ public enum CardTests { action: FioriButton(title: "Primary"), secondaryAction: FioriButton(title: "Secondary")) - static let sampleCard13 = Card(title: "Title", - subtitle: "Subtitle that goes to multiple lines before truncating just like that", - icons: [TextOrIcon.icon(Image(systemName: "circle.fill")), TextOrIcon.icon(Image(systemName: "paperclip")), TextOrIcon.text("1")], - headerAction: FioriButton(title: "..."), - counter: "1 of 3", - action: FioriButton(title: "Primary"), - secondaryAction: FioriButton(title: "Secondary"), tertiaryAction: FioriButton(title: "Tertiary")) + static let sampleCard13 = Card { + Text("Title") + } subtitle: { + Text("Subtitle that goes to multiple lines before truncating just like that") + } icons: { + Image(systemName: "exclamationmark.triangle.fill") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(.preferredColor(.negativeLabel)) + Image(systemName: "paperclip") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(.preferredColor(.quaternaryLabel)) + Text("1") + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(.preferredColor(.quaternaryLabel)) + } headerAction: { + FioriButton(title: "...") + } counter: { + Text("1 of 3") + } action: { + FioriButton(title: "Primary") + } secondaryAction: { + FioriButton(title: "Secondary") + } tertiaryAction: { + FioriButton(title: "Tertiary") + } static let sampleCard14 = Card(title: "Title", subtitle: "Subtitle that goes to multiple lines before truncating just like that", @@ -1000,16 +1076,22 @@ public enum CardTests { static let previewCardSamples = [sampleCard1, sampleCard2, sampleCard3, sampleCard4, sampleCard5, sampleCard6, sampleCard7, sampleCard8, sampleCard9, sampleCard10, sampleCard11, vbCard, fullCard, headerOnly, titleOnly, noHeader] } -struct CardPreview: PreviewProvider { - static var previews: some View { +#Preview { + List { ForEach(0 ..< CardTests.previewCardSamples.count, id: \.self) { i in - VStack { - CardTests.previewCardSamples[i] - .cardStyle(.card) - .padding(32) - } - .background(Color.green) - .environment(\.colorScheme, .dark) + CardTests.previewCardSamples[i] } } + .border(Color.green) +} + +#Preview("Intrinsic Height Card") { + VStack { + CardTests.previewCardSamples[1] + .cardStyle(.card) + .cardStyle(.intrinsicHeightCard) + .padding() + Text("Hello") + } + .background(Color.preferredColor(.primaryGroupedBackground)) } From 9962042b01f0ee5a80f04b6a9e095c3be3bcbe7e Mon Sep 17 00:00:00 2001 From: Xiaoyu Liu Date: Fri, 30 Aug 2024 07:01:58 +0800 Subject: [PATCH 11/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20footnote=20icons=20?= =?UTF-8?q?layout=20(#785)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/CustomBuilder/FootnoteIconsListView.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift index 9b771b4e2..d7fcc14e9 100644 --- a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift @@ -95,16 +95,15 @@ struct FootnoteIconsHStack: Layout { for (index, subview) in subviews.enumerated() { let subviewSize = subview.sizeThatFits(proposal) maxHeight = max(maxHeight, subviewSize.height) - if subviewSize.width + totalWidth <= contentWidth { - totalWidth += subviewSize.width - totalWidth += self.spacing + let gap = index > 0 ? self.spacing : 0 + if totalWidth + subviewSize.width + gap <= contentWidth { + totalWidth += (subviewSize.width + gap) } else { cache.count = index cache.size = CGSize(width: totalWidth, height: maxHeight) - break + return } } - totalWidth -= self.spacing cache.count = subviews.count cache.size = CGSize(width: totalWidth, height: maxHeight) } @@ -119,7 +118,9 @@ struct FootnoteIconsHStack: Layout { proposal: ProposedViewSize(CGSize(width: subviewSize.width, height: subviewSize.height))) xOffset += (subviewSize.width + self.spacing) } else { - break + let maxOffset = CGFloat.greatestFiniteMagnitude + let offScreen = CGPoint(x: maxOffset, y: maxOffset) + subview.place(at: offScreen, proposal: .unspecified) } } } From a5dc2c0ff0a17edfe2c3cc290507b8e0686f68a8 Mon Sep 17 00:00:00 2001 From: bigCode Date: Sat, 31 Aug 2024 07:39:30 +0800 Subject: [PATCH 12/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:HCPSDKFIORIU?= =?UTF-8?q?IKIT-2716]Step=20Progress=20Indicator=20(#784)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bill Zhou --- .../StepProgressIndicatorExample.swift | 100 ++++++++++++++++++ .../StepProgressIndicator/StepItem.swift | 3 + .../StepProgressIndicator/_DefaultSteps.swift | 8 +- 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift index 7e9504259..bfed3ef30 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift @@ -35,6 +35,11 @@ struct StepProgressIndicatorExample: View { } label: { Text("Steps By Model") } + NavigationLink { + SPIExampleWithIcon() + } label: { + Text("Steps By Icon") + } } } } @@ -45,6 +50,97 @@ struct StepProgressIndicatorExample_Previews: PreviewProvider { } } +struct SPIExampleWithIcon: View { + static let icon = Image(systemName: "app.dashed") + @State var title: String = "" + @State var steps = [StepItemData(title: "Sign In", icon: FioriIcon.arrows.initiative, state: .completed), + StepItemData(title: "User Info", icon: FioriIcon.people.personPlaceholder, state: .completed), + StepItemData(title: "Account Info", icon: FioriIcon.actions.edit, state: .completed), + StepItemData(title: "Settings", icon: FioriIcon.actions.actionSettings, state: .normal, substeps: [StepItemData(title: "Settings 1")]), + StepItemData(title: "Other0", icon: icon, state: .disabled), + StepItemData(title: "Other1", icon: icon, state: .completed), + StepItemData(title: "other2", icon: icon, state: .error)] + @State var selection: String = "" + var body: some View { + VStack(alignment: .leading) { + Text("Icon").bold() + StepProgressIndicator(selection: self.$selection, + stepItems: self.steps) + { + Text(self.$title.wrappedValue).lineLimit(1) + } action: { + Button {} label: { + HStack(spacing: 2) { + Text("All Steps(\(self.steps.count))") + .foregroundStyle(Color.preferredColor(.tintColor)) + FioriIcon.actions.slimArrowRight + .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) + .foregroundStyle(Color.preferredColor(.separator)) + } + } + } + Spacer().padding(20) + Button { + self.completeStep() + } label: { + Text("Mark as Completed") + } + .padding(20) + } + .padding() + .onChange(of: self.selection, perform: { _ in + self.updateCurrentStepName() + }) + .onAppear { + self.updateCurrentStepName() + } + } + + func getStep() -> StepItem? { + func findStep(in data: [StepItem]) -> StepItem? { + for step in data { + if step.id == self.selection { + return step + } + + if !step.substeps.isEmpty { + if let foundItem = findStep(in: step.substeps) { + return foundItem + } else { + continue + } + } + } + + return nil + } + return findStep(in: self.steps) + } + + func updateCurrentStepName() { + let selectedTitle = "\(getStep()?.title ?? "no title")" + if self.title != selectedTitle { + self.title = selectedTitle + } + } + + func completeStep() { + for index in self.steps.indices { + if self.steps[index].id == self.selection { + self.steps[index].state = .completed + } else { + let substeps = self.steps[index].substeps + guard !substeps.isEmpty else { continue } + for subindex in substeps.indices { + if substeps[subindex].id == self.selection { + self.steps[index].substeps[subindex].state = .completed + } + } + } + } + } +} + struct SPIExampleWithoutHeader: View { @State var steps = [StepItemData(title: "Step A", state: .completed), StepItemData(title: "Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name Step B This is a very very long step name"), @@ -432,6 +528,8 @@ struct StepItemData: StepItem { var id: String = UUID().uuidString /// Step title. var title: String? + /// Node icon + var icon: Image? /// Step state. var state: StepProgressIndicatorState = .normal /// Sub-steps for this one. @@ -439,11 +537,13 @@ struct StepItemData: StepItem { init(id: String = UUID().uuidString, title: String? = nil, + icon: Image? = nil, state: StepProgressIndicatorState = [], substeps: [StepItemData] = []) { self.id = id self.title = title + self.icon = icon self.state = state self.substeps = substeps } diff --git a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepItem.swift b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepItem.swift index b95312824..10dd8ef99 100644 --- a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepItem.swift +++ b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepItem.swift @@ -1,4 +1,5 @@ import Foundation +import SwiftUI /// Step items data model for `StepProgressIndicator` with a default style. public protocol StepItem { @@ -10,4 +11,6 @@ public protocol StepItem { var state: StepProgressIndicatorState { get set } /// Substeps for this one. var substeps: [StepItem] { get set } + /// Node icon. + var icon: Image? { get } } diff --git a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/_DefaultSteps.swift b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/_DefaultSteps.swift index 445e80bcb..f8a571c5f 100644 --- a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/_DefaultSteps.swift +++ b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/_DefaultSteps.swift @@ -98,8 +98,12 @@ struct DefaultSingleStep: View { } node: { ZStack { self.node(by: self.stepItem.state, isSelected: isSelected) - Text("\(self.index + 1)") - .font(Font.fiori(forTextStyle: .footnote)) + if self.stepItem.icon.isEmpty { + Text("\(self.index + 1)") + .font(Font.fiori(forTextStyle: .footnote)) + } else { + self.stepItem.icon + } } .frame(width: self.sideLength, height: self.sideLength) .overlay { From c36ba3e487ab454ee98eb2fb63220806cc585f1c Mon Sep 17 00:00:00 2001 From: hengyi-zhang Date: Wed, 4 Sep 2024 01:21:30 +0800 Subject: [PATCH 13/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:HCPSDKFIORIU?= =?UTF-8?q?IKIT-2728]=20Update=20button=20visible=20and=20touch=20area=20h?= =?UTF-8?q?eight=20(#786)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bill Zhou --- .../FioriButton/FioriButton.swift | 51 ++++--------------- .../FioriButtonStyleProvider.swift | 2 +- 2 files changed, 10 insertions(+), 43 deletions(-) diff --git a/Sources/FioriSwiftUICore/FioriButton/FioriButton.swift b/Sources/FioriSwiftUICore/FioriButton/FioriButton.swift index 009b25cd4..76c892f66 100644 --- a/Sources/FioriSwiftUICore/FioriButton/FioriButton.swift +++ b/Sources/FioriSwiftUICore/FioriButton/FioriButton.swift @@ -62,7 +62,7 @@ public struct FioriButton: View { let image: (UIControl.State) -> any View let imagePosition: FioriButtonImagePosition let imageTitleSpacing: CGFloat - private let touchAreaInset: CGFloat = 50 + private let touchAreaInset: CGFloat = 3 @Environment(\.isEnabled) private var isEnabled @Environment(\.fioriButtonStyle) private var fioriButtonStyle @@ -157,24 +157,15 @@ public struct FioriButton: View { }, imagePosition: self.imagePosition, imageTitleSpacing: self.imageTitleSpacing) return Group { - if self.isSelectionPersistent { - self.fioriButtonStyle.makeBody(configuration: config) - .overlay(GeometryReader { proxy in - Color.clear.contentShape(Rectangle()).gesture(self.createGesture(proxy.size)) - }) - } else { - Button { - self.action?(.normal) - } label: { - EmptyView() - } - .buttonStyle(_ButtonStyleImpl(fioriButtonStyle: self.fioriButtonStyle, label: self.label, image: self.image, imagePosition: self.imagePosition, imageTitleSpacing: self.imageTitleSpacing, isEnabled: self.isEnabled)) - } + self.fioriButtonStyle.makeBody(configuration: config) + .overlay(GeometryReader { proxy in + Color.clear.contentShape(Rectangle()).gesture(self.createGesture(proxy.size)) + }) } } func createGesture(_ size: CGSize) -> some Gesture { - let touchArea = CGRect(origin: .zero, size: size).insetBy(dx: -self.touchAreaInset, dy: -self.touchAreaInset) + let touchArea = CGRect(origin: .zero, size: size).insetBy(dx: 0, dy: -self.touchAreaInset) var isCancelled = false return DragGesture(minimumDistance: 0) @@ -185,6 +176,9 @@ public struct FioriButton: View { if !touchArea.contains(value.location) { isCancelled = true + } else if !self.isSelectionPersistent { + self._state = self.state == .normal ? .selected : .normal + self.action?(self.state) } } .onEnded { _ in @@ -226,33 +220,6 @@ public extension FioriButton { } } -private struct _ButtonStyleImpl: ButtonStyle { - let fioriButtonStyle: AnyFioriButtonStyle - let label: (UIControl.State) -> any View - let image: (UIControl.State) -> any View - let imagePosition: FioriButtonImagePosition - let imageTitleSpacing: CGFloat - let isEnabled: Bool - - func makeBody(configuration: Configuration) -> some View { - let state: UIControl.State = self.isEnabled ? (configuration.isPressed ? .highlighted : .normal) : .disabled - - let config = FioriButtonStyleConfiguration(state: state, _label: { state in - let v = self.label(state) - return FioriButtonStyleConfiguration.Label(v) - }, _image: { state in - let v = self.image(state) - return FioriButtonStyleConfiguration.Image(v) - }, imagePosition: self.imagePosition, imageTitleSpacing: self.imageTitleSpacing) - - return ZStack { - self.fioriButtonStyle.makeBody(configuration: config) - - configuration.label.hidden() - } - } -} - /// Place the image along the top, leading, bottom, or trailing edge of the button. public enum FioriButtonImagePosition { /// place the image along the top edge of the button. diff --git a/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyleProvider.swift b/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyleProvider.swift index 625add4ac..11d8a3177 100644 --- a/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyleProvider.swift +++ b/Sources/FioriSwiftUICore/FioriButton/FioriButtonStyleProvider.swift @@ -198,7 +198,7 @@ extension View { .foregroundColor(config.foregroundColor) .tint(config.foregroundColor) .padding(config.padding) - .frame(minWidth: 44, maxWidth: config.maxWidth, minHeight: 44) + .frame(minWidth: 44, maxWidth: config.maxWidth, minHeight: 38) .background(RoundedRectangle(cornerRadius: 8).fill(config.backgroundColor)) .contentShape(Rectangle()) } From 4889cbd4f557baa3d023e3d13d740c653c9b222e Mon Sep 17 00:00:00 2001 From: Bill Zhou Date: Wed, 4 Sep 2024 14:01:28 -0700 Subject: [PATCH 14/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[HCPSDKFIORIUIKIT-2?= =?UTF-8?q?720]=20Style=20updates=20in=20ObjectItem=20(#793)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 fix layout issue in Icons and IconBuilder * fix: 🐛 [HCPSDKFIORIUIKIT-2720] Style updates in ObjectItem --- .../SingleActionCollectionView.swift | 1 - .../Views/IconStack/IconBuilder.swift | 12 +- Sources/FioriSwiftUICore/Views/MHStack.swift | 2 +- .../FioriSwiftUICore/Views/TagStack/Tag.swift | 4 +- .../_FioriStyles/ObjectItemStyle.fiori.swift | 50 ++++- .../AccessoryIcon.generated.swift | 2 +- .../Action/Action.generated.swift | 2 +- .../Attribute/Attribute.generated.swift | 2 +- .../Avatars/Avatars.generated.swift | 2 +- .../BannerMessage.generated.swift | 8 +- .../Card/Card.generated.swift | 36 ++-- .../CardBody/CardBody.generated.swift | 2 +- .../CardExtHeader.generated.swift | 10 +- .../CardFooter/CardFooter.generated.swift | 8 +- .../CardHeader/CardHeader.generated.swift | 26 +-- .../CardMainHeader.generated.swift | 12 +- .../CardMedia/CardMedia.generated.swift | 4 +- .../CloseAction/CloseAction.generated.swift | 2 +- .../Counter/Counter.generated.swift | 2 +- .../DecrementAction.generated.swift | 2 +- .../DemoView/DemoView.generated.swift | 8 +- .../Description/Description.generated.swift | 2 +- .../DetailImage/DetailImage.generated.swift | 2 +- .../FilledIcon/FilledIcon.generated.swift | 2 +- .../Footnote/Footnote.generated.swift | 2 +- .../FootnoteIcons.generated.swift | 2 +- .../FootnoteIconsText.generated.swift | 2 +- .../GreetingText/GreetingText.generated.swift | 2 +- .../HeaderAction/HeaderAction.generated.swift | 2 +- .../HelperText/HelperText.generated.swift | 2 +- .../Icon/Icon.generated.swift | 2 +- .../Icons/Icons.generated.swift | 2 +- .../IllustratedMessage.generated.swift | 10 +- .../IncrementAction.generated.swift | 2 +- .../InformationView.generated.swift | 4 +- .../JouleWelcomeScreen.generated.swift | 10 +- .../KeyValueFormView.generated.swift | 6 +- .../Kpi/Kpi.generated.swift | 2 +- .../KpiCaption/KpiCaption.generated.swift | 2 +- .../LabelItem/LabelItem.generated.swift | 4 +- .../LinearProgressIndicator.generated.swift | 2 +- ...inearProgressIndicatorView.generated.swift | 4 +- .../MandatoryFieldIndicator.generated.swift | 2 +- .../MediaImage/MediaImage.generated.swift | 2 +- .../MenuSelection.generated.swift | 2 +- .../MenuSelectionItem.generated.swift | 4 +- .../MessageContent.generated.swift | 2 +- .../MoreActionOverflow.generated.swift | 2 +- .../NoteFormView/NoteFormView.generated.swift | 2 +- .../NowIndicatorNode.generated.swift | 2 +- .../ObjectItem/ObjectItem.generated.swift | 26 +-- .../Options/Options.generated.swift | 2 +- .../OverflowAction.generated.swift | 2 +- .../Placeholder/Placeholder.generated.swift | 2 +- .../PlaceholderTextEditor.generated.swift | 2 +- .../PlaceholderTextField.generated.swift | 2 +- .../ProfileHeader.generated.swift | 8 +- .../RatingControlFormView.generated.swift | 4 +- .../Row1/Row1.generated.swift | 2 +- .../Row2/Row2.generated.swift | 2 +- .../Row3/Row3.generated.swift | 2 +- .../SecondaryAction.generated.swift | 2 +- .../SecondaryTimestamp.generated.swift | 2 +- .../SideBarListItem.generated.swift | 10 +- .../Status/Status.generated.swift | 2 +- .../StepperField/StepperField.generated.swift | 4 +- .../StepperView/StepperView.generated.swift | 10 +- .../SubAttribute/SubAttribute.generated.swift | 2 +- .../Substatus/Substatus.generated.swift | 2 +- .../Subtitle/Subtitle.generated.swift | 2 +- .../Switch/Switch.generated.swift | 2 +- .../SwitchView/SwitchView.generated.swift | 2 +- .../Tags/Tags.generated.swift | 2 +- .../TertiaryAction.generated.swift | 2 +- .../TextFieldFormView.generated.swift | 6 +- .../TextInputField.generated.swift | 2 +- .../TextInputInfoView.generated.swift | 6 +- .../TextView/TextView.generated.swift | 2 +- .../Timeline/Timeline.generated.swift | 20 +- .../TimelineMarker.generated.swift | 10 +- .../TimelineNode/TimelineNode.generated.swift | 2 +- .../TimelineNowIndicator.generated.swift | 2 +- .../Timestamp/Timestamp.generated.swift | 2 +- .../Title/Title.generated.swift | 2 +- .../TitleFormView.generated.swift | 2 +- .../TopDivider/TopDivider.generated.swift | 2 +- .../.swiftpm/configuration/Package.resolved | 185 ++++++++++++++++++ .../Array+Extension.swift | 2 +- .../Type+Extension.swift | 28 +-- 89 files changed, 441 insertions(+), 213 deletions(-) create mode 100644 sourcery/.lib/.swiftpm/configuration/Package.resolved diff --git a/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/SingleActionCollectionView.swift b/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/SingleActionCollectionView.swift index 0f3dbc246..bed9046c5 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/SingleActionCollectionView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/SingleActionCollectionView.swift @@ -35,7 +35,6 @@ struct SingleActionCollectionView: View { }, label: { _ in Image(systemName: "arrow.down.circle") .resizable() - .foregroundColor(Color.preferredColor(.tintColor)) .frame(width: 32, height: 32) }) }) } else { diff --git a/Sources/FioriSwiftUICore/Views/IconStack/IconBuilder.swift b/Sources/FioriSwiftUICore/Views/IconStack/IconBuilder.swift index 032683294..1d7377445 100644 --- a/Sources/FioriSwiftUICore/Views/IconStack/IconBuilder.swift +++ b/Sources/FioriSwiftUICore/Views/IconStack/IconBuilder.swift @@ -20,15 +20,14 @@ public protocol ViewList: View, _ViewEmptyChecking { /// conform View protocol for IconStack public extension ViewList { var body: some View { - VStack(alignment: .center, spacing: 6) { + VStack(spacing: 6) { ForEach(0 ..< min(numberOfIconsToShow(), count), id: \.self) { index in view(at: index) .lineLimit(1) .minimumScaleFactor(0.1) - .frame(width: 16) + .frame(width: 14, height: 14) } } - .clipped() } } @@ -49,8 +48,13 @@ public struct Single: ViewList { } /// the View at Index in the ViewList + @ViewBuilder public func view(at index: Int) -> some View { - self.view + if index == 0 { + self.view + } else { + EmptyView() + } } /// is first item a text in the ViewList diff --git a/Sources/FioriSwiftUICore/Views/MHStack.swift b/Sources/FioriSwiftUICore/Views/MHStack.swift index dc3863090..b641682fd 100644 --- a/Sources/FioriSwiftUICore/Views/MHStack.swift +++ b/Sources/FioriSwiftUICore/Views/MHStack.swift @@ -35,7 +35,7 @@ public struct MHStack: View { /// want the stack to choose a default distance for each pair of /// subviews. /// - content: A view builder that creates the content of this stack. - public init(spacing: CGFloat? = 10, lineSpacing: CGFloat? = 10, @TagBuilder content: () -> T) { + public init(spacing: CGFloat? = 8, lineSpacing: CGFloat? = 10, @TagBuilder content: () -> T) { self.tags = content() self.spacing = spacing! self.lineSpacing = lineSpacing! diff --git a/Sources/FioriSwiftUICore/Views/TagStack/Tag.swift b/Sources/FioriSwiftUICore/Views/TagStack/Tag.swift index b2c0d68e7..590f20506 100644 --- a/Sources/FioriSwiftUICore/Views/TagStack/Tag.swift +++ b/Sources/FioriSwiftUICore/Views/TagStack/Tag.swift @@ -43,7 +43,7 @@ public struct LightTagStyle: TagStyle { .foregroundColor(.preferredColor(.secondaryLabel)) .lineLimit(1) .padding(EdgeInsets(top: 2, leading: 3, bottom: 2, trailing: 3)) - .background(RoundedRectangle(cornerRadius: 4).stroke(Color.preferredColor(.quaternaryLabel), lineWidth: 0.5)) + .background(RoundedRectangle(cornerRadius: 8).stroke(Color.preferredColor(.quaternaryLabel), lineWidth: 0.5)) } } @@ -57,7 +57,7 @@ public struct DarkTagStyle: TagStyle { .foregroundColor(.preferredColor(.primaryLabel, background: .darkConstant)) .lineLimit(1) .padding(EdgeInsets(top: 2, leading: 3, bottom: 2, trailing: 3)) - .background(RoundedRectangle(cornerRadius: 4).fill(Color.preferredColor(.tertiaryLabel))) + .background(RoundedRectangle(cornerRadius: 8).fill(Color.preferredColor(.tertiaryLabel))) } } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift index 796cff2b3..4b7c51541 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift @@ -520,7 +520,8 @@ extension ObjectItemFioriStyle { func makeBody(_ configuration: TitleConfiguration) -> some View { Title(configuration) - // Add default style here + .font(.fiori(forTextStyle: .headline, weight: .semibold)) + .foregroundStyle(Color.preferredColor(.baseBlack)) } } @@ -529,7 +530,7 @@ extension ObjectItemFioriStyle { func makeBody(_ configuration: SubtitleConfiguration) -> some View { Subtitle(configuration) - // Add default style here + .foregroundStyle(Color.preferredColor(.secondaryLabel)) .lineLimit(1) } } @@ -558,7 +559,7 @@ extension ObjectItemFioriStyle { func makeBody(_ configuration: StatusConfiguration) -> some View { Status(configuration) - // Add default style here + .font(.fiori(forTextStyle: .subheadline)) .lineLimit(1) } } @@ -568,7 +569,7 @@ extension ObjectItemFioriStyle { func makeBody(_ configuration: SubstatusConfiguration) -> some View { Substatus(configuration) - // Add default style here + .font(.fiori(forTextStyle: .subheadline)) .lineLimit(1) } } @@ -589,7 +590,7 @@ extension ObjectItemFioriStyle { func makeBody(_ configuration: IconsConfiguration) -> some View { Icons(configuration) - // Add default style here + .foregroundStyle(Color.preferredColor(.tertiaryLabel)) } } @@ -710,6 +711,45 @@ public struct ObjectItemBorderedAction: ActionStyle { config.title .foregroundStyle(.blue) // take effect } + .padding([.leading, .trailing], 8) + } +} + +#Preview("With Action") { + List { + ObjectItem(title: { + Text("Title") + }, subtitle: { + Text("Subtitle") + }, footnote: { + Text("Footnote") + }, description: { + Text("Description") + }, status: { + Text("Status") + }, substatus: { + Text("Substatus") + }, detailImage: { + Image(systemName: "person.circle").resizable().frame(width: 45, height: 45) + }, icons: { + Text("1") + Circle().fill(Color.preferredColor(.tintColor)).frame(width: 14, height: 14) + Image(systemName: "paperclip").font(.system(size: 14)) + }, footnoteIcons: { + Color.red + Color.green + Color.blue + Color.red + Color.green + Color.blue + Color.red + Color.green + Color.blue + }, footnoteIconsText: { + Text("Footnote icons text.") + }, action: { + FioriButton(title: "Action") + }) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AccessoryIcon/AccessoryIcon.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AccessoryIcon/AccessoryIcon.generated.swift index 28fd9a50f..49e5d260d 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AccessoryIcon/AccessoryIcon.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AccessoryIcon/AccessoryIcon.generated.swift @@ -55,7 +55,7 @@ private extension AccessoryIcon { } func defaultStyle() -> some View { - AccessoryIcon(accessoryIcon: { self.accessoryIcon }) + AccessoryIcon(.init(accessoryIcon: .init(self.accessoryIcon))) .shouldApplyDefaultStyle(false) .accessoryIconStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Action/Action.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Action/Action.generated.swift index c192f464e..10f6abf08 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Action/Action.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Action/Action.generated.swift @@ -55,7 +55,7 @@ private extension Action { } func defaultStyle() -> some View { - Action(action: { self.action }) + Action(.init(action: .init(self.action))) .shouldApplyDefaultStyle(false) .actionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Attribute/Attribute.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Attribute/Attribute.generated.swift index f6032d910..3de202cd4 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Attribute/Attribute.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Attribute/Attribute.generated.swift @@ -55,7 +55,7 @@ private extension Attribute { } func defaultStyle() -> some View { - Attribute(attribute: { self.attribute }) + Attribute(.init(attribute: .init(self.attribute))) .shouldApplyDefaultStyle(false) .attributeStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Avatars/Avatars.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Avatars/Avatars.generated.swift index 6dc72b53d..974301e95 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Avatars/Avatars.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Avatars/Avatars.generated.swift @@ -55,7 +55,7 @@ private extension Avatars { } func defaultStyle() -> some View { - Avatars(avatars: { self.avatars }) + Avatars(.init(avatars: .init(self.avatars))) .shouldApplyDefaultStyle(false) .avatarsStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/BannerMessage/BannerMessage.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/BannerMessage/BannerMessage.generated.swift index b2733cd91..4a2bbeb58 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/BannerMessage/BannerMessage.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/BannerMessage/BannerMessage.generated.swift @@ -21,10 +21,10 @@ public struct BannerMessage { @ViewBuilder topDivider: () -> any View = { Rectangle().fill(Color.clear) }, bannerTapAction: (() -> Void)? = nil) { - self.icon = Icon { icon() } - self.title = Title { title() } - self.closeAction = CloseAction { closeAction() } - self.topDivider = TopDivider { topDivider() } + self.icon = Icon(icon: icon) + self.title = Title(title: title) + self.closeAction = CloseAction(closeAction: closeAction) + self.topDivider = TopDivider(topDivider: topDivider) self.bannerTapAction = bannerTapAction } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/Card.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/Card.generated.swift index ae1c4c638..6b086d2d8 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/Card.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Card/Card.generated.swift @@ -46,24 +46,24 @@ public struct Card { @ViewBuilder tertiaryAction: () -> any View = { EmptyView() }, @ViewBuilder overflowAction: () -> any View = { FioriButton { _ in Image(systemName: "ellipsis") } }) { - self.mediaImage = MediaImage { mediaImage() } - self.description = Description { description() } - self.title = Title { title() } - self.subtitle = Subtitle { subtitle() } - self.icons = Icons { icons() } - self.detailImage = DetailImage { detailImage() } - self.headerAction = HeaderAction { headerAction() } - self.counter = Counter { counter() } - self.row1 = Row1 { row1() } - self.row2 = Row2 { row2() } - self.row3 = Row3 { row3() } - self.kpi = Kpi { kpi() } - self.kpiCaption = KpiCaption { kpiCaption() } - self.cardBody = CardBody { cardBody() } - self.action = Action { action() } - self.secondaryAction = SecondaryAction { secondaryAction() } - self.tertiaryAction = TertiaryAction { tertiaryAction() } - self.overflowAction = OverflowAction { overflowAction() } + self.mediaImage = MediaImage(mediaImage: mediaImage) + self.description = Description(description: description) + self.title = Title(title: title) + self.subtitle = Subtitle(subtitle: subtitle) + self.icons = Icons(icons: icons) + self.detailImage = DetailImage(detailImage: detailImage) + self.headerAction = HeaderAction(headerAction: headerAction) + self.counter = Counter(counter: counter) + self.row1 = Row1(row1: row1) + self.row2 = Row2(row2: row2) + self.row3 = Row3(row3: row3) + self.kpi = Kpi(kpi: kpi) + self.kpiCaption = KpiCaption(kpiCaption: kpiCaption) + self.cardBody = CardBody(cardBody: cardBody) + self.action = Action(action: action) + self.secondaryAction = SecondaryAction(secondaryAction: secondaryAction) + self.tertiaryAction = TertiaryAction(tertiaryAction: tertiaryAction) + self.overflowAction = OverflowAction(overflowAction: overflowAction) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardBody/CardBody.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardBody/CardBody.generated.swift index 67af1540e..12bd1ed4e 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardBody/CardBody.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardBody/CardBody.generated.swift @@ -49,7 +49,7 @@ private extension CardBody { } func defaultStyle() -> some View { - CardBody(cardBody: { self.cardBody }) + CardBody(.init(cardBody: .init(self.cardBody))) .shouldApplyDefaultStyle(false) .cardBodyStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardExtHeader/CardExtHeader.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardExtHeader/CardExtHeader.generated.swift index f74966af8..00ac8fae3 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardExtHeader/CardExtHeader.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardExtHeader/CardExtHeader.generated.swift @@ -20,11 +20,11 @@ public struct CardExtHeader { @ViewBuilder kpi: () -> any View = { EmptyView() }, @ViewBuilder kpiCaption: () -> any View = { EmptyView() }) { - self.row1 = Row1 { row1() } - self.row2 = Row2 { row2() } - self.row3 = Row3 { row3() } - self.kpi = Kpi { kpi() } - self.kpiCaption = KpiCaption { kpiCaption() } + self.row1 = Row1(row1: row1) + self.row2 = Row2(row2: row2) + self.row3 = Row3(row3: row3) + self.kpi = Kpi(kpi: kpi) + self.kpiCaption = KpiCaption(kpiCaption: kpiCaption) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooter.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooter.generated.swift index 01d1672ee..93e0cefa0 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooter.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardFooter/CardFooter.generated.swift @@ -18,10 +18,10 @@ public struct CardFooter { @ViewBuilder tertiaryAction: () -> any View = { EmptyView() }, @ViewBuilder overflowAction: () -> any View = { FioriButton { _ in Image(systemName: "ellipsis") } }) { - self.action = Action { action() } - self.secondaryAction = SecondaryAction { secondaryAction() } - self.tertiaryAction = TertiaryAction { tertiaryAction() } - self.overflowAction = OverflowAction { overflowAction() } + self.action = Action(action: action) + self.secondaryAction = SecondaryAction(secondaryAction: secondaryAction) + self.tertiaryAction = TertiaryAction(tertiaryAction: tertiaryAction) + self.overflowAction = OverflowAction(overflowAction: overflowAction) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardHeader/CardHeader.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardHeader/CardHeader.generated.swift index b532b157c..73e2afc53 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardHeader/CardHeader.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardHeader/CardHeader.generated.swift @@ -36,19 +36,19 @@ public struct CardHeader { @ViewBuilder kpi: () -> any View = { EmptyView() }, @ViewBuilder kpiCaption: () -> any View = { EmptyView() }) { - self.mediaImage = MediaImage { mediaImage() } - self.description = Description { description() } - self.title = Title { title() } - self.subtitle = Subtitle { subtitle() } - self.icons = Icons { icons() } - self.detailImage = DetailImage { detailImage() } - self.headerAction = HeaderAction { headerAction() } - self.counter = Counter { counter() } - self.row1 = Row1 { row1() } - self.row2 = Row2 { row2() } - self.row3 = Row3 { row3() } - self.kpi = Kpi { kpi() } - self.kpiCaption = KpiCaption { kpiCaption() } + self.mediaImage = MediaImage(mediaImage: mediaImage) + self.description = Description(description: description) + self.title = Title(title: title) + self.subtitle = Subtitle(subtitle: subtitle) + self.icons = Icons(icons: icons) + self.detailImage = DetailImage(detailImage: detailImage) + self.headerAction = HeaderAction(headerAction: headerAction) + self.counter = Counter(counter: counter) + self.row1 = Row1(row1: row1) + self.row2 = Row2(row2: row2) + self.row3 = Row3(row3: row3) + self.kpi = Kpi(kpi: kpi) + self.kpiCaption = KpiCaption(kpiCaption: kpiCaption) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardMainHeader/CardMainHeader.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardMainHeader/CardMainHeader.generated.swift index ecc25ad29..92769c1a1 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardMainHeader/CardMainHeader.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardMainHeader/CardMainHeader.generated.swift @@ -22,12 +22,12 @@ public struct CardMainHeader { @ViewBuilder headerAction: () -> any View = { EmptyView() }, @ViewBuilder counter: () -> any View = { EmptyView() }) { - self.title = Title { title() } - self.subtitle = Subtitle { subtitle() } - self.icons = Icons { icons() } - self.detailImage = DetailImage { detailImage() } - self.headerAction = HeaderAction { headerAction() } - self.counter = Counter { counter() } + self.title = Title(title: title) + self.subtitle = Subtitle(subtitle: subtitle) + self.icons = Icons(icons: icons) + self.detailImage = DetailImage(detailImage: detailImage) + self.headerAction = HeaderAction(headerAction: headerAction) + self.counter = Counter(counter: counter) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardMedia/CardMedia.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardMedia/CardMedia.generated.swift index 1440d07de..33455615d 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardMedia/CardMedia.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CardMedia/CardMedia.generated.swift @@ -14,8 +14,8 @@ public struct CardMedia { public init(@ViewBuilder mediaImage: () -> any View = { EmptyView() }, @ViewBuilder description: () -> any View = { EmptyView() }) { - self.mediaImage = MediaImage { mediaImage() } - self.description = Description { description() } + self.mediaImage = MediaImage(mediaImage: mediaImage) + self.description = Description(description: description) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CloseAction/CloseAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CloseAction/CloseAction.generated.swift index d09a97c99..167798e7f 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/CloseAction/CloseAction.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/CloseAction/CloseAction.generated.swift @@ -55,7 +55,7 @@ private extension CloseAction { } func defaultStyle() -> some View { - CloseAction(closeAction: { self.closeAction }) + CloseAction(.init(closeAction: .init(self.closeAction))) .shouldApplyDefaultStyle(false) .closeActionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Counter/Counter.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Counter/Counter.generated.swift index 3a8abe42c..fc3d4ee97 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Counter/Counter.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Counter/Counter.generated.swift @@ -55,7 +55,7 @@ private extension Counter { } func defaultStyle() -> some View { - Counter(counter: { self.counter }) + Counter(.init(counter: .init(self.counter))) .shouldApplyDefaultStyle(false) .counterStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DecrementAction/DecrementAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DecrementAction/DecrementAction.generated.swift index 3f7e73e37..b7f7c62e4 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DecrementAction/DecrementAction.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DecrementAction/DecrementAction.generated.swift @@ -57,7 +57,7 @@ private extension DecrementAction { } func defaultStyle() -> some View { - DecrementAction(decrementAction: { self.decrementAction }) + DecrementAction(.init(decrementAction: .init(self.decrementAction))) .shouldApplyDefaultStyle(false) .decrementActionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DemoView/DemoView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DemoView/DemoView.generated.swift index 01b10e994..26dfee177 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DemoView/DemoView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DemoView/DemoView.generated.swift @@ -20,10 +20,10 @@ struct DemoView { @ViewBuilder action: () -> any View = { EmptyView() }, isOn: Binding) { - self.title = Title { title() } - self.subtitle = Subtitle { subtitle() } - self.status = Status { status() } - self.action = Action { action() } + self.title = Title(title: title) + self.subtitle = Subtitle(subtitle: subtitle) + self.status = Status(status: status) + self.action = Action(action: action) self._isOn = isOn } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Description/Description.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Description/Description.generated.swift index 61ea23558..5841f98d2 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Description/Description.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Description/Description.generated.swift @@ -55,7 +55,7 @@ private extension Description { } func defaultStyle() -> some View { - Description(description: { self.description }) + Description(.init(description: .init(self.description))) .shouldApplyDefaultStyle(false) .descriptionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DetailImage/DetailImage.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DetailImage/DetailImage.generated.swift index fef99f10f..88a6a9c22 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DetailImage/DetailImage.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DetailImage/DetailImage.generated.swift @@ -55,7 +55,7 @@ private extension DetailImage { } func defaultStyle() -> some View { - DetailImage(detailImage: { self.detailImage }) + DetailImage(.init(detailImage: .init(self.detailImage))) .shouldApplyDefaultStyle(false) .detailImageStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/FilledIcon/FilledIcon.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FilledIcon/FilledIcon.generated.swift index e27c78cb0..7bf54783c 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/FilledIcon/FilledIcon.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FilledIcon/FilledIcon.generated.swift @@ -55,7 +55,7 @@ private extension FilledIcon { } func defaultStyle() -> some View { - FilledIcon(filledIcon: { self.filledIcon }) + FilledIcon(.init(filledIcon: .init(self.filledIcon))) .shouldApplyDefaultStyle(false) .filledIconStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Footnote/Footnote.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Footnote/Footnote.generated.swift index c77079afd..a1e8526ae 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Footnote/Footnote.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Footnote/Footnote.generated.swift @@ -55,7 +55,7 @@ private extension Footnote { } func defaultStyle() -> some View { - Footnote(footnote: { self.footnote }) + Footnote(.init(footnote: .init(self.footnote))) .shouldApplyDefaultStyle(false) .footnoteStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIcons/FootnoteIcons.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIcons/FootnoteIcons.generated.swift index b7512728a..f32dc506b 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIcons/FootnoteIcons.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIcons/FootnoteIcons.generated.swift @@ -55,7 +55,7 @@ private extension FootnoteIcons { } func defaultStyle() -> some View { - FootnoteIcons(footnoteIcons: { self.footnoteIcons }) + FootnoteIcons(.init(footnoteIcons: .init(self.footnoteIcons))) .shouldApplyDefaultStyle(false) .footnoteIconsStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsText.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsText.generated.swift index 4fc6b540a..fbda6091e 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsText.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/FootnoteIconsText/FootnoteIconsText.generated.swift @@ -55,7 +55,7 @@ private extension FootnoteIconsText { } func defaultStyle() -> some View { - FootnoteIconsText(footnoteIconsText: { self.footnoteIconsText }) + FootnoteIconsText(.init(footnoteIconsText: .init(self.footnoteIconsText))) .shouldApplyDefaultStyle(false) .footnoteIconsTextStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/GreetingText/GreetingText.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/GreetingText/GreetingText.generated.swift index 62910dd96..d68cb15e0 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/GreetingText/GreetingText.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/GreetingText/GreetingText.generated.swift @@ -55,7 +55,7 @@ private extension GreetingText { } func defaultStyle() -> some View { - GreetingText(greetingText: { self.greetingText }) + GreetingText(.init(greetingText: .init(self.greetingText))) .shouldApplyDefaultStyle(false) .greetingTextStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/HeaderAction/HeaderAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/HeaderAction/HeaderAction.generated.swift index caf9b4d31..056c8c21d 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/HeaderAction/HeaderAction.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/HeaderAction/HeaderAction.generated.swift @@ -55,7 +55,7 @@ private extension HeaderAction { } func defaultStyle() -> some View { - HeaderAction(headerAction: { self.headerAction }) + HeaderAction(.init(headerAction: .init(self.headerAction))) .shouldApplyDefaultStyle(false) .headerActionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/HelperText/HelperText.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/HelperText/HelperText.generated.swift index 2e927728a..f64680262 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/HelperText/HelperText.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/HelperText/HelperText.generated.swift @@ -55,7 +55,7 @@ private extension HelperText { } func defaultStyle() -> some View { - HelperText(helperText: { self.helperText }) + HelperText(.init(helperText: .init(self.helperText))) .shouldApplyDefaultStyle(false) .helperTextStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Icon/Icon.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Icon/Icon.generated.swift index 321375497..2e83ffa87 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Icon/Icon.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Icon/Icon.generated.swift @@ -55,7 +55,7 @@ private extension Icon { } func defaultStyle() -> some View { - Icon(icon: { self.icon }) + Icon(.init(icon: .init(self.icon))) .shouldApplyDefaultStyle(false) .iconStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Icons/Icons.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Icons/Icons.generated.swift index 8251378ff..d6f209d9a 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Icons/Icons.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Icons/Icons.generated.swift @@ -55,7 +55,7 @@ private extension Icons { } func defaultStyle() -> some View { - Icons(icons: { self.icons }) + Icons(.init(icons: .init(self.icons))) .shouldApplyDefaultStyle(false) .iconsStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessage.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessage.generated.swift index 64b766559..ebaa49c34 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessage.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/IllustratedMessage/IllustratedMessage.generated.swift @@ -29,11 +29,11 @@ public struct IllustratedMessage { isActionVerticallyAligned: Bool = false, contentAlignment: HorizontalAlignment = .leading) { - self.detailImage = DetailImage { detailImage() } - self.title = Title { title() } - self.description = Description { description() } - self.action = Action { action() } - self.secondaryAction = SecondaryAction { secondaryAction() } + self.detailImage = DetailImage(detailImage: detailImage) + self.title = Title(title: title) + self.description = Description(description: description) + self.action = Action(action: action) + self.secondaryAction = SecondaryAction(secondaryAction: secondaryAction) self.detailImageSize = detailImageSize self.isActionVerticallyAligned = isActionVerticallyAligned self.contentAlignment = contentAlignment diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/IncrementAction/IncrementAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/IncrementAction/IncrementAction.generated.swift index 1d6d71273..8676c64ef 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/IncrementAction/IncrementAction.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/IncrementAction/IncrementAction.generated.swift @@ -57,7 +57,7 @@ private extension IncrementAction { } func defaultStyle() -> some View { - IncrementAction(incrementAction: { self.incrementAction }) + IncrementAction(.init(incrementAction: .init(self.incrementAction))) .shouldApplyDefaultStyle(false) .incrementActionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/InformationView/InformationView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/InformationView/InformationView.generated.swift index f0270bc12..2ffc7ae82 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/InformationView/InformationView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/InformationView/InformationView.generated.swift @@ -14,8 +14,8 @@ public struct InformationView { public init(@ViewBuilder icon: () -> any View = { EmptyView() }, @ViewBuilder description: () -> any View = { EmptyView() }) { - self.icon = Icon { icon() } - self.description = Description { description() } + self.icon = Icon(icon: icon) + self.description = Description(description: description) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/JouleWelcomeScreen/JouleWelcomeScreen.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/JouleWelcomeScreen/JouleWelcomeScreen.generated.swift index 0f00a2f56..73650848d 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/JouleWelcomeScreen/JouleWelcomeScreen.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/JouleWelcomeScreen/JouleWelcomeScreen.generated.swift @@ -20,11 +20,11 @@ public struct JouleWelcomeScreen { @ViewBuilder footnote: () -> any View = { EmptyView() }, @ViewBuilder messageContent: () -> any View = { EmptyView() }) { - self.mediaImage = MediaImage { mediaImage() } - self.greetingText = GreetingText { greetingText() } - self.title = Title { title() } - self.footnote = Footnote { footnote() } - self.messageContent = MessageContent { messageContent() } + self.mediaImage = MediaImage(mediaImage: mediaImage) + self.greetingText = GreetingText(greetingText: greetingText) + self.title = Title(title: title) + self.footnote = Footnote(footnote: footnote) + self.messageContent = MessageContent(messageContent: messageContent) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/KeyValueFormView/KeyValueFormView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/KeyValueFormView/KeyValueFormView.generated.swift index f3f732187..1645669a4 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/KeyValueFormView/KeyValueFormView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/KeyValueFormView/KeyValueFormView.generated.swift @@ -53,9 +53,9 @@ public struct KeyValueFormView { @ViewBuilder mandatoryFieldIndicator: () -> any View = { EmptyView() }, isRequired: Bool = false) { - self.title = Title { title() } + self.title = Title(title: title) self._text = text - self.placeholder = Placeholder { placeholder() } + self.placeholder = Placeholder(placeholder: placeholder) self.controlState = controlState self.errorMessage = errorMessage self.minTextEditorHeight = minTextEditorHeight @@ -67,7 +67,7 @@ public struct KeyValueFormView { self.allowsBeyondLimit = allowsBeyondLimit self.charCountReachLimitMessage = charCountReachLimitMessage self.charCountBeyondLimitMsg = charCountBeyondLimitMsg - self.mandatoryFieldIndicator = MandatoryFieldIndicator { mandatoryFieldIndicator() } + self.mandatoryFieldIndicator = MandatoryFieldIndicator(mandatoryFieldIndicator: mandatoryFieldIndicator) self.isRequired = isRequired } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Kpi/Kpi.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Kpi/Kpi.generated.swift index f03176cfa..2daa06f8d 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Kpi/Kpi.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Kpi/Kpi.generated.swift @@ -55,7 +55,7 @@ private extension Kpi { } func defaultStyle() -> some View { - Kpi(kpi: { self.kpi }) + Kpi(.init(kpi: .init(self.kpi))) .shouldApplyDefaultStyle(false) .kpiStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/KpiCaption/KpiCaption.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/KpiCaption/KpiCaption.generated.swift index b7e87464a..2af80ec31 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/KpiCaption/KpiCaption.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/KpiCaption/KpiCaption.generated.swift @@ -55,7 +55,7 @@ private extension KpiCaption { } func defaultStyle() -> some View { - KpiCaption(kpiCaption: { self.kpiCaption }) + KpiCaption(.init(kpiCaption: .init(self.kpiCaption))) .shouldApplyDefaultStyle(false) .kpiCaptionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/LabelItem/LabelItem.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/LabelItem/LabelItem.generated.swift index 77508dafe..4e61f4cb0 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/LabelItem/LabelItem.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/LabelItem/LabelItem.generated.swift @@ -17,8 +17,8 @@ public struct LabelItem { @ViewBuilder title: () -> any View, alignment: HorizontalAlignment? = nil) { - self.icon = Icon { icon() } - self.title = Title { title() } + self.icon = Icon(icon: icon) + self.title = Title(title: title) self.alignment = alignment } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/LinearProgressIndicator/LinearProgressIndicator.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/LinearProgressIndicator/LinearProgressIndicator.generated.swift index 7c779dd56..260c16381 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/LinearProgressIndicator/LinearProgressIndicator.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/LinearProgressIndicator/LinearProgressIndicator.generated.swift @@ -49,7 +49,7 @@ private extension LinearProgressIndicator { } func defaultStyle() -> some View { - LinearProgressIndicator(indicatorProgress: self.$indicatorProgress) + LinearProgressIndicator(.init(indicatorProgress: self.$indicatorProgress)) .shouldApplyDefaultStyle(false) .linearProgressIndicatorStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/LinearProgressIndicatorView/LinearProgressIndicatorView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/LinearProgressIndicatorView/LinearProgressIndicatorView.generated.swift index d3ab70060..9e98aa7f6 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/LinearProgressIndicatorView/LinearProgressIndicatorView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/LinearProgressIndicatorView/LinearProgressIndicatorView.generated.swift @@ -17,8 +17,8 @@ public struct LinearProgressIndicatorView { @ViewBuilder description: () -> any View = { EmptyView() }) { self._indicatorProgress = indicatorProgress - self.icon = Icon { icon() } - self.description = Description { description() } + self.icon = Icon(icon: icon) + self.description = Description(description: description) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MandatoryFieldIndicator/MandatoryFieldIndicator.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MandatoryFieldIndicator/MandatoryFieldIndicator.generated.swift index 2615db2e0..27d75915d 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MandatoryFieldIndicator/MandatoryFieldIndicator.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MandatoryFieldIndicator/MandatoryFieldIndicator.generated.swift @@ -55,7 +55,7 @@ private extension MandatoryFieldIndicator { } func defaultStyle() -> some View { - MandatoryFieldIndicator(mandatoryFieldIndicator: { self.mandatoryFieldIndicator }) + MandatoryFieldIndicator(.init(mandatoryFieldIndicator: .init(self.mandatoryFieldIndicator))) .shouldApplyDefaultStyle(false) .mandatoryFieldIndicatorStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MediaImage/MediaImage.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MediaImage/MediaImage.generated.swift index e42bfba1a..ba8d02b12 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MediaImage/MediaImage.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MediaImage/MediaImage.generated.swift @@ -55,7 +55,7 @@ private extension MediaImage { } func defaultStyle() -> some View { - MediaImage(mediaImage: { self.mediaImage }) + MediaImage(.init(mediaImage: .init(self.mediaImage))) .shouldApplyDefaultStyle(false) .mediaImageStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MenuSelection/MenuSelection.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MenuSelection/MenuSelection.generated.swift index ed109a72a..738cfbf53 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MenuSelection/MenuSelection.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MenuSelection/MenuSelection.generated.swift @@ -16,7 +16,7 @@ public struct MenuSelection { isExpanded: Binding, @ViewBuilder items: () -> any View = { EmptyView() }) { - self.action = Action { action() } + self.action = Action(action: action) self._isExpanded = isExpanded self.items = items() } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MenuSelectionItem/MenuSelectionItem.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MenuSelectionItem/MenuSelectionItem.generated.swift index 1da78a408..c6cea4649 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MenuSelectionItem/MenuSelectionItem.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MenuSelectionItem/MenuSelectionItem.generated.swift @@ -16,8 +16,8 @@ public struct MenuSelectionItem { @ViewBuilder title: () -> any View, action: (() -> Void)? = nil) { - self.icon = Icon { icon() } - self.title = Title { title() } + self.icon = Icon(icon: icon) + self.title = Title(title: title) self.action = action } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MessageContent/MessageContent.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MessageContent/MessageContent.generated.swift index d33a89b66..d8602b68b 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MessageContent/MessageContent.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MessageContent/MessageContent.generated.swift @@ -49,7 +49,7 @@ private extension MessageContent { } func defaultStyle() -> some View { - MessageContent(messageContent: { self.messageContent }) + MessageContent(.init(messageContent: .init(self.messageContent))) .shouldApplyDefaultStyle(false) .messageContentStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MoreActionOverflow/MoreActionOverflow.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MoreActionOverflow/MoreActionOverflow.generated.swift index 26a882dce..2ce4cd6d0 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/MoreActionOverflow/MoreActionOverflow.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/MoreActionOverflow/MoreActionOverflow.generated.swift @@ -49,7 +49,7 @@ private extension MoreActionOverflow { } func defaultStyle() -> some View { - MoreActionOverflow(moreActionOverflow: { self.moreActionOverflow }) + MoreActionOverflow(.init(moreActionOverflow: .init(self.moreActionOverflow))) .shouldApplyDefaultStyle(false) .moreActionOverflowStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/NoteFormView/NoteFormView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/NoteFormView/NoteFormView.generated.swift index 8b31ed3d9..75c208102 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/NoteFormView/NoteFormView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/NoteFormView/NoteFormView.generated.swift @@ -48,7 +48,7 @@ public struct NoteFormView { charCountBeyondLimitMsg: String? = nil) { self._text = text - self.placeholder = Placeholder { placeholder() } + self.placeholder = Placeholder(placeholder: placeholder) self.controlState = controlState self.errorMessage = errorMessage self.minTextEditorHeight = minTextEditorHeight diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/NowIndicatorNode/NowIndicatorNode.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/NowIndicatorNode/NowIndicatorNode.generated.swift index 8cf37e8fe..a9a77c8c1 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/NowIndicatorNode/NowIndicatorNode.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/NowIndicatorNode/NowIndicatorNode.generated.swift @@ -49,7 +49,7 @@ private extension NowIndicatorNode { } func defaultStyle() -> some View { - NowIndicatorNode(nowIndicatorNode: { self.nowIndicatorNode }) + NowIndicatorNode(.init(nowIndicatorNode: .init(self.nowIndicatorNode))) .shouldApplyDefaultStyle(false) .nowIndicatorNodeStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift index 1b319d574..cbfea9124 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift @@ -37,19 +37,19 @@ public struct ObjectItem { @TagBuilder tags: () -> any View = { EmptyView() }, @ViewBuilder action: () -> any View = { EmptyView() }) { - self.title = Title { title() } - self.subtitle = Subtitle { subtitle() } - self.footnote = Footnote { footnote() } - self.description = Description { description() } - self.status = Status { status() } - self.substatus = Substatus { substatus() } - self.detailImage = DetailImage { detailImage() } - self.icons = Icons { icons() } - self.avatars = Avatars { avatars() } - self.footnoteIcons = FootnoteIcons { footnoteIcons() } - self.footnoteIconsText = FootnoteIconsText { footnoteIconsText() } - self.tags = Tags { tags() } - self.action = Action { action() } + self.title = Title(title: title) + self.subtitle = Subtitle(subtitle: subtitle) + self.footnote = Footnote(footnote: footnote) + self.description = Description(description: description) + self.status = Status(status: status) + self.substatus = Substatus(substatus: substatus) + self.detailImage = DetailImage(detailImage: detailImage) + self.icons = Icons(icons: icons) + self.avatars = Avatars(avatars: avatars) + self.footnoteIcons = FootnoteIcons(footnoteIcons: footnoteIcons) + self.footnoteIconsText = FootnoteIconsText(footnoteIconsText: footnoteIconsText) + self.tags = Tags(tags: tags) + self.action = Action(action: action) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Options/Options.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Options/Options.generated.swift index b36773154..232e2db63 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Options/Options.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Options/Options.generated.swift @@ -49,7 +49,7 @@ private extension Options { } func defaultStyle() -> some View { - Options(options: self.options) + Options(.init(options: self.options)) .shouldApplyDefaultStyle(false) .optionsStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowAction.generated.swift index 1e606dc3e..608dd9324 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowAction.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OverflowAction/OverflowAction.generated.swift @@ -55,7 +55,7 @@ private extension OverflowAction { } func defaultStyle() -> some View { - OverflowAction(overflowAction: { self.overflowAction }) + OverflowAction(.init(overflowAction: .init(self.overflowAction))) .shouldApplyDefaultStyle(false) .overflowActionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Placeholder/Placeholder.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Placeholder/Placeholder.generated.swift index 4ee99ce26..9383b2767 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Placeholder/Placeholder.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Placeholder/Placeholder.generated.swift @@ -55,7 +55,7 @@ private extension Placeholder { } func defaultStyle() -> some View { - Placeholder(placeholder: { self.placeholder }) + Placeholder(.init(placeholder: .init(self.placeholder))) .shouldApplyDefaultStyle(false) .placeholderStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/PlaceholderTextEditor/PlaceholderTextEditor.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/PlaceholderTextEditor/PlaceholderTextEditor.generated.swift index 51847d45f..c4608ce5e 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/PlaceholderTextEditor/PlaceholderTextEditor.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/PlaceholderTextEditor/PlaceholderTextEditor.generated.swift @@ -15,7 +15,7 @@ public struct PlaceholderTextEditor { @ViewBuilder placeholder: () -> any View = { EmptyView() }) { self._text = text - self.placeholder = Placeholder { placeholder() } + self.placeholder = Placeholder(placeholder: placeholder) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/PlaceholderTextField/PlaceholderTextField.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/PlaceholderTextField/PlaceholderTextField.generated.swift index 3de9be7a7..46fa2e817 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/PlaceholderTextField/PlaceholderTextField.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/PlaceholderTextField/PlaceholderTextField.generated.swift @@ -15,7 +15,7 @@ public struct PlaceholderTextField { @ViewBuilder placeholder: () -> any View = { EmptyView() }) { self._text = text - self.placeholder = Placeholder { placeholder() } + self.placeholder = Placeholder(placeholder: placeholder) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ProfileHeader/ProfileHeader.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ProfileHeader/ProfileHeader.generated.swift index 2a18c62c3..25950855c 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ProfileHeader/ProfileHeader.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ProfileHeader/ProfileHeader.generated.swift @@ -24,10 +24,10 @@ public struct ProfileHeader { animatable: Bool = false, @ViewBuilder detailContent: () -> any View = { EmptyView() }) { - self.detailImage = DetailImage { detailImage() } - self.title = Title { title() } - self.subtitle = Subtitle { subtitle() } - self.description = Description { description() } + self.detailImage = DetailImage(detailImage: detailImage) + self.title = Title(title: title) + self.subtitle = Subtitle(subtitle: subtitle) + self.description = Description(description: description) self.animatable = animatable self.detailContent = detailContent() } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormView.generated.swift index 4749860c8..089409e86 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormView.generated.swift @@ -51,7 +51,7 @@ public struct RatingControlFormView { errorMessage: AttributedString? = nil, axis: Axis = .horizontal) { - self.title = Title { title() } + self.title = Title(title: title) self._rating = rating self.ratingControlStyle = ratingControlStyle self.ratingBounds = ratingBounds @@ -61,7 +61,7 @@ public struct RatingControlFormView { self.onColor = onColor self.offColor = offColor self.interItemSpacing = interItemSpacing - self.subtitle = Subtitle { subtitle() } + self.subtitle = Subtitle(subtitle: subtitle) self.controlState = controlState self.errorMessage = errorMessage self.axis = axis diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row1/Row1.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row1/Row1.generated.swift index 747df46b3..5eba498e4 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row1/Row1.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row1/Row1.generated.swift @@ -49,7 +49,7 @@ private extension Row1 { } func defaultStyle() -> some View { - Row1(row1: { self.row1 }) + Row1(.init(row1: .init(self.row1))) .shouldApplyDefaultStyle(false) .row1Style(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row2/Row2.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row2/Row2.generated.swift index b6c79bd1c..396ae97f0 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row2/Row2.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row2/Row2.generated.swift @@ -49,7 +49,7 @@ private extension Row2 { } func defaultStyle() -> some View { - Row2(row2: { self.row2 }) + Row2(.init(row2: .init(self.row2))) .shouldApplyDefaultStyle(false) .row2Style(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row3/Row3.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row3/Row3.generated.swift index 00fd8ff7f..416661c82 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row3/Row3.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Row3/Row3.generated.swift @@ -49,7 +49,7 @@ private extension Row3 { } func defaultStyle() -> some View { - Row3(row3: { self.row3 }) + Row3(.init(row3: .init(self.row3))) .shouldApplyDefaultStyle(false) .row3Style(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SecondaryAction/SecondaryAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SecondaryAction/SecondaryAction.generated.swift index a676b376c..a40f255ab 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SecondaryAction/SecondaryAction.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SecondaryAction/SecondaryAction.generated.swift @@ -55,7 +55,7 @@ private extension SecondaryAction { } func defaultStyle() -> some View { - SecondaryAction(secondaryAction: { self.secondaryAction }) + SecondaryAction(.init(secondaryAction: .init(self.secondaryAction))) .shouldApplyDefaultStyle(false) .secondaryActionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SecondaryTimestamp/SecondaryTimestamp.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SecondaryTimestamp/SecondaryTimestamp.generated.swift index c5d59840d..96c34fc8a 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SecondaryTimestamp/SecondaryTimestamp.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SecondaryTimestamp/SecondaryTimestamp.generated.swift @@ -55,7 +55,7 @@ private extension SecondaryTimestamp { } func defaultStyle() -> some View { - SecondaryTimestamp(secondaryTimestamp: { self.secondaryTimestamp }) + SecondaryTimestamp(.init(secondaryTimestamp: .init(self.secondaryTimestamp))) .shouldApplyDefaultStyle(false) .secondaryTimestampStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SideBarListItem/SideBarListItem.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SideBarListItem/SideBarListItem.generated.swift index cedd856d3..c1dd89665 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SideBarListItem/SideBarListItem.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SideBarListItem/SideBarListItem.generated.swift @@ -33,11 +33,11 @@ public struct SideBarListItem { data: SideBarItemModel, isSelected: Binding) { - self.icon = Icon { icon() } - self.filledIcon = FilledIcon { filledIcon() } - self.title = Title { title() } - self.subtitle = Subtitle { subtitle() } - self.accessoryIcon = AccessoryIcon { accessoryIcon() } + self.icon = Icon(icon: icon) + self.filledIcon = FilledIcon(filledIcon: filledIcon) + self.title = Title(title: title) + self.subtitle = Subtitle(subtitle: subtitle) + self.accessoryIcon = AccessoryIcon(accessoryIcon: accessoryIcon) self._isOn = isOn self.data = data self._isSelected = isSelected diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Status/Status.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Status/Status.generated.swift index 2f4024e2a..79d3b8b4e 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Status/Status.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Status/Status.generated.swift @@ -55,7 +55,7 @@ private extension Status { } func defaultStyle() -> some View { - Status(status: { self.status }) + Status(.init(status: .init(self.status))) .shouldApplyDefaultStyle(false) .statusStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift index ac6f398ff..5664540e7 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift @@ -24,9 +24,9 @@ public struct StepperField { step: Int? = nil, stepRange: ClosedRange) { - self.decrementAction = DecrementAction { decrementAction() } + self.decrementAction = DecrementAction(decrementAction: decrementAction) self._text = text - self.incrementAction = IncrementAction { incrementAction() } + self.incrementAction = IncrementAction(incrementAction: incrementAction) self.step = step self.stepRange = stepRange } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift index 8ff222e54..b1cbda8ed 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift @@ -30,14 +30,14 @@ public struct StepperView { @ViewBuilder icon: () -> any View = { EmptyView() }, @ViewBuilder description: () -> any View = { EmptyView() }) { - self.title = Title { title() } - self.decrementAction = DecrementAction { decrementAction() } + self.title = Title(title: title) + self.decrementAction = DecrementAction(decrementAction: decrementAction) self._text = text - self.incrementAction = IncrementAction { incrementAction() } + self.incrementAction = IncrementAction(incrementAction: incrementAction) self.step = step self.stepRange = stepRange - self.icon = Icon { icon() } - self.description = Description { description() } + self.icon = Icon(icon: icon) + self.description = Description(description: description) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SubAttribute/SubAttribute.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SubAttribute/SubAttribute.generated.swift index e114148ab..97d3499bf 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SubAttribute/SubAttribute.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SubAttribute/SubAttribute.generated.swift @@ -55,7 +55,7 @@ private extension SubAttribute { } func defaultStyle() -> some View { - SubAttribute(subAttribute: { self.subAttribute }) + SubAttribute(.init(subAttribute: .init(self.subAttribute))) .shouldApplyDefaultStyle(false) .subAttributeStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Substatus/Substatus.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Substatus/Substatus.generated.swift index ec176eaef..a52a898f1 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Substatus/Substatus.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Substatus/Substatus.generated.swift @@ -55,7 +55,7 @@ private extension Substatus { } func defaultStyle() -> some View { - Substatus(substatus: { self.substatus }) + Substatus(.init(substatus: .init(self.substatus))) .shouldApplyDefaultStyle(false) .substatusStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Subtitle/Subtitle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Subtitle/Subtitle.generated.swift index 82a94730b..d1bc90f0a 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Subtitle/Subtitle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Subtitle/Subtitle.generated.swift @@ -55,7 +55,7 @@ private extension Subtitle { } func defaultStyle() -> some View { - Subtitle(subtitle: { self.subtitle }) + Subtitle(.init(subtitle: .init(self.subtitle))) .shouldApplyDefaultStyle(false) .subtitleStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Switch/Switch.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Switch/Switch.generated.swift index d546588b0..87649b17e 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Switch/Switch.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Switch/Switch.generated.swift @@ -49,7 +49,7 @@ private extension Switch { } func defaultStyle() -> some View { - Switch(isOn: self.$isOn) + Switch(.init(isOn: self.$isOn)) .shouldApplyDefaultStyle(false) .switchStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchView.generated.swift index 5c0582391..25d947766 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/SwitchView/SwitchView.generated.swift @@ -22,7 +22,7 @@ public struct SwitchView { public init(@ViewBuilder title: () -> any View, isOn: Binding) { - self.title = Title { title() } + self.title = Title(title: title) self._isOn = isOn } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Tags/Tags.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Tags/Tags.generated.swift index 52ec2ee66..c7d058c17 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Tags/Tags.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Tags/Tags.generated.swift @@ -55,7 +55,7 @@ private extension Tags { } func defaultStyle() -> some View { - Tags(tags: { self.tags }) + Tags(.init(tags: .init(self.tags))) .shouldApplyDefaultStyle(false) .tagsStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryAction.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryAction.generated.swift index a5b734426..36c4183fe 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryAction.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TertiaryAction/TertiaryAction.generated.swift @@ -55,7 +55,7 @@ private extension TertiaryAction { } func defaultStyle() -> some View { - TertiaryAction(tertiaryAction: { self.tertiaryAction }) + TertiaryAction(.init(tertiaryAction: .init(self.tertiaryAction))) .shouldApplyDefaultStyle(false) .tertiaryActionStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextFieldFormView/TextFieldFormView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextFieldFormView/TextFieldFormView.generated.swift index 65377a279..df2dd9370 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextFieldFormView/TextFieldFormView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextFieldFormView/TextFieldFormView.generated.swift @@ -53,9 +53,9 @@ public struct TextFieldFormView { actionIcon: Image? = nil, action: (() -> Void)? = nil) { - self.title = Title { title() } + self.title = Title(title: title) self._text = text - self.placeholder = Placeholder { placeholder() } + self.placeholder = Placeholder(placeholder: placeholder) self.controlState = controlState self.errorMessage = errorMessage self.maxTextLength = maxTextLength @@ -65,7 +65,7 @@ public struct TextFieldFormView { self.allowsBeyondLimit = allowsBeyondLimit self.charCountReachLimitMessage = charCountReachLimitMessage self.charCountBeyondLimitMsg = charCountBeyondLimitMsg - self.mandatoryFieldIndicator = MandatoryFieldIndicator { mandatoryFieldIndicator() } + self.mandatoryFieldIndicator = MandatoryFieldIndicator(mandatoryFieldIndicator: mandatoryFieldIndicator) self.isRequired = isRequired self.actionIcon = actionIcon self.action = action diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextInputField/TextInputField.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextInputField/TextInputField.generated.swift index 8d842bb36..5d91f934f 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextInputField/TextInputField.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextInputField/TextInputField.generated.swift @@ -49,7 +49,7 @@ private extension TextInputField { } func defaultStyle() -> some View { - TextInputField(text: self.$text) + TextInputField(.init(text: self.$text)) .shouldApplyDefaultStyle(false) .textInputFieldStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextInputInfoView/TextInputInfoView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextInputInfoView/TextInputInfoView.generated.swift index 2e74ae645..691d449f3 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextInputInfoView/TextInputInfoView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextInputInfoView/TextInputInfoView.generated.swift @@ -16,9 +16,9 @@ struct TextInputInfoView { @ViewBuilder description: () -> any View = { EmptyView() }, @ViewBuilder counter: () -> any View = { EmptyView() }) { - self.icon = Icon { icon() } - self.description = Description { description() } - self.counter = Counter { counter() } + self.icon = Icon(icon: icon) + self.description = Description(description: description) + self.counter = Counter(counter: counter) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextView/TextView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextView/TextView.generated.swift index af658d776..0edd1ec3a 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextView/TextView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TextView/TextView.generated.swift @@ -49,7 +49,7 @@ private extension TextView { } func defaultStyle() -> some View { - TextView(text: self.$text) + TextView(.init(text: self.$text)) .shouldApplyDefaultStyle(false) .textViewStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Timeline/Timeline.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Timeline/Timeline.generated.swift index 611f769ec..0a6ac692c 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Timeline/Timeline.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Timeline/Timeline.generated.swift @@ -48,16 +48,16 @@ public struct Timeline { isPast: Bool = false, isPresent: Bool = false) { - self.timestamp = Timestamp { timestamp() } - self.secondaryTimestamp = SecondaryTimestamp { secondaryTimestamp() } - self.timelineNode = TimelineNode { timelineNode() } - self.icon = Icon { icon() } - self.title = Title { title() } - self.subtitle = Subtitle { subtitle() } - self.attribute = Attribute { attribute() } - self.status = Status { status() } - self.substatus = Substatus { substatus() } - self.subAttribute = SubAttribute { subAttribute() } + self.timestamp = Timestamp(timestamp: timestamp) + self.secondaryTimestamp = SecondaryTimestamp(secondaryTimestamp: secondaryTimestamp) + self.timelineNode = TimelineNode(timelineNode: timelineNode) + self.icon = Icon(icon: icon) + self.title = Title(title: title) + self.subtitle = Subtitle(subtitle: subtitle) + self.attribute = Attribute(attribute: attribute) + self.status = Status(status: status) + self.substatus = Substatus(substatus: substatus) + self.subAttribute = SubAttribute(subAttribute: subAttribute) self.isPast = isPast self.isPresent = isPresent } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineMarker/TimelineMarker.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineMarker/TimelineMarker.generated.swift index dbd1886c8..d54dda8ed 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineMarker/TimelineMarker.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineMarker/TimelineMarker.generated.swift @@ -44,11 +44,11 @@ public struct TimelineMarker { showUpperVerticalLine: Bool = true, showLowerVerticalLine: Bool = true) { - self.timestamp = Timestamp { timestamp() } - self.secondaryTimestamp = SecondaryTimestamp { secondaryTimestamp() } - self.timelineNode = TimelineNode { timelineNode() } - self.icon = Icon { icon() } - self.title = Title { title() } + self.timestamp = Timestamp(timestamp: timestamp) + self.secondaryTimestamp = SecondaryTimestamp(secondaryTimestamp: secondaryTimestamp) + self.timelineNode = TimelineNode(timelineNode: timelineNode) + self.icon = Icon(icon: icon) + self.title = Title(title: title) self.isPast = isPast self.isPresent = isPresent self.showUpperVerticalLine = showUpperVerticalLine diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineNode/TimelineNode.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineNode/TimelineNode.generated.swift index 73294ec30..7346a708a 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineNode/TimelineNode.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineNode/TimelineNode.generated.swift @@ -55,7 +55,7 @@ private extension TimelineNode { } func defaultStyle() -> some View { - TimelineNode(timelineNode: { self.timelineNode }) + TimelineNode(.init(timelineNode: .init(self.timelineNode))) .shouldApplyDefaultStyle(false) .timelineNodeStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineNowIndicator/TimelineNowIndicator.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineNowIndicator/TimelineNowIndicator.generated.swift index 031ae9e23..e4ec9a7cd 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineNowIndicator/TimelineNowIndicator.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelineNowIndicator/TimelineNowIndicator.generated.swift @@ -17,7 +17,7 @@ public struct TimelineNowIndicator { fileprivate var _shouldApplyDefaultStyle = true public init(@ViewBuilder nowIndicatorNode: () -> any View = { Image(systemName: "circle.fill") }) { - self.nowIndicatorNode = NowIndicatorNode { nowIndicatorNode() } + self.nowIndicatorNode = NowIndicatorNode(nowIndicatorNode: nowIndicatorNode) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Timestamp/Timestamp.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Timestamp/Timestamp.generated.swift index 6157dd6fe..df3b87b35 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Timestamp/Timestamp.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Timestamp/Timestamp.generated.swift @@ -55,7 +55,7 @@ private extension Timestamp { } func defaultStyle() -> some View { - Timestamp(timestamp: { self.timestamp }) + Timestamp(.init(timestamp: .init(self.timestamp))) .shouldApplyDefaultStyle(false) .timestampStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Title/Title.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Title/Title.generated.swift index b2fa783a9..6252fe541 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Title/Title.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Title/Title.generated.swift @@ -55,7 +55,7 @@ private extension Title { } func defaultStyle() -> some View { - Title(title: { self.title }) + Title(.init(title: .init(self.title))) .shouldApplyDefaultStyle(false) .titleStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TitleFormView/TitleFormView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TitleFormView/TitleFormView.generated.swift index 191b5f232..97c938d71 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TitleFormView/TitleFormView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TitleFormView/TitleFormView.generated.swift @@ -42,7 +42,7 @@ public struct TitleFormView { charCountBeyondLimitMsg: String? = nil) { self._text = text - self.placeholder = Placeholder { placeholder() } + self.placeholder = Placeholder(placeholder: placeholder) self.controlState = controlState self.errorMessage = errorMessage self.maxTextLength = maxTextLength diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TopDivider/TopDivider.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TopDivider/TopDivider.generated.swift index b056e2419..07110305c 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TopDivider/TopDivider.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TopDivider/TopDivider.generated.swift @@ -49,7 +49,7 @@ private extension TopDivider { } func defaultStyle() -> some View { - TopDivider(topDivider: { self.topDivider }) + TopDivider(.init(topDivider: .init(self.topDivider))) .shouldApplyDefaultStyle(false) .topDividerStyle(.fiori) .typeErased diff --git a/sourcery/.lib/.swiftpm/configuration/Package.resolved b/sourcery/.lib/.swiftpm/configuration/Package.resolved new file mode 100644 index 000000000..57eeb043f --- /dev/null +++ b/sourcery/.lib/.swiftpm/configuration/Package.resolved @@ -0,0 +1,185 @@ +{ + "pins" : [ + { + "identity" : "aexml", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tadija/AEXML.git", + "state" : { + "revision" : "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3", + "version" : "4.6.1" + } + }, + { + "identity" : "commander", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kylef/Commander.git", + "state" : { + "revision" : "4b6133c3071d521489a80c38fb92d7983f19d438", + "version" : "0.9.1" + } + }, + { + "identity" : "pathkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kylef/PathKit.git", + "state" : { + "revision" : "3bfd2737b700b9a36565a8c94f4ad2b050a5e574", + "version" : "1.0.1" + } + }, + { + "identity" : "sourcery", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzysztofzablocki/Sourcery.git", + "state" : { + "branch" : "2.2.5", + "revision" : "070d0b1f6c839b85a5bf1330a6c3ed49362ef96c" + } + }, + { + "identity" : "spectre", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kylef/Spectre.git", + "state" : { + "revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7", + "version" : "0.10.1" + } + }, + { + "identity" : "stencil", + "kind" : "remoteSourceControl", + "location" : "https://github.com/art-divin/Stencil.git", + "state" : { + "revision" : "03a1dca8923bef5a34c08f5a560fb420326f7244", + "version" : "0.15.3" + } + }, + { + "identity" : "stencilswiftkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/art-divin/StencilSwiftKit.git", + "state" : { + "revision" : "6b07f85def6984e7015d65502d41b91f3f8db6d5", + "version" : "2.10.4" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" + } + }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "df5d2fcd22e3f480e3ef85bf23e277a4a0ef524d", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-certificates", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-certificates.git", + "state" : { + "revision" : "01d7664523af5c169f26038f1e5d444ce47ae5ff", + "version" : "1.0.1" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "d029d9d39c87bed85b1c50adee7c41795261a192", + "version" : "1.0.6" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "629f0b679d0fd0a6ae823d7f750b9ab032c00b80", + "version" : "3.0.0" + } + }, + { + "identity" : "swift-driver", + "kind" : "remoteSourceControl", + "location" : "https://github.com/art-divin/swift-driver.git", + "state" : { + "revision" : "1c07ced84c1dfc1f9c3253dcbaa216fc9c76ee25", + "version" : "1.0.1" + } + }, + { + "identity" : "swift-llbuild", + "kind" : "remoteSourceControl", + "location" : "https://github.com/art-divin/swift-llbuild.git", + "state" : { + "revision" : "783aec21649a6c47d1a8314db4144bdceb11df30", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-package-manager", + "kind" : "remoteSourceControl", + "location" : "https://github.com/art-divin/swift-package-manager.git", + "state" : { + "revision" : "7df9321541e544d711dd93f5b97dd4e8bf7e100e", + "version" : "1.0.8" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "2bc86522d115234d1f588efe2bcb4ce4be8f8b82", + "version" : "510.0.3" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "836bc4557b74fe6d2660218d56e3ce96aff76574", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-tools-support-core", + "kind" : "remoteSourceControl", + "location" : "https://github.com/art-divin/swift-tools-support-core.git", + "state" : { + "revision" : "930e82e5ae2432c71fe05f440b5d778285270bdb", + "version" : "1.0.0" + } + }, + { + "identity" : "xcodeproj", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tuist/XcodeProj.git", + "state" : { + "revision" : "447c159b0c5fb047a024fd8d942d4a76cf47dde0", + "version" : "8.16.0" + } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3", + "version" : "5.0.6" + } + } + ], + "version" : 2 +} diff --git a/sourcery/.lib/Sources/utils/ForStyleableComponent/Array+Extension.swift b/sourcery/.lib/Sources/utils/ForStyleableComponent/Array+Extension.swift index acc44cf97..b5c253075 100644 --- a/sourcery/.lib/Sources/utils/ForStyleableComponent/Array+Extension.swift +++ b/sourcery/.lib/Sources/utils/ForStyleableComponent/Array+Extension.swift @@ -50,7 +50,7 @@ extension [Variable] { map { variable in let name = variable.name if variable.isResultBuilder { - let assignment = isBaseComponent || !variable.isStyleable ? "\(name)()" : "\(name.capitalizingFirst()) { \(name)() }" + let assignment = isBaseComponent || !variable.isStyleable ? "\(name)()" : "\(name.capitalizingFirst())(\(name): \(name))" return "self.\(name) = \(assignment)" } else if variable.isBinding { return "self._\(name) = \(name)" diff --git a/sourcery/.lib/Sources/utils/ForStyleableComponent/Type+Extension.swift b/sourcery/.lib/Sources/utils/ForStyleableComponent/Type+Extension.swift index 313b10a36..675559a4a 100644 --- a/sourcery/.lib/Sources/utils/ForStyleableComponent/Type+Extension.swift +++ b/sourcery/.lib/Sources/utils/ForStyleableComponent/Type+Extension.swift @@ -83,25 +83,25 @@ extension Type { } var privateHelperExtension: String { - let initDecl: String + let initDecl = "\(componentName)(.init(\(allStoredVariables.configurationInitArgs)))" let fioriStyle: String switch self.componentType { case .base: - let initArgs = allStoredVariables.map { variable in - let name = variable.name - if variable.isResultBuilder { - return "\(name): { self.\(name) }" - } else if variable.isBinding { - return "\(name): self.$\(name)" - } else { - return "\(name): self.\(name)" - } - } - .joined(separator: ",\n") - initDecl = "\(componentName)(\(initArgs))" +// let initArgs = allStoredVariables.map { variable in +// let name = variable.name +// if variable.isResultBuilder { +// return "\(name): { self.\(name) }" +// } else if variable.isBinding { +// return "\(name): self.$\(name)" +// } else { +// return "\(name): self.\(name)" +// } +// } +// .joined(separator: ",\n") +// initDecl = "\(componentName)(\(initArgs))" fioriStyle = ".fiori" case .composite: - initDecl = "\(componentName)(.init(\(allStoredVariables.configurationInitArgs)))" +// initDecl = "\(componentName)(.init(\(allStoredVariables.configurationInitArgs)))" fioriStyle = "\(self.fioriStyleName).ContentFioriStyle()" case .none: return "" From 1acd47c6c50a77a915f873c4d60e38a4a3f58e31 Mon Sep 17 00:00:00 2001 From: zzchao-1999 <149659707+zzchao-1999@users.noreply.github.com> Date: Thu, 5 Sep 2024 07:57:30 +0800 Subject: [PATCH 15/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:HCPSDKFIORIU?= =?UTF-8?q?IKIT-1934]SwiftUI=20TimelinePreviewView=20(#780)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-1934]SwiftUI TimelinePreviewView * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-1934]Address bot checks * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-1934]Address the comments * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-1934]Address new comments * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-1934]Added a new base component * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-1934]Updated model to protocol * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-1934]Added Docs * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-1934]Corrected Docs issue --------- Co-authored-by: Bill Zhou --- .../Examples.xcodeproj/project.pbxproj | 29 ++- .../CustomTimelinePreviewExample.swift | 57 ++++++ .../Timeline/SimpleTimelineExample.swift | 2 +- .../SimpleTimelinePreviewExample.swift | 66 +++++++ .../Timeline/TimelineExample.swift | 13 +- .../Timeline/TimelinePreviewExample.swift | 77 ++++++++ .../TimelinePreviewItemProtocol.swift | 32 ++++ .../BaseComponentProtocols.swift | 7 + .../CompositeComponentProtocols.swift | 55 ++++++ .../OptionalTitleStyle.fiori.swift | 19 ++ .../TimelineNowIndicatorStyle.fiori.swift | 6 +- .../TimelinePreviewItemStyle.fiori.swift | 102 ++++++++++ .../TimelinePreviewStyle.fiori.swift | 177 ++++++++++++++++++ .../OptionalTitle.generated.swift | 63 +++++++ .../OptionalTitleStyle.generated.swift | 28 +++ .../TimelinePreview.generated.swift | 114 +++++++++++ .../TimelinePreviewStyle.generated.swift | 39 ++++ .../TimelinePreviewItem.generated.swift | 93 +++++++++ .../TimelinePreviewItemStyle.generated.swift | 46 +++++ ...entStyleProtocol+Extension.generated.swift | 168 +++++++++++++++++ .../EnvironmentVariables.generated.swift | 63 +++++++ .../ModifiedStyle.generated.swift | 84 +++++++++ .../ResolvedStyle.generated.swift | 48 +++++ .../View+Extension_.generated.swift | 51 +++++ ...iewEmptyChecking+Extension.generated.swift | 22 +++ .../en.lproj/FioriSwiftUICore.strings | 9 + 26 files changed, 1454 insertions(+), 16 deletions(-) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/Timeline/CustomTimelinePreviewExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/Timeline/SimpleTimelinePreviewExample.swift create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/Timeline/TimelinePreviewExample.swift create mode 100644 Sources/FioriSwiftUICore/Components/TimelinePreviewItemProtocol.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/OptionalTitleStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/TimelinePreviewItemStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/TimelinePreviewStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitleStyle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreview.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreviewStyle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItem.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItemStyle.generated.swift diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 1ca14cab4..8b109406c 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -31,10 +31,6 @@ 691DE21925F2A30B00094D4A /* KPIViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691DE21825F2A30B00094D4A /* KPIViewExample.swift */; }; 692F338B26556A6A009B98DA /* SideBarExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692F338A26556A6A009B98DA /* SideBarExample.swift */; }; 69B2B5D9268A333C009AC6B3 /* KPIProgressViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B2B5D8268A333C009AC6B3 /* KPIProgressViewExample.swift */; }; - 8732C2C52C350957002110E9 /* TimelineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2C42C350957002110E9 /* TimelineExample.swift */; }; - 8732C2C72C3524B6002110E9 /* TimelineItemsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2C62C3524B6002110E9 /* TimelineItemsExample.swift */; }; - 8732C2C92C3524C9002110E9 /* SimpleTimelineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2C82C3524C9002110E9 /* SimpleTimelineExample.swift */; }; - 8732C2CB2C3524D9002110E9 /* CustomTimelineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2CA2C3524D9002110E9 /* CustomTimelineExample.swift */; }; 6D6E86252C50D42000EDB6F4 /* FioriButtonInListExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86242C50D42000EDB6F4 /* FioriButtonInListExample.swift */; }; 6D6E86292C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86282C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift */; }; 6D6E86672C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86662C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift */; }; @@ -49,7 +45,14 @@ 6DEC32002C48FB010084DD20 /* LoadingButtonSingleStatusExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC31FF2C48FB010084DD20 /* LoadingButtonSingleStatusExample.swift */; }; 6DEC32022C4A4DC70084DD20 /* CardFullWidthSingleButtonExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC32012C4A4DC70084DD20 /* CardFullWidthSingleButtonExample.swift */; }; 6DEC32042C4E49C70084DD20 /* CardFixedWidthButtonsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC32032C4E49C70084DD20 /* CardFixedWidthButtonsExample.swift */; }; + 8732C2C52C350957002110E9 /* TimelineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2C42C350957002110E9 /* TimelineExample.swift */; }; + 8732C2C72C3524B6002110E9 /* TimelineItemsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2C62C3524B6002110E9 /* TimelineItemsExample.swift */; }; + 8732C2C92C3524C9002110E9 /* SimpleTimelineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2C82C3524C9002110E9 /* SimpleTimelineExample.swift */; }; + 8732C2CB2C3524D9002110E9 /* CustomTimelineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732C2CA2C3524D9002110E9 /* CustomTimelineExample.swift */; }; 878219C42BEE128E002FDFBC /* StepperViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878219C32BEE128E002FDFBC /* StepperViewExample.swift */; }; + 87F492312C73AD99002B8703 /* CustomTimelinePreviewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F492302C73AD99002B8703 /* CustomTimelinePreviewExample.swift */; }; + 87F492332C73ADA1002B8703 /* SimpleTimelinePreviewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F492322C73ADA1002B8703 /* SimpleTimelinePreviewExample.swift */; }; + 87F492352C73ADAA002B8703 /* TimelinePreviewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F492342C73ADAA002B8703 /* TimelinePreviewExample.swift */; }; 8A55795724C1286E0098003A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A55795624C1286E0098003A /* AppDelegate.swift */; }; 8A55795924C1286E0098003A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A55795824C1286E0098003A /* SceneDelegate.swift */; }; 8A55795B24C1286E0098003A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A55795A24C1286E0098003A /* ContentView.swift */; }; @@ -234,10 +237,6 @@ 691DE21825F2A30B00094D4A /* KPIViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KPIViewExample.swift; sourceTree = ""; }; 692F338A26556A6A009B98DA /* SideBarExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarExample.swift; sourceTree = ""; }; 69B2B5D8268A333C009AC6B3 /* KPIProgressViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KPIProgressViewExample.swift; sourceTree = ""; }; - 8732C2C42C350957002110E9 /* TimelineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineExample.swift; sourceTree = ""; }; - 8732C2C62C3524B6002110E9 /* TimelineItemsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemsExample.swift; sourceTree = ""; }; - 8732C2C82C3524C9002110E9 /* SimpleTimelineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleTimelineExample.swift; sourceTree = ""; }; - 8732C2CA2C3524D9002110E9 /* CustomTimelineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimelineExample.swift; sourceTree = ""; }; 6D6E86242C50D42000EDB6F4 /* FioriButtonInListExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInListExample.swift; sourceTree = ""; }; 6D6E86282C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInListMultipleLineExample.swift; sourceTree = ""; }; 6D6E86662C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInCollectionExample.swift; sourceTree = ""; }; @@ -252,7 +251,14 @@ 6DEC31FF2C48FB010084DD20 /* LoadingButtonSingleStatusExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingButtonSingleStatusExample.swift; sourceTree = ""; }; 6DEC32012C4A4DC70084DD20 /* CardFullWidthSingleButtonExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardFullWidthSingleButtonExample.swift; sourceTree = ""; }; 6DEC32032C4E49C70084DD20 /* CardFixedWidthButtonsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardFixedWidthButtonsExample.swift; sourceTree = ""; }; + 8732C2C42C350957002110E9 /* TimelineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineExample.swift; sourceTree = ""; }; + 8732C2C62C3524B6002110E9 /* TimelineItemsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemsExample.swift; sourceTree = ""; }; + 8732C2C82C3524C9002110E9 /* SimpleTimelineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleTimelineExample.swift; sourceTree = ""; }; + 8732C2CA2C3524D9002110E9 /* CustomTimelineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimelineExample.swift; sourceTree = ""; }; 878219C32BEE128E002FDFBC /* StepperViewExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepperViewExample.swift; sourceTree = ""; }; + 87F492302C73AD99002B8703 /* CustomTimelinePreviewExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomTimelinePreviewExample.swift; sourceTree = ""; }; + 87F492322C73ADA1002B8703 /* SimpleTimelinePreviewExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleTimelinePreviewExample.swift; sourceTree = ""; }; + 87F492342C73ADAA002B8703 /* TimelinePreviewExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelinePreviewExample.swift; sourceTree = ""; }; 8A1E99AD24D59C8000ED8A39 /* cloud-sdk-ios-fiori */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "cloud-sdk-ios-fiori"; path = ../..; sourceTree = ""; }; 8A55795324C1286E0098003A /* Examples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Examples.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8A55795624C1286E0098003A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -518,6 +524,9 @@ 8732C2C32C35092D002110E9 /* Timeline */ = { isa = PBXGroup; children = ( + 87F492342C73ADAA002B8703 /* TimelinePreviewExample.swift */, + 87F492322C73ADA1002B8703 /* SimpleTimelinePreviewExample.swift */, + 87F492302C73AD99002B8703 /* CustomTimelinePreviewExample.swift */, 8732C2C42C350957002110E9 /* TimelineExample.swift */, 8732C2C62C3524B6002110E9 /* TimelineItemsExample.swift */, 8732C2C82C3524C9002110E9 /* SimpleTimelineExample.swift */, @@ -1069,7 +1078,6 @@ C18868D12B32535100F865F7 /* SearchFontAndColor.swift in Sources */, 9D0B26092B9BA5C0004278A5 /* KeyValueFormViewExample.swift in Sources */, 8732C2C52C350957002110E9 /* TimelineExample.swift in Sources */, - 9D057DAB2C2F260200F5331C /* RatingControlExample.swift in Sources */, 8A557A1A24C12C820098003A /* ChartsContentView.swift in Sources */, 8A5579CE24C1293C0098003A /* SettingColor.swift in Sources */, 1F55FEF32AC941FF00D7A1BE /* View+Extensions.swift in Sources */, @@ -1078,6 +1086,7 @@ 8AD9DFB225D49967007448EC /* StylingModifierExample.swift in Sources */, 9D0B260A2B9BA5C0004278A5 /* NoteFormViewExample.swift in Sources */, 9DEC27B72C3F3DE70070B571 /* OtherViewExamples.swift in Sources */, + 87F492332C73ADA1002B8703 /* SimpleTimelinePreviewExample.swift in Sources */, 8A5579D124C1293C0098003A /* Settings.swift in Sources */, 8AD9DFB025D49967007448EC /* ContactItemActionItemsExample.swift in Sources */, 8A6D64B125AE658300D2D76C /* ExpHeaderView.swift in Sources */, @@ -1139,8 +1148,10 @@ 692F338B26556A6A009B98DA /* SideBarExample.swift in Sources */, 8A5579D324C1293C0098003A /* SettingsPoint.swift in Sources */, B80DA9BA260BBF8600C0B2E9 /* SingleActionProfiles.swift in Sources */, + 87F492352C73ADAA002B8703 /* TimelinePreviewExample.swift in Sources */, B18D593C2B0C52C700ABB1AD /* TabViewExample.swift in Sources */, 8A5579D424C1293C0098003A /* SettingsBaseline.swift in Sources */, + 87F492312C73AD99002B8703 /* CustomTimelinePreviewExample.swift in Sources */, 1F90888C261A59820015A84D /* FioriButtonExample.swift in Sources */, 6D6E86252C50D42000EDB6F4 /* FioriButtonInListExample.swift in Sources */, B8D4377125F983730024EE7D /* ObjectCell_Rules_Alignment.swift in Sources */, diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Timeline/CustomTimelinePreviewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/CustomTimelinePreviewExample.swift new file mode 100644 index 000000000..f3dbbfac1 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/CustomTimelinePreviewExample.swift @@ -0,0 +1,57 @@ +import FioriSwiftUICore +import SwiftUI + +struct CustomTimelinePreviewExample: View { + @State private var items0: [TimelinePreviewItemModelImplementation] = [ + TimelinePreviewItemModelImplementation(title: "open", icon: Image(systemName: "a.square"), timelineNode: TimelineNodeType.inProgress, due: ISO8601DateFormatter().date(from: "2024-09-21T12:00:00Z")!, dateFormat: "dd/MM/yyyy"), + TimelinePreviewItemModelImplementation(title: "Before start", icon: Image(systemName: "a.square"), timelineNode: TimelineNodeType.start, due: ISO8601DateFormatter().date(from: "2024-07-23T12:00:00Z")!, dateFormat: "dd/MM/yyyy"), + TimelinePreviewItemModelImplementation(title: "start", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2024-08-19T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "open", icon: Image(systemName: "o.square"), timelineNode: TimelineNodeType.inProgress, due: ISO8601DateFormatter().date(from: "2024-08-17T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "open", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2024-09-27T12:00:00Z")!) + ] + + var body: some View { + VStack { + TimelinePreview( + optionalTitle: { Text("Timeline") }, + action: { + FioriButton( + label: { _ in + Label("See All (\(self.items0.count))", systemImage: "arrowtriangle.right") + .labelStyle(MyLabelStyle()) + } + ) + }, + items: .constant(self.items0.map { $0 as any TimelinePreviewItemModel }) + ) + .timelinePreviewItemStyle(content: { config in + TimelinePreviewItem(config) + .titleStyle(content: { titleConfig in + titleConfig.title.foregroundColor(.yellow) + }) + .timestampStyle(content: { timestampConfig in + timestampConfig.timestamp.foregroundColor(.red) + }) + }) + .optionalTitleStyle(content: { config in + config.optionalTitle.foregroundColor(.purple) + }) + Spacer() + } + } +} + +struct MyLabelStyle: LabelStyle { + func makeBody(configuration: Configuration) -> some View { + HStack(alignment: .center) { + configuration.title + .foregroundColor(.purple) + configuration.icon + .foregroundColor(.green) + } + } +} + +#Preview { + CustomTimelinePreviewExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Timeline/SimpleTimelineExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/SimpleTimelineExample.swift index f60f9730f..94a7fb5f5 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/Timeline/SimpleTimelineExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/SimpleTimelineExample.swift @@ -35,7 +35,7 @@ struct SimpleTimelineExample: View { } } .listStyle(.plain) - .environment(\.defaultMinListRowHeight, 7) + .environment(\.defaultMinListRowHeight, 5) } } diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Timeline/SimpleTimelinePreviewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/SimpleTimelinePreviewExample.swift new file mode 100644 index 000000000..17663f1c5 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/SimpleTimelinePreviewExample.swift @@ -0,0 +1,66 @@ +import FioriSwiftUICore +import SwiftUI + +struct SimpleTimelinePreviewExample: View { + @State private var items0: [TimelinePreviewItemModelImplementation] = [ + TimelinePreviewItemModelImplementation(title: "POC", timelineNode: TimelineNodeType.beforeStart, due: ISO8601DateFormatter().date(from: "2024-06-03T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Project Phase 3", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2024-06-25T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Project Phase 2", timelineNode: TimelineNodeType.inProgress, due: ISO8601DateFormatter().date(from: "2024-06-20T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Project Phase 1", timelineNode: TimelineNodeType.complete, due: ISO8601DateFormatter().date(from: "2024-06-12T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Project Start", timelineNode: TimelineNodeType.start, due: ISO8601DateFormatter().date(from: "2024-06-05T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Project End", timelineNode: TimelineNodeType.end, due: ISO8601DateFormatter().date(from: "2024-07-25T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Accept Test", timelineNode: TimelineNodeType.beforeEnd, due: ISO8601DateFormatter().date(from: "2024-07-15T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Project Phase 4", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2024-07-05T12:00:00Z")!) + ] + + var body: some View { + VStack { + NavigationLink(destination: TimelineView()) { + TimelinePreview(optionalTitle: { Text("Timeline Preview") }, items: .constant(self.items0.map { $0 as any TimelinePreviewItemModel })) + } + Spacer() + } + } +} + +struct TimelineView: View { + var body: some View { + List { + Section(header: Text("Simple Timeline Example")) { + TimelineMarker(timestamp: "06/03/24", secondaryTimestamp: .icon(Image(systemName: "sun.max")), timelineNode: .beforeStart, title: "POC", isPast: true, showUpperVerticalLine: false) + .modifier(CustomListRowModifier()) + .secondaryTimestampStyle(content: { config in + config.secondaryTimestamp.foregroundColor(.yellow) + }) + TimelineMarker(timestamp: "06/05/24", secondaryTimestamp: .text("Sunny"), timelineNode: .start, title: "Project Start", isPast: true) + .modifier(CustomListRowModifier()) + Timeline(timestamp: "06/12/24", secondaryTimestamp: .icon(Image(systemName: "sun.max")), timelineNode: .complete, title: "Project Phase 1", attribute: "xx features implementation done", status: .text("Done"), isPast: true) + .modifier(CustomListRowModifier()) + .secondaryTimestampStyle(content: { config in + config.secondaryTimestamp.foregroundColor(.yellow) + }) + Timeline(timestamp: "06/20/24", timelineNode: .inProgress, title: "Project Phase 2", subtitle: "Integration test", status: .text("ongoing"), isPresent: true) + .modifier(CustomListRowModifier()) + TimelineNowIndicator() + .modifier(CustomListRowModifier()) + Timeline(timestamp: "06/25/24", timelineNode: .open, title: "Project Phase 3", attribute: "feature list: xx, xx, xx", status: .text("pending")) + .modifier(CustomListRowModifier()) + Timeline(timestamp: "06/28/24", timelineNode: .open, title: "Project Phase 4", attribute: "feature list: xx, xx, xx", status: .text("pending")) + .modifier(CustomListRowModifier()) + TimelineMarker(timestamp: "07/05/24", timelineNode: .beforeEnd, title: "Accept Test") + .modifier(CustomListRowModifier()) + TimelineMarker(timestamp: "07/09/24", secondaryTimestamp: .icon(Image(systemName: "sun.max")), timelineNode: .end, title: "Project End", showLowerVerticalLine: false) + .modifier(CustomListRowModifier()) + .secondaryTimestampStyle(content: { config in + config.secondaryTimestamp.foregroundColor(.yellow) + }) + } + } + .listStyle(.plain) + .environment(\.defaultMinListRowHeight, 7) + } +} + +#Preview { + SimpleTimelinePreviewExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Timeline/TimelineExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/TimelineExample.swift index 4b1bac39b..b6397b13f 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/Timeline/TimelineExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/TimelineExample.swift @@ -3,9 +3,16 @@ import SwiftUI struct TimelineExample: View { var body: some View { List { - NavigationLink("TimelineItems", destination: TimelineItemsExample()) - NavigationLink("SimpleTimelineExample", destination: SimpleTimelineExample()) - NavigationLink("CustomTimelineExample", destination: CustomTimelineExample()) + Section(header: Text("TimelinePreview")) { + NavigationLink("Simple TimelinePreview Example", destination: TimelinePreviewExample()) + NavigationLink("Simple TimelinePreview Use Case", destination: SimpleTimelinePreviewExample()) + NavigationLink("Custom TimelinePreview Example", destination: CustomTimelinePreviewExample()) + } + Section(header: Text("Timeline")) { + NavigationLink("Timeline Items", destination: TimelineItemsExample()) + NavigationLink("Simple Timeline Example", destination: SimpleTimelineExample()) + NavigationLink("Custom Timeline Example", destination: CustomTimelineExample()) + } } } } diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Timeline/TimelinePreviewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/TimelinePreviewExample.swift new file mode 100644 index 000000000..234d04f8a --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/Timeline/TimelinePreviewExample.swift @@ -0,0 +1,77 @@ +import FioriSwiftUICore +import SwiftUI + +struct TimelinePreviewItemModelImplementation: TimelinePreviewItemModel { + var id: UUID + var title: String + var icon: Image? + var timelineNode: FioriSwiftUICore.TimelineNodeType + var due: Date + var formatter: DateFormatter? + var isFuture: Bool? + var isCurrent: Bool? + + init(id: UUID = UUID(), title: String, icon: Image? = nil, timelineNode: FioriSwiftUICore.TimelineNodeType, due: Date, dateFormat: String? = nil, isFuture: Bool? = nil, isCurrent: Bool? = nil) { + self.id = id + self.title = title + self.icon = icon + self.timelineNode = timelineNode + self.due = due + self.formatter = DateFormatter() + if let dateFormat { + self.formatter?.dateFormat = dateFormat + } else { + self.formatter?.dateFormat = "MMMM dd yyyy" + } + self.isFuture = isFuture + self.isCurrent = isCurrent + } +} + +struct TimelinePreviewExample: View { + @State private var items0: [TimelinePreviewItemModelImplementation] = [ + TimelinePreviewItemModelImplementation(title: "Open", icon: Image(systemName: "a.square"), timelineNode: TimelineNodeType.inProgress, due: ISO8601DateFormatter().date(from: "2025-09-21T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Before start", timelineNode: TimelineNodeType.start, due: ISO8601DateFormatter().date(from: "2025-07-23T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Start", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2025-08-16T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.inProgress, due: ISO8601DateFormatter().date(from: "2025-08-15T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2025-09-27T12:00:00Z")!) + ] + @State private var items1: [TimelinePreviewItemModelImplementation] = [ + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.inProgress, due: ISO8601DateFormatter().date(from: "2024-09-21T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "LooooooooooooooooooooooongTitle", timelineNode: TimelineNodeType.start, due: ISO8601DateFormatter().date(from: "2024-07-23T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Start", timelineNode: TimelineNodeType.start, due: ISO8601DateFormatter().date(from: "2024-08-29T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2024-08-27T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2024-09-27T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2024-09-07T12:00:00Z")!) + ] + @State private var items2: [TimelinePreviewItemModelImplementation] = [ + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.inProgress, due: ISO8601DateFormatter().date(from: "2023-09-21T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Start", timelineNode: TimelineNodeType.start, due: ISO8601DateFormatter().date(from: "2023-08-10T12:00:00Z")!, dateFormat: "EEEE, MMMM dd, yyyy h:mm a"), + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.open, due: ISO8601DateFormatter().date(from: "2023-08-17T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "Open", timelineNode: TimelineNodeType.end, due: ISO8601DateFormatter().date(from: "2023-09-27T12:00:00Z")!) + ] + @State private var items3: [TimelinePreviewItemModelImplementation] = [ + TimelinePreviewItemModelImplementation(title: "Inprogress", timelineNode: TimelineNodeType.inProgress, due: ISO8601DateFormatter().date(from: "2024-07-21T12:00:00Z")!), + TimelinePreviewItemModelImplementation(title: "End", timelineNode: TimelineNodeType.end, due: ISO8601DateFormatter().date(from: "2024-08-26T12:00:00Z")!) + ] + + var body: some View { + List { + Text("TimelinePreview: Future") + TimelinePreview(optionalTitle: { Text("Timeline") }, items: .constant(self.items0.map { $0 as any TimelinePreviewItemModel })) + Text("TimelinePreview: Present") + TimelinePreview(optionalTitle: { Text("Timeline") }, items: .constant(self.items1.map { $0 as any TimelinePreviewItemModel })) + Text("TimelinePreview: Past") + TimelinePreview(optionalTitle: { Text("Timeline") }, items: .constant(self.items2.map { $0 as any TimelinePreviewItemModel })) + Text("TimelinePreview: No Header") + TimelinePreview(items: .constant(self.items2.map { $0 as any TimelinePreviewItemModel })) + Text("TimelinePreview: End") + TimelinePreview(optionalTitle: { Text("Timeline") }, items: .constant(self.items3.map { $0 as any TimelinePreviewItemModel })) + } + .listStyle(.plain) + } +} + +#Preview { + TimelinePreviewExample() +} diff --git a/Sources/FioriSwiftUICore/Components/TimelinePreviewItemProtocol.swift b/Sources/FioriSwiftUICore/Components/TimelinePreviewItemProtocol.swift new file mode 100644 index 000000000..bf4e4de99 --- /dev/null +++ b/Sources/FioriSwiftUICore/Components/TimelinePreviewItemProtocol.swift @@ -0,0 +1,32 @@ +import Foundation +import SwiftUI + +/// Protocol for a timelinePreviewItem +public protocol TimelinePreviewItemModel: Identifiable { + var id: UUID { get } + var title: String { get } + var icon: Image? { get } + var timelineNode: TimelineNodeType { get } + var due: Date { get } + var formatter: DateFormatter? { get } + var isFuture: Bool? { get set } + var isCurrent: Bool? { get set } +} + +/// Extension to provide a default date formatter for the `TimelinePreviewItemModel`. +extension TimelinePreviewItemModel { + var formatter: DateFormatter { + let formatter = DateFormatter() + formatter.dateFormat = "MMMM dd yyyy" + return formatter + } +} + +/// Extension to provide an initializer for `TimelinePreviewItem` from a `TimelinePreviewItemModel`. +public extension TimelinePreviewItem { + /// Initialize a `TimelinePreviewItem` from a `TimelinePreviewItemModel`. + init(model: any TimelinePreviewItemModel) { + // Initialize the `TimelinePreviewItem` with values + self.init(title: AttributedString(model.title), icon: model.icon, timelineNode: model.timelineNode, timestamp: AttributedString(model.formatter.string(from: model.due)), isFuture: model.isFuture ?? false, nodeType: model.timelineNode) + } +} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift index bb422e118..02bc108d5 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift @@ -320,3 +320,10 @@ protocol _NowIndicatorNodeComponent { protocol _OptionsComponent { var options: [AttributedString] { get } } + +// sourcery: BaseComponent +protocol _OptionalTitleComponent { + // sourcery: @ViewBuilder + // sourcery: defaultValue = "" + var optionalTitle: AttributedString? { get } +} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index 5e9e9df23..8a6fa9a2b 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -424,6 +424,61 @@ protocol _SegmentedControlPickerComponent: _OptionsComponent { var selectedIndex: Int { get } } +/// 'TimelinePreviewItem' is an item specialized for placement in TimelinePreview. +// sourcery: CompositeComponent +protocol _TimelinePreviewItemComponent: _TitleComponent, _IconComponent, _TimelineNodeComponent, _TimestampComponent { + // sourcery: defaultValue = false + /// The node is in future or not. Default is not in future. + var isFuture: Bool { get } + /// Timeline node type + var nodeType: TimelineNodeType { get } +} + +/// `TimelinePreview` is an view for showing a collection of tasks. It comes with a header and a collection view which uses `TimelinePreviewItem` to represent data items within it. +/// +/// ## Usage +/// ```swift +/// Create a struct that conforms to the protocol: TimelinePreviewItemModel, providing implementation for the required properties and methods: +/// struct TimelinePreviewItemModelImplementation: TimelinePreviewItemModel { +/// var id: UUID +/// var title: AttributedString +/// var icon: Image? +/// var timelineNode: FioriSwiftUICore.TimelineNodeType +/// var due: Date +/// var formatter: DateFormatter? +/// var isFuture: Bool? +/// var isCurrent: Bool? +/// +/// init(id: UUID = UUID(), title: AttributedString, icon: Image? = nil, timelineNode: FioriSwiftUICore.TimelineNodeType, due: Date, dateFormat: String? = nil, isFuture: Bool? = nil, isCurrent: Bool? = nil) { +/// self.id = id +/// self.title = title +/// self.icon = icon +/// self.timelineNode = timelineNode +/// self.due = due +/// self.formatter = DateFormatter() +/// if let dateFormat { +/// self.formatter.dateFormat = dateFormat +/// } else { +/// self.formatter.dateFormat = "MMMM dd yyyy" +/// } +/// self.isFuture = isFuture +/// self.isCurrent = isCurrent +/// } +/// } +/// +/// Create a Protocol Instance array with Initial Value +/// @State private var items: [TimelinePreviewItemModelImplementation] = [TimelinePreviewItemModelImplementation(title: "Complete", timelineNode: TimelineNodeType.complete, due: ISO8601DateFormatter().date(from: "2023-07-21T12:00:00Z")!),TimelinePreviewItemModelImplementation(title: "End", timelineNode: TimelineNodeType.end, due: ISO8601DateFormatter().date(from: "2023-08-10T12:00:00Z")!)] +/// +/// Create TimelinePreview with the array +/// TimelinePreview(optionalTitle: { Text("Timeline") }, data: .constant(items.map { $0 as any TimelinePreviewItemModel })) +/// ``` +// sourcery: CompositeComponent +protocol _TimelinePreviewComponent: _OptionalTitleComponent, _ActionComponent { + // sourcery: @Binding + /// The data for all timelinePreviewItems + var items: [any TimelinePreviewItemModel] { get } +} + /// `SwitchView`provides a Fiori style title and`Toggle`. /// /// ## Usage diff --git a/Sources/FioriSwiftUICore/_FioriStyles/OptionalTitleStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/OptionalTitleStyle.fiori.swift new file mode 100644 index 000000000..f2c389abd --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/OptionalTitleStyle.fiori.swift @@ -0,0 +1,19 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct OptionalTitleBaseStyle: OptionalTitleStyle { + @ViewBuilder + public func makeBody(_ configuration: OptionalTitleConfiguration) -> some View { + configuration.optionalTitle + } +} + +// Default fiori styles +public struct OptionalTitleFioriStyle: OptionalTitleStyle { + @ViewBuilder + public func makeBody(_ configuration: OptionalTitleConfiguration) -> some View { + OptionalTitle(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/TimelineNowIndicatorStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/TimelineNowIndicatorStyle.fiori.swift index 10ddce151..eea8d35a3 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/TimelineNowIndicatorStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/TimelineNowIndicatorStyle.fiori.swift @@ -8,7 +8,7 @@ public struct TimelineNowIndicatorBaseStyle: TimelineNowIndicatorStyle { HStack(alignment: .center, spacing: 0) { configuration.nowIndicatorNode Rectangle() - .frame(height: 2) + .frame(height: 1) .foregroundColor(Color.preferredColor(.tintColor)) .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } @@ -20,7 +20,7 @@ extension TimelineNowIndicatorFioriStyle { struct ContentFioriStyle: TimelineNowIndicatorStyle { func makeBody(_ configuration: TimelineNowIndicatorConfiguration) -> some View { TimelineNowIndicator(configuration) - .padding(EdgeInsets(top: 0, leading: 88, bottom: 0, trailing: 0)) + .padding(EdgeInsets(top: 0, leading: 90, bottom: 0, trailing: 0)) } } @@ -29,7 +29,7 @@ extension TimelineNowIndicatorFioriStyle { func makeBody(_ configuration: NowIndicatorNodeConfiguration) -> some View { NowIndicatorNode(configuration) - .font(.system(size: 7)) + .font(.system(size: 5)) .foregroundColor(.preferredColor(.tintColor)) .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/TimelinePreviewItemStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/TimelinePreviewItemStyle.fiori.swift new file mode 100644 index 000000000..0d5a8acb4 --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/TimelinePreviewItemStyle.fiori.swift @@ -0,0 +1,102 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct TimelinePreviewItemBaseStyle: TimelinePreviewItemStyle { + public func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + VStack(alignment: .leading, spacing: 0) { + configuration.timestamp + HStack(alignment: .center, spacing: 0) { + if configuration.icon.isEmpty { + configuration.timelineNode + } else { + configuration.icon + } + if configuration.nodeType != TimelineNodeType.end { + Rectangle() + .frame(height: 2) + .foregroundColor(Color.preferredColor(configuration.isFuture ? .separatorOpaque : .tintColor)) + .padding(.trailing, -3) + } else { + Spacer() + } + } + .frame(minHeight: 15) + .padding(EdgeInsets(top: 12, leading: 0, bottom: 12, trailing: 0)) + configuration.title + } + } +} + +// Default fiori styles +extension TimelinePreviewItemFioriStyle { + struct ContentFioriStyle: TimelinePreviewItemStyle { + func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + TimelinePreviewItem(configuration) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + + struct TitleFioriStyle: TitleStyle { + let timelinePreviewItemConfiguration: TimelinePreviewItemConfiguration + + func makeBody(_ configuration: TitleConfiguration) -> some View { + Title(configuration) + .lineLimit(/*@START_MENU_TOKEN@*/2/*@END_MENU_TOKEN@*/) + .foregroundColor(Color.preferredColor(.primaryLabel)) + .font(.fiori(forTextStyle: .subheadline)) + .alignmentGuide(.timelinePreviewAlignmentGuide) { context in + context[.firstTextBaseline] + } + .multilineTextAlignment(.leading) + } + } + + struct IconFioriStyle: IconStyle { + let timelinePreviewItemConfiguration: TimelinePreviewItemConfiguration + + func makeBody(_ configuration: IconConfiguration) -> some View { + Icon(configuration) + .font(TimelineStyleHelpers.getFontSize(for: self.timelinePreviewItemConfiguration)) + .foregroundColor(Color.preferredColor(self.timelinePreviewItemConfiguration.isFuture ? .separatorOpaque : .tintColor)) + } + } + + struct TimelineNodeFioriStyle: TimelineNodeStyle { + let timelinePreviewItemConfiguration: TimelinePreviewItemConfiguration + + func makeBody(_ configuration: TimelineNodeConfiguration) -> some View { + TimelineNode(configuration) + .font(TimelineStyleHelpers.getFontSize(for: self.timelinePreviewItemConfiguration)) + .fontWeight(.bold) + .foregroundColor(Color.preferredColor(self.timelinePreviewItemConfiguration.isFuture ? .separatorOpaque : .tintColor)) + } + } + + struct TimestampFioriStyle: TimestampStyle { + let timelinePreviewItemConfiguration: TimelinePreviewItemConfiguration + + func makeBody(_ configuration: TimestampConfiguration) -> some View { + Timestamp(configuration) + .lineLimit(/*@START_MENU_TOKEN@*/2/*@END_MENU_TOKEN@*/) + .foregroundColor(Color.preferredColor(.tertiaryLabel)) + .font(.fiori(forTextStyle: .footnote)) + .alignmentGuide(.timelinePreviewAlignmentGuide) { context in + context[.lastTextBaseline] + } + .multilineTextAlignment(/*@START_MENU_TOKEN@*/ .leading/*@END_MENU_TOKEN@*/) + } + } + + enum TimelineStyleHelpers { + static func getFontSize(for configuration: TimelinePreviewItemConfiguration) -> Font { + switch configuration.nodeType { + case .beforeStart, .start, .beforeEnd, .end: + return .fiori(forTextStyle: .caption2) + default: + return .fiori(forTextStyle: .subheadline) + } + } + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/TimelinePreviewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/TimelinePreviewStyle.fiori.swift new file mode 100644 index 000000000..b55afbb93 --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/TimelinePreviewStyle.fiori.swift @@ -0,0 +1,177 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct TimelinePreviewBaseStyle: TimelinePreviewStyle { + @Environment(\.horizontalSizeClass) var horizontalSizeClass + @State private var itemCount = 0 + @State private var VSize: CGSize = .zero + + public func makeBody(_ configuration: TimelinePreviewConfiguration) -> some View { + VStack(alignment: .leading, spacing: 0) { + if !configuration.optionalTitle.isEmpty || !configuration.action.isEmpty { + BuildHeader(configuration: configuration, itemCount: configuration.items.count) + } + BuildTimelinePreviewItem(configuration: configuration, displayItems: self.getDisplayItemCount(VSWidth: self.VSize.width)) + }.readSize { newSize in + self.VSize = newSize + } + } + + func getDisplayItemCount(VSWidth: CGFloat) -> Int { + let itemCount: Int + switch self.horizontalSizeClass { + case .regular: + itemCount = VSWidth > 672 ? 4 : 3 + default: + itemCount = 2 + } + return itemCount + } +} + +struct BuildHeader: View { + let configuration: TimelinePreviewConfiguration + let itemCount: Int + + var body: some View { + HStack { + self.configuration.optionalTitle + Spacer() + self.configuration.action + .actionStyle(content: { actionConfig in + if actionConfig.action.isEmpty { + let labelFormat = NSLocalizedString("See All (%d)", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + let labelString = String(format: labelFormat, self.itemCount) + FioriButton(label: { _ in Label(labelString, systemImage: "chevron.forward").labelStyle(SeeAllActionLabelStyle()) }) + .fioriButtonStyle(FioriPlainButtonStyle()) + } else { + self.configuration.action + } + }) + } + .padding(EdgeInsets(top: 0, leading: 0, bottom: 16, trailing: 0)) + } +} + +struct BuildTimelinePreviewItem: View { + let configuration: TimelinePreviewConfiguration + let displayItems: Int + + var body: some View { + HStack(alignment: .timelinePreviewAlignmentGuide, spacing: 4) { + ForEach(self.filterItems(itemsData: self.configuration.items), id: \.id) { item in + let itemLabelFormat = NSLocalizedString("Item %d of %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + let itemLabelString = String(format: itemLabelFormat, item.formatter.string(from: item.due), item.title) + let dateString = item.formatter.string(from: item.due) + let timestampFormat = NSLocalizedString("Today, %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + let timestampString = String(format: timestampFormat, dateString) + let dateAttributedString = AttributedString(Date.compareTwoDates(first: item.due, second: Date()) == .orderedSame ? timestampString : dateString) + TimelinePreviewItem(title: AttributedString(item.title), icon: item.icon, timelineNode: item.timelineNode, timestamp: dateAttributedString, isFuture: item.isFuture ?? false, nodeType: item.timelineNode) + .accessibilityElement(children: .ignore) + .accessibilityLabel(itemLabelString) + } + } + } + + func filterItems(itemsData: [any TimelinePreviewItemModel]) -> [any TimelinePreviewItemModel] { + // flag item with isFuture and isCurrent + let updatedItems = itemsData.map { item in + var mutableItem = item + mutableItem.isFuture = Date.compareTwoDates(first: mutableItem.due, second: Date()) == .orderedDescending + mutableItem.isCurrent = Date.compareTwoDates(first: mutableItem.due, second: Date()) == .orderedSame + return mutableItem + } + // sort the data by due and filter data by display item count + let filteredItems = Array(updatedItems.sorted(by: { $0.due < $1.due }).prefix(self.displayItems)) + + return filteredItems + } +} + +// Default fiori styles +extension TimelinePreviewFioriStyle { + struct ContentFioriStyle: TimelinePreviewStyle { + func makeBody(_ configuration: TimelinePreviewConfiguration) -> some View { + TimelinePreview(configuration) + .fixedSize(horizontal: false, vertical: true) + .contentShape(.rect) + .padding(EdgeInsets(top: 11, leading: 16, bottom: 16, trailing: 16)) + } + } + + struct OptionalTitleFioriStyle: OptionalTitleStyle { + let timelinePreviewConfiguration: TimelinePreviewConfiguration + + func makeBody(_ configuration: OptionalTitleConfiguration) -> some View { + OptionalTitle(configuration) + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(Color.preferredColor(.secondaryLabel)) + .multilineTextAlignment(.leading) + } + } + + struct ActionFioriStyle: ActionStyle { + let timelinePreviewConfiguration: TimelinePreviewConfiguration + + func makeBody(_ configuration: ActionConfiguration) -> some View { + Action(configuration) + .font(.fiori(forTextStyle: .subheadline)) + .fioriButtonStyle(FioriPlainButtonStyle()) + } + } +} + +struct SeeAllActionLabelStyle: LabelStyle { + func makeBody(configuration: Configuration) -> some View { + HStack(alignment: .center) { + configuration.title + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(Color.preferredColor(.tintColor)) + configuration.icon + .font(.fiori(forTextStyle: .subheadline)) + .foregroundColor(Color.preferredColor(.secondaryLabel)) + } + } +} + +extension Date { + static func compareTwoDates(first: Date, second: Date) -> ComparisonResult { + let calendar = Calendar.current + let components: Set = [Calendar.Component.day, .month, .year, .hour, .minute, .second] + let date1Components = calendar.dateComponents(components, from: first) + let date2Components = calendar.dateComponents(components, from: second) + let date1 = calendar.date(from: date1Components) + let date2 = calendar.date(from: date2Components) + + guard let date1, let date2 else { + fatalError("Two dates in comparison must have the same component") + } + + return date1.compare(date2) + } +} + +extension VerticalAlignment { + private struct TimelinePreviewAlignment: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[VerticalAlignment.bottom] + } + } + + static let timelinePreviewAlignmentGuide = VerticalAlignment( + TimelinePreviewAlignment.self + ) +} + +extension View { + func readSize(onChange: @escaping (CGSize) -> Void) -> some View { + background( + GeometryReader { geometryProxy in + Color.clear.preference(key: SizePreferenceKey.self, value: geometryProxy.size) + } + ) + .onPreferenceChange(SizePreferenceKey.self, perform: onChange) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitle.generated.swift new file mode 100644 index 000000000..61fe127d6 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitle.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct OptionalTitle { + let optionalTitle: any View + + @Environment(\.optionalTitleStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder optionalTitle: () -> any View = { EmptyView() }) { + self.optionalTitle = optionalTitle() + } +} + +public extension OptionalTitle { + init(optionalTitle: AttributedString?) { + self.init(optionalTitle: { OptionalText(optionalTitle) }) + } +} + +public extension OptionalTitle { + init(_ configuration: OptionalTitleConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: OptionalTitleConfiguration, shouldApplyDefaultStyle: Bool) { + self.optionalTitle = configuration.optionalTitle + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension OptionalTitle: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(optionalTitle: .init(self.optionalTitle))).typeErased + .transformEnvironment(\.optionalTitleStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension OptionalTitle { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + OptionalTitle(optionalTitle: { self.optionalTitle }) + .shouldApplyDefaultStyle(false) + .optionalTitleStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitleStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitleStyle.generated.swift new file mode 100644 index 000000000..96985c000 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitleStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol OptionalTitleStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: OptionalTitleConfiguration) -> Body +} + +struct AnyOptionalTitleStyle: OptionalTitleStyle { + let content: (OptionalTitleConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (OptionalTitleConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: OptionalTitleConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct OptionalTitleConfiguration { + public let optionalTitle: OptionalTitle + + public typealias OptionalTitle = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreview.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreview.generated.swift new file mode 100644 index 000000000..d5ec8bcd9 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreview.generated.swift @@ -0,0 +1,114 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +/// `TimelinePreview` is an view for showing a collection of tasks. It comes with a header and a collection view which uses `TimelinePreviewItem` to represent data items within it. +/// +/// ## Usage +/// ```swift +/// Create a struct that conforms to the protocol: TimelinePreviewItemModel, providing implementation for the required properties and methods: +/// struct TimelinePreviewItemModelImplementation: TimelinePreviewItemModel { +/// var id: UUID +/// var title: AttributedString +/// var icon: Image? +/// var timelineNode: FioriSwiftUICore.TimelineNodeType +/// var due: Date +/// var formatter: DateFormatter? +/// var isFuture: Bool? +/// var isCurrent: Bool? +/// +/// init(id: UUID = UUID(), title: AttributedString, icon: Image? = nil, timelineNode: FioriSwiftUICore.TimelineNodeType, due: Date, dateFormat: String? = nil, isFuture: Bool? = nil, isCurrent: Bool? = nil) { +/// self.id = id +/// self.title = title +/// self.icon = icon +/// self.timelineNode = timelineNode +/// self.due = due +/// self.formatter = DateFormatter() +/// if let dateFormat { +/// self.formatter.dateFormat = dateFormat +/// } else { +/// self.formatter.dateFormat = "MMMM dd yyyy" +/// } +/// self.isFuture = isFuture +/// self.isCurrent = isCurrent +/// } +/// } +/// +/// Create a Protocol Instance array with Initial Value +/// @State private var items: [TimelinePreviewItemModelImplementation] = [TimelinePreviewItemModelImplementation(title: "Complete", timelineNode: TimelineNodeType.complete, due: ISO8601DateFormatter().date(from: "2023-07-21T12:00:00Z")!),TimelinePreviewItemModelImplementation(title: "End", timelineNode: TimelineNodeType.end, due: ISO8601DateFormatter().date(from: "2023-08-10T12:00:00Z")!)] +/// +/// Create TimelinePreview with the array +/// TimelinePreview(optionalTitle: { Text("Timeline") }, data: .constant(items.map { $0 as any TimelinePreviewItemModel })) +/// ``` +public struct TimelinePreview { + let optionalTitle: any View + let action: any View + /// The data for all timelinePreviewItems + @Binding var items: [any TimelinePreviewItemModel] + + @Environment(\.timelinePreviewStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder optionalTitle: () -> any View = { EmptyView() }, + @ViewBuilder action: () -> any View = { EmptyView() }, + items: Binding<[any TimelinePreviewItemModel]>) + { + self.optionalTitle = OptionalTitle { optionalTitle() } + self.action = Action { action() } + self._items = items + } +} + +public extension TimelinePreview { + init(optionalTitle: AttributedString?, + action: FioriButton? = nil, + items: Binding<[any TimelinePreviewItemModel]>) + { + self.init(optionalTitle: { OptionalText(optionalTitle) }, action: { action }, items: items) + } +} + +public extension TimelinePreview { + init(_ configuration: TimelinePreviewConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: TimelinePreviewConfiguration, shouldApplyDefaultStyle: Bool) { + self.optionalTitle = configuration.optionalTitle + self.action = configuration.action + self._items = configuration.$items + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension TimelinePreview: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(optionalTitle: .init(self.optionalTitle), action: .init(self.action), items: self.$items)).typeErased + .transformEnvironment(\.timelinePreviewStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension TimelinePreview { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + TimelinePreview(.init(optionalTitle: .init(self.optionalTitle), action: .init(self.action), items: self.$items)) + .shouldApplyDefaultStyle(false) + .timelinePreviewStyle(TimelinePreviewFioriStyle.ContentFioriStyle()) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreviewStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreviewStyle.generated.swift new file mode 100644 index 000000000..b547169b0 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreviewStyle.generated.swift @@ -0,0 +1,39 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol TimelinePreviewStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: TimelinePreviewConfiguration) -> Body +} + +struct AnyTimelinePreviewStyle: TimelinePreviewStyle { + let content: (TimelinePreviewConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (TimelinePreviewConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: TimelinePreviewConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct TimelinePreviewConfiguration { + public let optionalTitle: OptionalTitle + public let action: Action + @Binding public var items: [any TimelinePreviewItemModel] + + public typealias OptionalTitle = ConfigurationViewWrapper + public typealias Action = ConfigurationViewWrapper +} + +public struct TimelinePreviewFioriStyle: TimelinePreviewStyle { + public func makeBody(_ configuration: TimelinePreviewConfiguration) -> some View { + TimelinePreview(configuration) + .optionalTitleStyle(OptionalTitleFioriStyle(timelinePreviewConfiguration: configuration)) + .actionStyle(ActionFioriStyle(timelinePreviewConfiguration: configuration)) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItem.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItem.generated.swift new file mode 100644 index 000000000..47138f117 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItem.generated.swift @@ -0,0 +1,93 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +/// 'TimelinePreviewItem' is an item specialized for placement in TimelinePreview. +public struct TimelinePreviewItem { + let title: any View + let icon: any View + let timelineNode: any View + let timestamp: any View + /// The node is in future or not. Default is not in future. + let isFuture: Bool + /// Timeline node type + let nodeType: TimelineNodeType + + @Environment(\.timelinePreviewItemStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder title: () -> any View, + @ViewBuilder icon: () -> any View = { EmptyView() }, + @ViewBuilder timelineNode: () -> any View, + @ViewBuilder timestamp: () -> any View = { EmptyView() }, + isFuture: Bool = false, + nodeType: TimelineNodeType) + { + self.title = Title { title() } + self.icon = Icon { icon() } + self.timelineNode = TimelineNode { timelineNode() } + self.timestamp = Timestamp { timestamp() } + self.isFuture = isFuture + self.nodeType = nodeType + } +} + +public extension TimelinePreviewItem { + init(title: AttributedString, + icon: Image? = nil, + timelineNode: TimelineNodeType, + timestamp: AttributedString? = nil, + isFuture: Bool = false, + nodeType: TimelineNodeType) + { + self.init(title: { Text(title) }, icon: { icon }, timelineNode: { TimelineNodeView(timelineNode) }, timestamp: { OptionalText(timestamp) }, isFuture: isFuture, nodeType: nodeType) + } +} + +public extension TimelinePreviewItem { + init(_ configuration: TimelinePreviewItemConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: TimelinePreviewItemConfiguration, shouldApplyDefaultStyle: Bool) { + self.title = configuration.title + self.icon = configuration.icon + self.timelineNode = configuration.timelineNode + self.timestamp = configuration.timestamp + self.isFuture = configuration.isFuture + self.nodeType = configuration.nodeType + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension TimelinePreviewItem: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(title: .init(self.title), icon: .init(self.icon), timelineNode: .init(self.timelineNode), timestamp: .init(self.timestamp), isFuture: self.isFuture, nodeType: self.nodeType)).typeErased + .transformEnvironment(\.timelinePreviewItemStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension TimelinePreviewItem { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + TimelinePreviewItem(.init(title: .init(self.title), icon: .init(self.icon), timelineNode: .init(self.timelineNode), timestamp: .init(self.timestamp), isFuture: self.isFuture, nodeType: self.nodeType)) + .shouldApplyDefaultStyle(false) + .timelinePreviewItemStyle(TimelinePreviewItemFioriStyle.ContentFioriStyle()) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItemStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItemStyle.generated.swift new file mode 100644 index 000000000..ac90d1198 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItemStyle.generated.swift @@ -0,0 +1,46 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol TimelinePreviewItemStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> Body +} + +struct AnyTimelinePreviewItemStyle: TimelinePreviewItemStyle { + let content: (TimelinePreviewItemConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (TimelinePreviewItemConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct TimelinePreviewItemConfiguration { + public let title: Title + public let icon: Icon + public let timelineNode: TimelineNode + public let timestamp: Timestamp + public let isFuture: Bool + public let nodeType: TimelineNodeType + + public typealias Title = ConfigurationViewWrapper + public typealias Icon = ConfigurationViewWrapper + public typealias TimelineNode = ConfigurationViewWrapper + public typealias Timestamp = ConfigurationViewWrapper +} + +public struct TimelinePreviewItemFioriStyle: TimelinePreviewItemStyle { + public func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + TimelinePreviewItem(configuration) + .titleStyle(TitleFioriStyle(timelinePreviewItemConfiguration: configuration)) + .iconStyle(IconFioriStyle(timelinePreviewItemConfiguration: configuration)) + .timelineNodeStyle(TimelineNodeFioriStyle(timelinePreviewItemConfiguration: configuration)) + .timestampStyle(TimestampFioriStyle(timelinePreviewItemConfiguration: configuration)) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift index 628f967ee..f721a3fdd 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift @@ -2845,6 +2845,20 @@ public extension ObjectItemStyle where Self == ObjectItemActionStyle { } } +// MARK: OptionalTitleStyle + +public extension OptionalTitleStyle where Self == OptionalTitleBaseStyle { + static var base: OptionalTitleBaseStyle { + OptionalTitleBaseStyle() + } +} + +public extension OptionalTitleStyle where Self == OptionalTitleFioriStyle { + static var fiori: OptionalTitleFioriStyle { + OptionalTitleFioriStyle() + } +} + // MARK: OptionsStyle public extension OptionsStyle where Self == OptionsBaseStyle { @@ -4518,6 +4532,160 @@ public extension TimelineNowIndicatorStyle where Self == TimelineNowIndicatorNow } } +// MARK: TimelinePreviewStyle + +public extension TimelinePreviewStyle where Self == TimelinePreviewBaseStyle { + static var base: TimelinePreviewBaseStyle { + TimelinePreviewBaseStyle() + } +} + +public extension TimelinePreviewStyle where Self == TimelinePreviewFioriStyle { + static var fiori: TimelinePreviewFioriStyle { + TimelinePreviewFioriStyle() + } +} + +public struct TimelinePreviewOptionalTitleStyle: TimelinePreviewStyle { + let style: any OptionalTitleStyle + + public func makeBody(_ configuration: TimelinePreviewConfiguration) -> some View { + TimelinePreview(configuration) + .optionalTitleStyle(self.style) + .typeErased + } +} + +public extension TimelinePreviewStyle where Self == TimelinePreviewOptionalTitleStyle { + static func optionalTitleStyle(_ style: some OptionalTitleStyle) -> TimelinePreviewOptionalTitleStyle { + TimelinePreviewOptionalTitleStyle(style: style) + } + + static func optionalTitleStyle(@ViewBuilder content: @escaping (OptionalTitleConfiguration) -> some View) -> TimelinePreviewOptionalTitleStyle { + let style = AnyOptionalTitleStyle(content) + return TimelinePreviewOptionalTitleStyle(style: style) + } +} + +public struct TimelinePreviewActionStyle: TimelinePreviewStyle { + let style: any ActionStyle + + public func makeBody(_ configuration: TimelinePreviewConfiguration) -> some View { + TimelinePreview(configuration) + .actionStyle(self.style) + .typeErased + } +} + +public extension TimelinePreviewStyle where Self == TimelinePreviewActionStyle { + static func actionStyle(_ style: some ActionStyle) -> TimelinePreviewActionStyle { + TimelinePreviewActionStyle(style: style) + } + + static func actionStyle(@ViewBuilder content: @escaping (ActionConfiguration) -> some View) -> TimelinePreviewActionStyle { + let style = AnyActionStyle(content) + return TimelinePreviewActionStyle(style: style) + } +} + +// MARK: TimelinePreviewItemStyle + +public extension TimelinePreviewItemStyle where Self == TimelinePreviewItemBaseStyle { + static var base: TimelinePreviewItemBaseStyle { + TimelinePreviewItemBaseStyle() + } +} + +public extension TimelinePreviewItemStyle where Self == TimelinePreviewItemFioriStyle { + static var fiori: TimelinePreviewItemFioriStyle { + TimelinePreviewItemFioriStyle() + } +} + +public struct TimelinePreviewItemTitleStyle: TimelinePreviewItemStyle { + let style: any TitleStyle + + public func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + TimelinePreviewItem(configuration) + .titleStyle(self.style) + .typeErased + } +} + +public extension TimelinePreviewItemStyle where Self == TimelinePreviewItemTitleStyle { + static func titleStyle(_ style: some TitleStyle) -> TimelinePreviewItemTitleStyle { + TimelinePreviewItemTitleStyle(style: style) + } + + static func titleStyle(@ViewBuilder content: @escaping (TitleConfiguration) -> some View) -> TimelinePreviewItemTitleStyle { + let style = AnyTitleStyle(content) + return TimelinePreviewItemTitleStyle(style: style) + } +} + +public struct TimelinePreviewItemIconStyle: TimelinePreviewItemStyle { + let style: any IconStyle + + public func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + TimelinePreviewItem(configuration) + .iconStyle(self.style) + .typeErased + } +} + +public extension TimelinePreviewItemStyle where Self == TimelinePreviewItemIconStyle { + static func iconStyle(_ style: some IconStyle) -> TimelinePreviewItemIconStyle { + TimelinePreviewItemIconStyle(style: style) + } + + static func iconStyle(@ViewBuilder content: @escaping (IconConfiguration) -> some View) -> TimelinePreviewItemIconStyle { + let style = AnyIconStyle(content) + return TimelinePreviewItemIconStyle(style: style) + } +} + +public struct TimelinePreviewItemTimelineNodeStyle: TimelinePreviewItemStyle { + let style: any TimelineNodeStyle + + public func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + TimelinePreviewItem(configuration) + .timelineNodeStyle(self.style) + .typeErased + } +} + +public extension TimelinePreviewItemStyle where Self == TimelinePreviewItemTimelineNodeStyle { + static func timelineNodeStyle(_ style: some TimelineNodeStyle) -> TimelinePreviewItemTimelineNodeStyle { + TimelinePreviewItemTimelineNodeStyle(style: style) + } + + static func timelineNodeStyle(@ViewBuilder content: @escaping (TimelineNodeConfiguration) -> some View) -> TimelinePreviewItemTimelineNodeStyle { + let style = AnyTimelineNodeStyle(content) + return TimelinePreviewItemTimelineNodeStyle(style: style) + } +} + +public struct TimelinePreviewItemTimestampStyle: TimelinePreviewItemStyle { + let style: any TimestampStyle + + public func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + TimelinePreviewItem(configuration) + .timestampStyle(self.style) + .typeErased + } +} + +public extension TimelinePreviewItemStyle where Self == TimelinePreviewItemTimestampStyle { + static func timestampStyle(_ style: some TimestampStyle) -> TimelinePreviewItemTimestampStyle { + TimelinePreviewItemTimestampStyle(style: style) + } + + static func timestampStyle(@ViewBuilder content: @escaping (TimestampConfiguration) -> some View) -> TimelinePreviewItemTimestampStyle { + let style = AnyTimestampStyle(content) + return TimelinePreviewItemTimestampStyle(style: style) + } +} + // MARK: TimestampStyle public extension TimestampStyle where Self == TimestampBaseStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift index 93458187f..e44679694 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift @@ -990,6 +990,27 @@ extension EnvironmentValues { } } +// MARK: OptionalTitleStyle + +struct OptionalTitleStyleStackKey: EnvironmentKey { + static let defaultValue: [any OptionalTitleStyle] = [] +} + +extension EnvironmentValues { + var optionalTitleStyle: any OptionalTitleStyle { + self.optionalTitleStyleStack.last ?? .base + } + + var optionalTitleStyleStack: [any OptionalTitleStyle] { + get { + self[OptionalTitleStyleStackKey.self] + } + set { + self[OptionalTitleStyleStackKey.self] = newValue + } + } +} + // MARK: OptionsStyle struct OptionsStyleStackKey: EnvironmentKey { @@ -1704,6 +1725,48 @@ extension EnvironmentValues { } } +// MARK: TimelinePreviewStyle + +struct TimelinePreviewStyleStackKey: EnvironmentKey { + static let defaultValue: [any TimelinePreviewStyle] = [] +} + +extension EnvironmentValues { + var timelinePreviewStyle: any TimelinePreviewStyle { + self.timelinePreviewStyleStack.last ?? .base.concat(.fiori) + } + + var timelinePreviewStyleStack: [any TimelinePreviewStyle] { + get { + self[TimelinePreviewStyleStackKey.self] + } + set { + self[TimelinePreviewStyleStackKey.self] = newValue + } + } +} + +// MARK: TimelinePreviewItemStyle + +struct TimelinePreviewItemStyleStackKey: EnvironmentKey { + static let defaultValue: [any TimelinePreviewItemStyle] = [] +} + +extension EnvironmentValues { + var timelinePreviewItemStyle: any TimelinePreviewItemStyle { + self.timelinePreviewItemStyleStack.last ?? .base.concat(.fiori) + } + + var timelinePreviewItemStyleStack: [any TimelinePreviewItemStyle] { + get { + self[TimelinePreviewItemStyleStackKey.self] + } + set { + self[TimelinePreviewItemStyleStackKey.self] = newValue + } + } +} + // MARK: TimestampStyle struct TimestampStyleStackKey: EnvironmentKey { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift index 6e3e6cf6e..a67669b8b 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift @@ -1324,6 +1324,34 @@ public extension ObjectItemStyle { } } +// MARK: OptionalTitleStyle + +extension ModifiedStyle: OptionalTitleStyle where Style: OptionalTitleStyle { + public func makeBody(_ configuration: OptionalTitleConfiguration) -> some View { + OptionalTitle(configuration) + .optionalTitleStyle(self.style) + .modifier(self.modifier) + } +} + +public struct OptionalTitleStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.optionalTitleStyle(self.style) + } +} + +public extension OptionalTitleStyle { + func modifier(_ modifier: some ViewModifier) -> some OptionalTitleStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some OptionalTitleStyle) -> some OptionalTitleStyle { + style.modifier(OptionalTitleStyleModifier(style: self)) + } +} + // MARK: OptionsStyle extension ModifiedStyle: OptionsStyle where Style: OptionsStyle { @@ -2276,6 +2304,62 @@ public extension TimelineNowIndicatorStyle { } } +// MARK: TimelinePreviewStyle + +extension ModifiedStyle: TimelinePreviewStyle where Style: TimelinePreviewStyle { + public func makeBody(_ configuration: TimelinePreviewConfiguration) -> some View { + TimelinePreview(configuration) + .timelinePreviewStyle(self.style) + .modifier(self.modifier) + } +} + +public struct TimelinePreviewStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.timelinePreviewStyle(self.style) + } +} + +public extension TimelinePreviewStyle { + func modifier(_ modifier: some ViewModifier) -> some TimelinePreviewStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some TimelinePreviewStyle) -> some TimelinePreviewStyle { + style.modifier(TimelinePreviewStyleModifier(style: self)) + } +} + +// MARK: TimelinePreviewItemStyle + +extension ModifiedStyle: TimelinePreviewItemStyle where Style: TimelinePreviewItemStyle { + public func makeBody(_ configuration: TimelinePreviewItemConfiguration) -> some View { + TimelinePreviewItem(configuration) + .timelinePreviewItemStyle(self.style) + .modifier(self.modifier) + } +} + +public struct TimelinePreviewItemStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.timelinePreviewItemStyle(self.style) + } +} + +public extension TimelinePreviewItemStyle { + func modifier(_ modifier: some ViewModifier) -> some TimelinePreviewItemStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some TimelinePreviewItemStyle) -> some TimelinePreviewItemStyle { + style.modifier(TimelinePreviewItemStyleModifier(style: self)) + } +} + // MARK: TimestampStyle extension ModifiedStyle: TimestampStyle where Style: TimestampStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift index ce2a30855..8f2cbba9d 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift @@ -755,6 +755,22 @@ extension ObjectItemStyle { } } +// MARK: OptionalTitleStyle + +struct ResolvedOptionalTitleStyle: View { + let style: Style + let configuration: OptionalTitleConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension OptionalTitleStyle { + func resolve(configuration: OptionalTitleConfiguration) -> some View { + ResolvedOptionalTitleStyle(style: self, configuration: configuration) + } +} + // MARK: OptionsStyle struct ResolvedOptionsStyle: View { @@ -1299,6 +1315,38 @@ extension TimelineNowIndicatorStyle { } } +// MARK: TimelinePreviewStyle + +struct ResolvedTimelinePreviewStyle: View { + let style: Style + let configuration: TimelinePreviewConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension TimelinePreviewStyle { + func resolve(configuration: TimelinePreviewConfiguration) -> some View { + ResolvedTimelinePreviewStyle(style: self, configuration: configuration) + } +} + +// MARK: TimelinePreviewItemStyle + +struct ResolvedTimelinePreviewItemStyle: View { + let style: Style + let configuration: TimelinePreviewItemConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension TimelinePreviewItemStyle { + func resolve(configuration: TimelinePreviewItemConfiguration) -> some View { + ResolvedTimelinePreviewItemStyle(style: self, configuration: configuration) + } +} + // MARK: TimestampStyle struct ResolvedTimestampStyle: View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift index be8fd660a..3af5d1b6f 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift @@ -802,6 +802,23 @@ public extension View { } } +// MARK: OptionalTitleStyle + +public extension View { + func optionalTitleStyle(_ style: some OptionalTitleStyle) -> some View { + self.transformEnvironment(\.optionalTitleStyleStack) { stack in + stack.append(style) + } + } + + func optionalTitleStyle(@ViewBuilder content: @escaping (OptionalTitleConfiguration) -> some View) -> some View { + self.transformEnvironment(\.optionalTitleStyleStack) { stack in + let style = AnyOptionalTitleStyle(content) + stack.append(style) + } + } +} + // MARK: OptionsStyle public extension View { @@ -1380,6 +1397,40 @@ public extension View { } } +// MARK: TimelinePreviewStyle + +public extension View { + func timelinePreviewStyle(_ style: some TimelinePreviewStyle) -> some View { + self.transformEnvironment(\.timelinePreviewStyleStack) { stack in + stack.append(style) + } + } + + func timelinePreviewStyle(@ViewBuilder content: @escaping (TimelinePreviewConfiguration) -> some View) -> some View { + self.transformEnvironment(\.timelinePreviewStyleStack) { stack in + let style = AnyTimelinePreviewStyle(content) + stack.append(style) + } + } +} + +// MARK: TimelinePreviewItemStyle + +public extension View { + func timelinePreviewItemStyle(_ style: some TimelinePreviewItemStyle) -> some View { + self.transformEnvironment(\.timelinePreviewItemStyleStack) { stack in + stack.append(style) + } + } + + func timelinePreviewItemStyle(@ViewBuilder content: @escaping (TimelinePreviewItemConfiguration) -> some View) -> some View { + self.transformEnvironment(\.timelinePreviewItemStyleStack) { stack in + let style = AnyTimelinePreviewItemStyle(content) + stack.append(style) + } + } +} + // MARK: TimestampStyle public extension View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift index b9c915603..6a10a842f 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift @@ -360,6 +360,12 @@ extension ObjectItem: _ViewEmptyChecking { } } +extension OptionalTitle: _ViewEmptyChecking { + public var isEmpty: Bool { + optionalTitle.isEmpty + } +} + extension Options: _ViewEmptyChecking { public var isEmpty: Bool { false @@ -596,6 +602,22 @@ extension TimelineNowIndicator: _ViewEmptyChecking { } } +extension TimelinePreview: _ViewEmptyChecking { + public var isEmpty: Bool { + optionalTitle.isEmpty && + action.isEmpty + } +} + +extension TimelinePreviewItem: _ViewEmptyChecking { + public var isEmpty: Bool { + title.isEmpty && + icon.isEmpty && + timelineNode.isEmpty && + timestamp.isEmpty + } +} + extension Timestamp: _ViewEmptyChecking { public var isEmpty: Bool { timestamp.isEmpty diff --git a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings index 767669327..2fe276f9c 100644 --- a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings @@ -183,3 +183,12 @@ /* XACT: RatingControl accessibility label */ "%d out of %d stars" = "%d out of %d stars"; + +/* XBUT: timeline preview component see all label */ +"See All (%d)" = "See All (%d)"; + +/* XACT: Timeline preview item accessibility label */ +"Item %d of %d" = "Item %d of %d"; + +/* XBUT: timeline preview component timestamp label */ +"Today, %d" = "Today, %d"; From c75f5449ed68af924d121797870ed4e8e32d6725 Mon Sep 17 00:00:00 2001 From: angiexyang <60192269+angiexyang@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:01:11 -0700 Subject: [PATCH 16/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:HCPSDKFIORIU?= =?UTF-8?q?IKIT-2687]=20SwiftUI=20DateTimePicker(1)=20(#792)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2687] SwiftUI DateTimePicker(1) * docs: ✏️ clean up comments --- .../Examples.xcodeproj/project.pbxproj | 4 + .../FioriSwiftUICore/CoreContentView.swift | 6 ++ .../Picker/DateTimePickerExample.swift | 43 ++++++++++ .../BaseComponentProtocols.swift | 6 ++ .../CompositeComponentProtocols.swift | 9 ++ .../DateTimePickerStyle.fiori.swift | 83 +++++++++++++++++++ .../_FioriStyles/ValueLabelStyle.fiori.swift | 19 +++++ .../DateTimePicker.generated.swift | 80 ++++++++++++++++++ .../DateTimePickerStyle.generated.swift | 40 +++++++++ .../ValueLabel/ValueLabel.generated.swift | 63 ++++++++++++++ .../ValueLabelStyle.generated.swift | 28 +++++++ ...entStyleProtocol+Extension.generated.swift | 70 ++++++++++++++++ .../EnvironmentVariables.generated.swift | 42 ++++++++++ .../ModifiedStyle.generated.swift | 56 +++++++++++++ .../ResolvedStyle.generated.swift | 32 +++++++ .../View+Extension_.generated.swift | 34 ++++++++ ...iewEmptyChecking+Extension.generated.swift | 13 +++ 17 files changed, 628 insertions(+) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/Picker/DateTimePickerExample.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/DateTimePickerStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/ValueLabelStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePickerStyle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabel.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabelStyle.generated.swift diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 8b109406c..8f0861c3f 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 3C180C282B858CF6007CE79A /* IllustratedMessageExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C180C272B858CF6007CE79A /* IllustratedMessageExample.swift */; }; 6432FFA02C5164F8008ECE89 /* SegmentedControlExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432FF9F2C5164F8008ECE89 /* SegmentedControlExample.swift */; }; 64905D072C6D13E20062AAD4 /* SwitchExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64905D062C6D13E20062AAD4 /* SwitchExample.swift */; }; + 64905D092C7693970062AAD4 /* DateTimePickerExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64905D082C7693970062AAD4 /* DateTimePickerExample.swift */; }; 691DE21925F2A30B00094D4A /* KPIViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691DE21825F2A30B00094D4A /* KPIViewExample.swift */; }; 692F338B26556A6A009B98DA /* SideBarExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692F338A26556A6A009B98DA /* SideBarExample.swift */; }; 69B2B5D9268A333C009AC6B3 /* KPIProgressViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B2B5D8268A333C009AC6B3 /* KPIProgressViewExample.swift */; }; @@ -234,6 +235,7 @@ 3C180C272B858CF6007CE79A /* IllustratedMessageExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IllustratedMessageExample.swift; sourceTree = ""; }; 6432FF9F2C5164F8008ECE89 /* SegmentedControlExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlExample.swift; sourceTree = ""; }; 64905D062C6D13E20062AAD4 /* SwitchExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchExample.swift; sourceTree = ""; }; + 64905D082C7693970062AAD4 /* DateTimePickerExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimePickerExample.swift; sourceTree = ""; }; 691DE21825F2A30B00094D4A /* KPIViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KPIViewExample.swift; sourceTree = ""; }; 692F338A26556A6A009B98DA /* SideBarExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarExample.swift; sourceTree = ""; }; 69B2B5D8268A333C009AC6B3 /* KPIProgressViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KPIProgressViewExample.swift; sourceTree = ""; }; @@ -799,6 +801,7 @@ children = ( B1D41B1F291A2D97004E64A5 /* DurationPickerExample.swift */, 6432FF9F2C5164F8008ECE89 /* SegmentedControlExample.swift */, + 64905D082C7693970062AAD4 /* DateTimePickerExample.swift */, ); path = Picker; sourceTree = ""; @@ -1059,6 +1062,7 @@ 108E43D5292DAB7C006532F3 /* EmptyStateViewExample.swift in Sources */, B13408922B01FA0700600331 /* NavigationBarExample.swift in Sources */, B84D24F22652F343007F2373 /* ObjectHeaderDeveloperExample.swift in Sources */, + 64905D092C7693970062AAD4 /* DateTimePickerExample.swift in Sources */, B80DA9C2260D04FB00C0B2E9 /* SingleActionLongFollowButton.swift in Sources */, 8A6D64BA25AE714100D2D76C /* ExampleHighlightingStyle.swift in Sources */, 99942D572616980A001912C5 /* OnboardingExamples.swift in Sources */, diff --git a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift index d4890d60f..1f783e55a 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift @@ -200,6 +200,12 @@ struct CoreContentView: View { { Text("SegmentedControlPicker") } + + NavigationLink( + destination: DateTimePickerExample()) + { + Text("DateTimePicker") + } } Section(header: Text("Onboarding")) { diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Picker/DateTimePickerExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Picker/DateTimePickerExample.swift new file mode 100644 index 000000000..05200de42 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/Picker/DateTimePickerExample.swift @@ -0,0 +1,43 @@ +import FioriSwiftUICore +import Foundation +import SwiftUI + +struct DateTimePickerExample: View { + @State var s1: Date = .init() + @State var s2: Date = .init() + @State var s3: Date = .init() + @State var s4: Date = .init() + + struct CustomTitleStyle: TitleStyle { + func makeBody(_ configuration: TitleConfiguration) -> some View { + Title(configuration) + .font(.fiori(forTextStyle: .title3)) + .foregroundStyle(Color.preferredColor(.indigo7)) + } + } + + struct CustomValueLabelStyle: ValueLabelStyle { + func makeBody(_ configuration: ValueLabelConfiguration) -> some View { + ValueLabel(configuration) + .font(.fiori(forTextStyle: .callout)) + .foregroundStyle(Color.preferredColor(.green7)) + } + } + + var body: some View { + VStack { + DateTimePicker(title: "Default", selectedDate: self.$s1) + DateTimePicker(title: "Date only", selectedDate: self.$s2, pickerComponents: [.date]) + DateTimePicker(title: "Time only", selectedDate: self.$s3, pickerComponents: [.hourAndMinute]) + DateTimePicker(title: "Custom Style", selectedDate: self.$s3) + .titleStyle(CustomTitleStyle()) + .valueLabelStyle(CustomValueLabelStyle()) + } + } +} + +struct DateTimePickerExample_Previews: PreviewProvider { + static var previews: some View { + DateTimePickerExample() + } +} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift index 02bc108d5..1a6028652 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift @@ -327,3 +327,9 @@ protocol _OptionalTitleComponent { // sourcery: defaultValue = "" var optionalTitle: AttributedString? { get } } + +// sourcery: BaseComponent +protocol _ValueLabelComponent { + // sourcery: @ViewBuilder + var valueLabel: AttributedString? { get } +} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index 8a6fa9a2b..ca26d7589 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -489,3 +489,12 @@ protocol _TimelinePreviewComponent: _OptionalTitleComponent, _ActionComponent { /// ``` // sourcery: CompositeComponent protocol _SwitchViewComponent: _TitleComponent, _SwitchComponent {} + +// sourcery: CompositeComponent +protocol _DateTimePickerComponent: _TitleComponent, _ValueLabelComponent { + // sourcery: @Binding + var selectedDate: Date { get } + + // sourcery: defaultValue = [.date, .hourAndMinute] + var pickerComponents: DatePicker.Components { get } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/DateTimePickerStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/DateTimePickerStyle.fiori.swift new file mode 100644 index 000000000..f74261e5e --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/DateTimePickerStyle.fiori.swift @@ -0,0 +1,83 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct DateTimePickerBaseStyle: DateTimePickerStyle { + @State var dateString: String = "No date selected" + @State var pickerVisible: Bool = false + + public func makeBody(_ configuration: DateTimePickerConfiguration) -> some View { + VStack { + HStack { + configuration.title + Spacer() + ValueLabel(valueLabel: AttributedString(self.dateString)) + .foregroundStyle(Color.preferredColor(self.pickerVisible ? .tintColor : .primaryLabel)) + .font(.fiori(forTextStyle: .body)) + } + .padding(EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)) + .contentShape(Rectangle()) + .onTapGesture(perform: { + self.pickerVisible.toggle() + }) + if self.pickerVisible { + Divider() + .frame(height: 0.33) + .foregroundStyle(Color.preferredColor(.separatorOpaque)) + .padding(.leading, 16) + self.showPicker(configuration) + } + } + .accessibilityElement() + } + + func showPicker(_ configuration: DateTimePickerConfiguration) -> some View { + let picker = DatePicker("", selection: configuration.$selectedDate, displayedComponents: configuration.pickerComponents) + .datePickerStyle(.graphical) + .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) + .onChange(of: configuration.selectedDate, perform: { _ in + self.formatDate(configuration) + }) + return picker + } + + func formatDate(_ configuration: DateTimePickerConfiguration) { + let formattedDate = configuration.selectedDate.formatted(date: .abbreviated, time: .omitted) + let formattedTime = configuration.selectedDate.formatted(date: .omitted, time: .shortened) + if configuration.pickerComponents == .date { + self.dateString = formattedDate + } else if configuration.pickerComponents == .hourAndMinute { + self.dateString = formattedTime + } else { + self.dateString = formattedDate + " " + formattedTime + } + } +} + +// Default fiori styles +extension DateTimePickerFioriStyle { + struct ContentFioriStyle: DateTimePickerStyle { + func makeBody(_ configuration: DateTimePickerConfiguration) -> some View { + DateTimePicker(configuration) + } + } + + struct TitleFioriStyle: TitleStyle { + let dateTimePickerConfiguration: DateTimePickerConfiguration + + func makeBody(_ configuration: TitleConfiguration) -> some View { + Title(configuration) + .foregroundStyle(Color.preferredColor(.primaryLabel)) + .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) + } + } + + struct ValueLabelFioriStyle: ValueLabelStyle { + let dateTimePickerConfiguration: DateTimePickerConfiguration + + func makeBody(_ configuration: ValueLabelConfiguration) -> some View { + ValueLabel(configuration) + } + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/ValueLabelStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/ValueLabelStyle.fiori.swift new file mode 100644 index 000000000..e61b58b9d --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/ValueLabelStyle.fiori.swift @@ -0,0 +1,19 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct ValueLabelBaseStyle: ValueLabelStyle { + @ViewBuilder + public func makeBody(_ configuration: ValueLabelConfiguration) -> some View { + configuration.valueLabel + } +} + +// Default fiori styles +public struct ValueLabelFioriStyle: ValueLabelStyle { + @ViewBuilder + public func makeBody(_ configuration: ValueLabelConfiguration) -> some View { + ValueLabel(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift new file mode 100644 index 000000000..84a970108 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift @@ -0,0 +1,80 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct DateTimePicker { + let title: any View + let valueLabel: any View + @Binding var selectedDate: Date + let pickerComponents: DatePicker.Components + + @Environment(\.dateTimePickerStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder title: () -> any View, + @ViewBuilder valueLabel: () -> any View = { EmptyView() }, + selectedDate: Binding, + pickerComponents: DatePicker.Components = [.date, .hourAndMinute]) + { + self.title = Title { title() } + self.valueLabel = ValueLabel { valueLabel() } + self._selectedDate = selectedDate + self.pickerComponents = pickerComponents + } +} + +public extension DateTimePicker { + init(title: AttributedString, + valueLabel: AttributedString? = nil, + selectedDate: Binding, + pickerComponents: DatePicker.Components = [.date, .hourAndMinute]) + { + self.init(title: { Text(title) }, valueLabel: { OptionalText(valueLabel) }, selectedDate: selectedDate, pickerComponents: pickerComponents) + } +} + +public extension DateTimePicker { + init(_ configuration: DateTimePickerConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: DateTimePickerConfiguration, shouldApplyDefaultStyle: Bool) { + self.title = configuration.title + self.valueLabel = configuration.valueLabel + self._selectedDate = configuration.$selectedDate + self.pickerComponents = configuration.pickerComponents + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension DateTimePicker: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(title: .init(self.title), valueLabel: .init(self.valueLabel), selectedDate: self.$selectedDate, pickerComponents: self.pickerComponents)).typeErased + .transformEnvironment(\.dateTimePickerStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension DateTimePicker { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + DateTimePicker(.init(title: .init(self.title), valueLabel: .init(self.valueLabel), selectedDate: self.$selectedDate, pickerComponents: self.pickerComponents)) + .shouldApplyDefaultStyle(false) + .dateTimePickerStyle(DateTimePickerFioriStyle.ContentFioriStyle()) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePickerStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePickerStyle.generated.swift new file mode 100644 index 000000000..aacf02615 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePickerStyle.generated.swift @@ -0,0 +1,40 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol DateTimePickerStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: DateTimePickerConfiguration) -> Body +} + +struct AnyDateTimePickerStyle: DateTimePickerStyle { + let content: (DateTimePickerConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (DateTimePickerConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: DateTimePickerConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct DateTimePickerConfiguration { + public let title: Title + public let valueLabel: ValueLabel + @Binding public var selectedDate: Date + public let pickerComponents: DatePicker.Components + + public typealias Title = ConfigurationViewWrapper + public typealias ValueLabel = ConfigurationViewWrapper +} + +public struct DateTimePickerFioriStyle: DateTimePickerStyle { + public func makeBody(_ configuration: DateTimePickerConfiguration) -> some View { + DateTimePicker(configuration) + .titleStyle(TitleFioriStyle(dateTimePickerConfiguration: configuration)) + .valueLabelStyle(ValueLabelFioriStyle(dateTimePickerConfiguration: configuration)) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabel.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabel.generated.swift new file mode 100644 index 000000000..0d7425818 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabel.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct ValueLabel { + let valueLabel: any View + + @Environment(\.valueLabelStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder valueLabel: () -> any View = { EmptyView() }) { + self.valueLabel = valueLabel() + } +} + +public extension ValueLabel { + init(valueLabel: AttributedString? = nil) { + self.init(valueLabel: { OptionalText(valueLabel) }) + } +} + +public extension ValueLabel { + init(_ configuration: ValueLabelConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: ValueLabelConfiguration, shouldApplyDefaultStyle: Bool) { + self.valueLabel = configuration.valueLabel + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension ValueLabel: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(valueLabel: .init(self.valueLabel))).typeErased + .transformEnvironment(\.valueLabelStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension ValueLabel { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + ValueLabel(valueLabel: { self.valueLabel }) + .shouldApplyDefaultStyle(false) + .valueLabelStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabelStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabelStyle.generated.swift new file mode 100644 index 000000000..0afd05d3d --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabelStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol ValueLabelStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: ValueLabelConfiguration) -> Body +} + +struct AnyValueLabelStyle: ValueLabelStyle { + let content: (ValueLabelConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (ValueLabelConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: ValueLabelConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct ValueLabelConfiguration { + public let valueLabel: ValueLabel + + public typealias ValueLabel = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift index f721a3fdd..dc1cfa43a 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift @@ -1396,6 +1396,62 @@ public extension CounterStyle where Self == CounterFioriStyle { } } +// MARK: DateTimePickerStyle + +public extension DateTimePickerStyle where Self == DateTimePickerBaseStyle { + static var base: DateTimePickerBaseStyle { + DateTimePickerBaseStyle() + } +} + +public extension DateTimePickerStyle where Self == DateTimePickerFioriStyle { + static var fiori: DateTimePickerFioriStyle { + DateTimePickerFioriStyle() + } +} + +public struct DateTimePickerTitleStyle: DateTimePickerStyle { + let style: any TitleStyle + + public func makeBody(_ configuration: DateTimePickerConfiguration) -> some View { + DateTimePicker(configuration) + .titleStyle(self.style) + .typeErased + } +} + +public extension DateTimePickerStyle where Self == DateTimePickerTitleStyle { + static func titleStyle(_ style: some TitleStyle) -> DateTimePickerTitleStyle { + DateTimePickerTitleStyle(style: style) + } + + static func titleStyle(@ViewBuilder content: @escaping (TitleConfiguration) -> some View) -> DateTimePickerTitleStyle { + let style = AnyTitleStyle(content) + return DateTimePickerTitleStyle(style: style) + } +} + +public struct DateTimePickerValueLabelStyle: DateTimePickerStyle { + let style: any ValueLabelStyle + + public func makeBody(_ configuration: DateTimePickerConfiguration) -> some View { + DateTimePicker(configuration) + .valueLabelStyle(self.style) + .typeErased + } +} + +public extension DateTimePickerStyle where Self == DateTimePickerValueLabelStyle { + static func valueLabelStyle(_ style: some ValueLabelStyle) -> DateTimePickerValueLabelStyle { + DateTimePickerValueLabelStyle(style: style) + } + + static func valueLabelStyle(@ViewBuilder content: @escaping (ValueLabelConfiguration) -> some View) -> DateTimePickerValueLabelStyle { + let style = AnyValueLabelStyle(content) + return DateTimePickerValueLabelStyle(style: style) + } +} + // MARK: DecrementActionStyle public extension DecrementActionStyle where Self == DecrementActionBaseStyle { @@ -4825,3 +4881,17 @@ public extension TopDividerStyle where Self == TopDividerFioriStyle { TopDividerFioriStyle() } } + +// MARK: ValueLabelStyle + +public extension ValueLabelStyle where Self == ValueLabelBaseStyle { + static var base: ValueLabelBaseStyle { + ValueLabelBaseStyle() + } +} + +public extension ValueLabelStyle where Self == ValueLabelFioriStyle { + static var fiori: ValueLabelFioriStyle { + ValueLabelFioriStyle() + } +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift index e44679694..d284153f7 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift @@ -297,6 +297,27 @@ extension EnvironmentValues { } } +// MARK: DateTimePickerStyle + +struct DateTimePickerStyleStackKey: EnvironmentKey { + static let defaultValue: [any DateTimePickerStyle] = [] +} + +extension EnvironmentValues { + var dateTimePickerStyle: any DateTimePickerStyle { + self.dateTimePickerStyleStack.last ?? .base.concat(.fiori) + } + + var dateTimePickerStyleStack: [any DateTimePickerStyle] { + get { + self[DateTimePickerStyleStackKey.self] + } + set { + self[DateTimePickerStyleStackKey.self] = newValue + } + } +} + // MARK: DecrementActionStyle struct DecrementActionStyleStackKey: EnvironmentKey { @@ -1850,3 +1871,24 @@ extension EnvironmentValues { } } } + +// MARK: ValueLabelStyle + +struct ValueLabelStyleStackKey: EnvironmentKey { + static let defaultValue: [any ValueLabelStyle] = [] +} + +extension EnvironmentValues { + var valueLabelStyle: any ValueLabelStyle { + self.valueLabelStyleStack.last ?? .base + } + + var valueLabelStyleStack: [any ValueLabelStyle] { + get { + self[ValueLabelStyleStackKey.self] + } + set { + self[ValueLabelStyleStackKey.self] = newValue + } + } +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift index a67669b8b..3d88f099e 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift @@ -400,6 +400,34 @@ public extension CounterStyle { } } +// MARK: DateTimePickerStyle + +extension ModifiedStyle: DateTimePickerStyle where Style: DateTimePickerStyle { + public func makeBody(_ configuration: DateTimePickerConfiguration) -> some View { + DateTimePicker(configuration) + .dateTimePickerStyle(self.style) + .modifier(self.modifier) + } +} + +public struct DateTimePickerStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.dateTimePickerStyle(self.style) + } +} + +public extension DateTimePickerStyle { + func modifier(_ modifier: some ViewModifier) -> some DateTimePickerStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some DateTimePickerStyle) -> some DateTimePickerStyle { + style.modifier(DateTimePickerStyleModifier(style: self)) + } +} + // MARK: DecrementActionStyle extension ModifiedStyle: DecrementActionStyle where Style: DecrementActionStyle { @@ -2471,3 +2499,31 @@ public extension TopDividerStyle { style.modifier(TopDividerStyleModifier(style: self)) } } + +// MARK: ValueLabelStyle + +extension ModifiedStyle: ValueLabelStyle where Style: ValueLabelStyle { + public func makeBody(_ configuration: ValueLabelConfiguration) -> some View { + ValueLabel(configuration) + .valueLabelStyle(self.style) + .modifier(self.modifier) + } +} + +public struct ValueLabelStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.valueLabelStyle(self.style) + } +} + +public extension ValueLabelStyle { + func modifier(_ modifier: some ViewModifier) -> some ValueLabelStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some ValueLabelStyle) -> some ValueLabelStyle { + style.modifier(ValueLabelStyleModifier(style: self)) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift index 8f2cbba9d..436f892d4 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift @@ -227,6 +227,22 @@ extension CounterStyle { } } +// MARK: DateTimePickerStyle + +struct ResolvedDateTimePickerStyle: View { + let style: Style + let configuration: DateTimePickerConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension DateTimePickerStyle { + func resolve(configuration: DateTimePickerConfiguration) -> some View { + ResolvedDateTimePickerStyle(style: self, configuration: configuration) + } +} + // MARK: DecrementActionStyle struct ResolvedDecrementActionStyle: View { @@ -1410,3 +1426,19 @@ extension TopDividerStyle { ResolvedTopDividerStyle(style: self, configuration: configuration) } } + +// MARK: ValueLabelStyle + +struct ResolvedValueLabelStyle: View { + let style: Style + let configuration: ValueLabelConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension ValueLabelStyle { + func resolve(configuration: ValueLabelConfiguration) -> some View { + ResolvedValueLabelStyle(style: self, configuration: configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift index 3af5d1b6f..89a312333 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift @@ -241,6 +241,23 @@ public extension View { } } +// MARK: DateTimePickerStyle + +public extension View { + func dateTimePickerStyle(_ style: some DateTimePickerStyle) -> some View { + self.transformEnvironment(\.dateTimePickerStyleStack) { stack in + stack.append(style) + } + } + + func dateTimePickerStyle(@ViewBuilder content: @escaping (DateTimePickerConfiguration) -> some View) -> some View { + self.transformEnvironment(\.dateTimePickerStyleStack) { stack in + let style = AnyDateTimePickerStyle(content) + stack.append(style) + } + } +} + // MARK: DecrementActionStyle public extension View { @@ -1498,3 +1515,20 @@ public extension View { } } } + +// MARK: ValueLabelStyle + +public extension View { + func valueLabelStyle(_ style: some ValueLabelStyle) -> some View { + self.transformEnvironment(\.valueLabelStyleStack) { stack in + stack.append(style) + } + } + + func valueLabelStyle(@ViewBuilder content: @escaping (ValueLabelConfiguration) -> some View) -> some View { + self.transformEnvironment(\.valueLabelStyleStack) { stack in + let style = AnyValueLabelStyle(content) + stack.append(style) + } + } +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift index 6a10a842f..c9234ccbf 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift @@ -132,6 +132,13 @@ extension Counter: _ViewEmptyChecking { } } +extension DateTimePicker: _ViewEmptyChecking { + public var isEmpty: Bool { + title.isEmpty && + valueLabel.isEmpty + } +} + extension DecrementAction: _ViewEmptyChecking { public var isEmpty: Bool { decrementAction.isEmpty @@ -641,3 +648,9 @@ extension TopDivider: _ViewEmptyChecking { topDivider.isEmpty } } + +extension ValueLabel: _ViewEmptyChecking { + public var isEmpty: Bool { + valueLabel.isEmpty + } +} From e07a2fbb8b3f41bb099f1d28edb364d1ed2341e1 Mon Sep 17 00:00:00 2001 From: Bill Zhou Date: Fri, 6 Sep 2024 13:36:39 -0700 Subject: [PATCH 17/29] =?UTF-8?q?=20fix:=20=F0=9F=90=9B=20[IOSSDKBUG-346]?= =?UTF-8?q?=20JouleWelcomeScreen=20title=20style=20update=20(#795)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 fix layout issue in Icons and IconBuilder * fix: 🐛 [HCPSDKFIORIUIKIT-2720] Style updates in ObjectItem * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update --- .../_FioriStyles/JouleWelcomeScreenStyle.fiori.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FioriSwiftUICore/_FioriStyles/JouleWelcomeScreenStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/JouleWelcomeScreenStyle.fiori.swift index e9d488d8f..4e48ee061 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/JouleWelcomeScreenStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/JouleWelcomeScreenStyle.fiori.swift @@ -79,7 +79,7 @@ extension JouleWelcomeScreenFioriStyle { func makeBody(_ configuration: TitleConfiguration) -> some View { Title(configuration) .foregroundStyle(Color.preferredColor(.baseWhite)) - .font(.fiori(forTextStyle: .title1, weight: .bold)) + .font(.fiori(forTextStyle: .largeTitle)) .lineLimit(2) } } From 25db735efe09f62a4f577ddcc0bc731803f61844 Mon Sep 17 00:00:00 2001 From: Bill Zhou Date: Fri, 6 Sep 2024 15:33:24 -0700 Subject: [PATCH 18/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[FIORIDESIGN-13047]?= =?UTF-8?q?=20Joule=20Menu=20Selection=20Icon=20Update=20(#797)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 fix layout issue in Icons and IconBuilder * fix: 🐛 [HCPSDKFIORIUIKIT-2720] Style updates in ObjectItem * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update * fix: 🐛 [FIORIDESIGN-13047] Joule Menu Selection Icon Update --- .../_FioriStyles/MenuSelectionItemStyle.fiori.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift index c6f8a3f6f..00a536f1d 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/MenuSelectionItemStyle.fiori.swift @@ -53,7 +53,7 @@ extension MenuSelectionItemFioriStyle { struct IconFioriStyle: IconStyle { let menuSelectionItemConfiguration: MenuSelectionItemConfiguration - @ScaledMetric var width: CGFloat = 14 + @ScaledMetric var width: CGFloat = 18 func makeBody(_ configuration: IconConfiguration) -> some View { Icon(configuration) From a9fd5d1f16939da400fb43bdb336c74f095caa86 Mon Sep 17 00:00:00 2001 From: xiaoqinggrace <52239714+xiaoqinggrace@users.noreply.github.com> Date: Sun, 8 Sep 2024 09:23:08 -0700 Subject: [PATCH 19/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[JIRA:IOSSDKBUG-324?= =?UTF-8?q?]Usage=20of=20SwiftUI=20KeyValueFormView=20(#789)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 [JIRA:IOSSDKBUG-324]Usage of SwiftUI KeyValueFormView ✅ Closes: IOSSDKBUG-324 * fix: 🐛 [JIRA:IOSSDKBUG-324]Usage of SwiftUI KeyValueFormView --------- Co-authored-by: I824136 --- .../FormViews/KeyValueFormViewExample.swift | 68 ++++++++++++------- .../NoteFormViewStyle.fiori.swift | 6 +- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FormViews/KeyValueFormViewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FormViews/KeyValueFormViewExample.swift index d6f5399f9..2c775ea79 100755 --- a/Apps/Examples/Examples/FioriSwiftUICore/FormViews/KeyValueFormViewExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/FormViews/KeyValueFormViewExample.swift @@ -2,6 +2,7 @@ import FioriSwiftUICore import SwiftUI struct KeyValueFormViewExample: View { + @State var isPresented: Bool = false var key1: AttributedString { let aString = AttributedString("Key 1") return aString @@ -42,11 +43,40 @@ struct KeyValueFormViewExample: View { @State var allowsBeyondLimit = false @State var hidesReadonlyHint = false @State var isRequired = false + @State var showList = true var body: some View { VStack { - Text("KeyValueFormViewExample") - List { + if !self.showList { + KeyValueFormView(title: "KeyValueFormView", text: self.$valueText1, placeholder: "KeyValueFormView", errorMessage: "", hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit, isRequired: self.isRequired) + .padding(.horizontal, 10) + } else { + Text("KeyValueFormViewExample") + List { + Text("Default KeyValueForm") + KeyValueFormView(title: self.key1, text: self.$valueText1, placeholder: "KeyValueFormView", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit, isRequired: self.isRequired) + + Text("Existing Text") + .italic() + KeyValueFormView(title: self.key2, text: self.$valueText2, placeholder: "KeyValueFormView", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit, isRequired: self.isRequired) + + Text("minHeight 50, maxHeight 200") + .italic() + KeyValueFormView(title: self.key3, text: self.$valueText3, placeholder: "Please enter something", errorMessage: self.getErrorMessage(), minTextEditorHeight: 50, maxTextEditorHeight: 200, hintText: self.getHintText(), allowsBeyondLimit: self.allowsBeyondLimit, isRequired: self.isRequired) + + Text("Disabled") + KeyValueFormView(title: "Disabled", text: self.$disabledText, placeholder: "Disabled", controlState: .disabled, minTextEditorHeight: 50, maxTextEditorHeight: 200, isRequired: self.isRequired) + + Text("Read-Only") + KeyValueFormView(title: "Read-Only", text: self.$readOnlyText, placeholder: "Read-Only", controlState: .readOnly, minTextEditorHeight: 50, maxTextLength: 200, hidesReadOnlyHint: self.hidesReadonlyHint, isRequired: self.isRequired) + } + #if !os(visionOS) + .scrollDismissesKeyboard(.immediately) + #endif + } + } + .sheet(isPresented: self.$isPresented, content: { + VStack { Toggle("Shows Hint Text", isOn: self.$showsHintText) .padding(.leading, 16) .padding(.trailing, 16) @@ -65,33 +95,21 @@ struct KeyValueFormViewExample: View { Toggle("Mandatory Field", isOn: self.$isRequired) .padding(.leading, 16) .padding(.trailing, 16) + Toggle("Show on List", isOn: self.$showList) + .padding(.leading, 16) + .padding(.trailing, 16) Button("Dismiss Keyboard") { hideKeyboard() } - .padding(.leading, 16) - .padding(.trailing, 16) - - Text("Default KeyValueForm") - KeyValueFormView(title: self.key1, text: self.$valueText1, placeholder: "KeyValueFormView", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit, isRequired: self.isRequired) - - Text("Existing Text") - .italic() - KeyValueFormView(title: self.key2, text: self.$valueText2, placeholder: "KeyValueFormView", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit, isRequired: self.isRequired) - - Text("minHeight 50, maxHeight 200") - .italic() - KeyValueFormView(title: self.key3, text: self.$valueText3, placeholder: "Please enter something", errorMessage: self.getErrorMessage(), minTextEditorHeight: 50, maxTextEditorHeight: 200, hintText: self.getHintText(), allowsBeyondLimit: self.allowsBeyondLimit, isRequired: self.isRequired) - - Text("Disabled") - KeyValueFormView(title: "Disabled", text: self.$disabledText, placeholder: "Disabled", controlState: .disabled, minTextEditorHeight: 50, maxTextEditorHeight: 200, isRequired: self.isRequired) - - Text("Read-Only") - KeyValueFormView(title: "Read-Only", text: self.$readOnlyText, placeholder: "Read-Only", controlState: .readOnly, minTextEditorHeight: 50, maxTextLength: 200, hidesReadOnlyHint: self.hidesReadonlyHint, isRequired: self.isRequired) } - #if !os(visionOS) - .scrollDismissesKeyboard(.immediately) - #endif - } + .padding() + .presentationDetents([.medium]) + }) + .toolbar(content: { + FioriButton(title: "Options") { _ in + self.isPresented = true + } + }) } func getHintText() -> AttributedString? { diff --git a/Sources/FioriSwiftUICore/_FioriStyles/NoteFormViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/NoteFormViewStyle.fiori.swift index 7d71a4037..389664d22 100755 --- a/Sources/FioriSwiftUICore/_FioriStyles/NoteFormViewStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/NoteFormViewStyle.fiori.swift @@ -53,7 +53,11 @@ extension NoteFormViewFioriStyle { PlaceholderTextEditor(config) .frame(minHeight: self.getMinHeight(configuration)) .frame(maxHeight: self.getMaxHeight(configuration)) - .background(RoundedRectangle(cornerRadius: 8).stroke(self.getBorderColor(configuration), lineWidth: self.getBorderWidth(configuration)).background(self.getBackgroundColor(configuration))) + .background(self.getBackgroundColor(configuration)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(self.getBorderColor(configuration), lineWidth: self.getBorderWidth(configuration)) + ) .cornerRadius(8) .onChange(of: configuration.text) { s in self.checkCharCount(configuration, textString: s) From 910198e794f3091dfad11c8f6e932ccce5ca4147 Mon Sep 17 00:00:00 2001 From: Xiaoyu Liu Date: Tue, 10 Sep 2024 03:23:11 +0800 Subject: [PATCH 20/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:=20HCPSDKFIO?= =?UTF-8?q?RIUIKIT-2708]=20avatar=20stack=20support=20(#788)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Examples.xcodeproj/project.pbxproj | 12 + .../AvatarStack/AvatarStackExample.swift | 112 ++++++ .../FioriSwiftUICore/CoreContentView.swift | 7 + .../ObjectItem/ObjectItemAvatarsExample.swift | 18 +- .../Models/ModelDefinitions.swift | 7 - .../Views/CustomBuilder/AvatarListView.swift | 127 ++++++ .../AvatarStackEnvironments.swift | 183 +++++++++ .../Views/CustomBuilder/AvatarsBuilder.swift | 367 +++--------------- .../CustomBuilder/FootnoteIconsBuilder.swift | 7 +- .../CustomBuilder/FootnoteIconsListView.swift | 6 +- .../BaseComponentProtocols.swift | 8 +- .../CompositeComponentProtocols.swift | 3 + .../_FioriStyles/AvatarStackStyle.fiori.swift | 82 ++++ .../AvatarsTitleStyle.fiori.swift | 20 + .../_FioriStyles/ObjectItemStyle.fiori.swift | 8 +- .../AvatarStack/AvatarStack.generated.swift | 70 ++++ .../AvatarStackStyle.generated.swift | 38 ++ .../Avatars/Avatars.generated.swift | 2 +- .../AvatarsTitle/AvatarsTitle.generated.swift | 63 +++ .../AvatarsTitleStyle.generated.swift | 28 ++ .../ObjectItem/ObjectItem.generated.swift | 2 +- ...entStyleProtocol+Extension.generated.swift | 70 ++++ .../EnvironmentVariables.generated.swift | 42 ++ .../ModifiedStyle.generated.swift | 56 +++ .../ResolvedStyle.generated.swift | 32 ++ .../View+Extension_.generated.swift | 34 ++ ...iewEmptyChecking+Extension.generated.swift | 13 + .../API/AvatarStack+API.generated.swift | 21 - 28 files changed, 1067 insertions(+), 371 deletions(-) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/AvatarStack/AvatarStackExample.swift create mode 100644 Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarListView.swift create mode 100644 Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarStackEnvironments.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/AvatarStackStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/AvatarsTitleStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStack.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStackStyle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitleStyle.generated.swift delete mode 100644 Sources/FioriSwiftUICore/_generated/ViewModels/API/AvatarStack+API.generated.swift diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 8f0861c3f..2502be8b9 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -130,6 +130,7 @@ B1DD86512B07534D00D7EDFD /* NavigationBarFioriStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DD86502B07534D00D7EDFD /* NavigationBarFioriStyle.swift */; }; B1DD86532B0758F000D7EDFD /* NavigationBarPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DD86522B0758F000D7EDFD /* NavigationBarPopover.swift */; }; B1DD86552B0759DD00D7EDFD /* NavigationBarCustomItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DD86542B0759DD00D7EDFD /* NavigationBarCustomItem.swift */; }; + B1F384322C815A540090A858 /* AvatarStackExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1F384312C815A540090A858 /* AvatarStackExample.swift */; }; B1F6FC302B22BDDA005190F9 /* ToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1F6FC2F2B22BDDA005190F9 /* ToolbarView.swift */; }; B80DA9BA260BBF8600C0B2E9 /* SingleActionProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80DA9B9260BBF8600C0B2E9 /* SingleActionProfiles.swift */; }; B80DA9BC260BED9400C0B2E9 /* SingleActionCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80DA9BB260BED9400C0B2E9 /* SingleActionCollectionView.swift */; }; @@ -338,6 +339,7 @@ B1DD86502B07534D00D7EDFD /* NavigationBarFioriStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarFioriStyle.swift; sourceTree = ""; }; B1DD86522B0758F000D7EDFD /* NavigationBarPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarPopover.swift; sourceTree = ""; }; B1DD86542B0759DD00D7EDFD /* NavigationBarCustomItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarCustomItem.swift; sourceTree = ""; }; + B1F384312C815A540090A858 /* AvatarStackExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarStackExample.swift; sourceTree = ""; }; B1F6FC2F2B22BDDA005190F9 /* ToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarView.swift; sourceTree = ""; }; B80DA9B9260BBF8600C0B2E9 /* SingleActionProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleActionProfiles.swift; sourceTree = ""; }; B80DA9BB260BED9400C0B2E9 /* SingleActionCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleActionCollectionView.swift; sourceTree = ""; }; @@ -608,6 +610,7 @@ 8A5579C824C1293C0098003A /* FioriSwiftUICore */ = { isa = PBXGroup; children = ( + B1F384302C815A410090A858 /* AvatarStack */, 8732C2C32C35092D002110E9 /* Timeline */, B19006582C201BAC000C8B10 /* ProfileHeader */, 1F1A1FF82C0BDA42007109D8 /* MenuSelection */, @@ -806,6 +809,14 @@ path = Picker; sourceTree = ""; }; + B1F384302C815A410090A858 /* AvatarStack */ = { + isa = PBXGroup; + children = ( + B1F384312C815A540090A858 /* AvatarStackExample.swift */, + ); + path = AvatarStack; + sourceTree = ""; + }; B80DA9C32612A54E00C0B2E9 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -1165,6 +1176,7 @@ 8A557A2224C12C9B0098003A /* CoreContentView.swift in Sources */, 8A5579D224C1293C0098003A /* Color+Extensions.swift in Sources */, 6D6E866F2C539CDE00EDB6F4 /* InPlaceLoadingFlexibleButtonExample.swift in Sources */, + B1F384322C815A540090A858 /* AvatarStackExample.swift in Sources */, B1BCB6E12C2EB362008AC070 /* ProfileHeaderStaticExample.swift in Sources */, C1C764882A818BEC00BCB0F7 /* SortFilterExample.swift in Sources */, 64905D072C6D13E20062AAD4 /* SwitchExample.swift in Sources */, diff --git a/Apps/Examples/Examples/FioriSwiftUICore/AvatarStack/AvatarStackExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/AvatarStack/AvatarStackExample.swift new file mode 100644 index 000000000..368e81718 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/AvatarStack/AvatarStackExample.swift @@ -0,0 +1,112 @@ +import Combine +import FioriSwiftUICore +import FioriThemeManager +import Foundation +import SwiftUI + +struct AvatarStackExample: View { + @StateObject var model = AvatarStackModel() + + @ViewBuilder var avatarStack: some View { + AvatarStack { + ForEach(0 ..< self.model.avatarsCount, id: \.self) { _ in + Color.random + } + } avatarsTitle: { + if self.model.title.isEmpty { + EmptyView() + } else { + Text(self.model.title) + } + } + .avatarsLayout(self.model.avatarsLayout) + .isAvatarCircular(self.model.isCircular) + .avatarsTitlePosition(self.model.titlePosition) + .avatarsSpacing(self.model.spacing) + .avatarsMaxCount(self.model.maxCount) + .avatarsBorder(self.model.borderColor, width: self.model.borderWidth) + .avatarSize(self.avatarSize) + } + + var avatarSize: CGSize? { + if let sideLength = model.sideLength { + CGSize(width: sideLength, height: sideLength) + } else { + nil + } + } + + var body: some View { + List { + Section { + self.avatarStack + } + + Picker("Avatar Count", selection: self.$model.avatarsCount) { + ForEach(0 ... 20, id: \.self) { number in + Text("\(number)").tag(number) + } + } + TextField("Enter Title", text: self.$model.title) + Toggle("isCircle", isOn: self.$model.isCircular) + + Picker("Avatars Layout", selection: self.$model.avatarsLayout) { + Text("grouped").tag(AvatarStack.Layout.grouped) + Text("horizontal").tag(AvatarStack.Layout.horizontal) + } + Picker("Title Position", selection: self.$model.titlePosition) { + Text("leading").tag(AvatarStack.TextPosition.leading) + Text("trailing").tag(AvatarStack.TextPosition.trailing) + Text("top").tag(AvatarStack.TextPosition.top) + Text("bottom").tag(AvatarStack.TextPosition.bottom) + } + + Picker("Spacing (only work for horizontal avatars)", selection: self.$model.spacing) { + ForEach([-4, -1, 0, 1, 4], id: \.self) { number in + Text("\(number)").tag(CGFloat(number)) + } + } + + Picker("Max Count", selection: self.$model.maxCount) { + Text("None").tag(nil as Int?) + ForEach([2, 4, 8], id: \.self) { number in + Text("\(number)").tag(number as Int?) + } + } + + Picker("Side Length", selection: self.$model.sideLength) { + Text("Default").tag(nil as CGFloat?) + ForEach([10, 16, 20, 30, 40], id: \.self) { number in + Text("\(number)").tag(CGFloat(number) as CGFloat?) + } + } + + Picker("Border Width", selection: self.$model.borderWidth) { + ForEach([0, 1, 2, 4], id: \.self) { number in + Text("\(number)").tag(CGFloat(number)) + } + } + + ColorPicker(selection: self.$model.borderColor, supportsOpacity: false) { + Text("Border Color") + } + } + } +} + +class AvatarStackModel: ObservableObject { + @Published var avatarsCount: Int = 2 + @Published var title: String = "This is a text for avatar stack." + @Published var isCircular: Bool = true + @Published var avatarsLayout: AvatarStack.Layout = .grouped + @Published var titlePosition: AvatarStack.TextPosition = .trailing + @Published var spacing: CGFloat = -1 + @Published var maxCount: Int? = nil + @Published var sideLength: CGFloat? = nil + @Published var borderColor = Color.clear + @Published var borderWidth: CGFloat = 1 +} + +#Preview { + AvatarStackExample() +} diff --git a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift index 1f783e55a..8158ba9be 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift @@ -7,6 +7,13 @@ struct CoreContentView: View { var body: some View { List { Section(header: Text("Views")) { + NavigationLink( + destination: AvatarStackExample(), + label: { + Text("AvatarStack") + } + ) + NavigationLink( destination: FioriButtonContentView(), label: { diff --git a/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/ObjectItemAvatarsExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/ObjectItemAvatarsExample.swift index 3bd9ea55a..efaf88067 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/ObjectItemAvatarsExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/ObjectItem/ObjectItemAvatarsExample.swift @@ -98,8 +98,7 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { Tag("103-Repair") }) .isAvatarCircular(false) - .avatarBorderWidth(2) - .avatarBorderColor(Color.yellow) + .avatarsBorder(Color.yellow, width: 2) .avatarSize(CGSize(width: 40, height: 40)) .splitPercent(0.5) .footnoteIconsMaxCount(-2) @@ -136,8 +135,7 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { Color.blue }) .isAvatarCircular(true) - .avatarBorderWidth(1) - .avatarBorderColor(Color.black) + .avatarsBorder(Color.black) .splitPercent(0.5) .footnoteIconsSpacing(2) return AnyView(oi) @@ -184,8 +182,7 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { Color.blue }) .isAvatarCircular(true) - .avatarBorderWidth(3) - .avatarBorderColor(Color.blue) + .avatarsBorder(Color.blue, width: 3) .avatarSize(CGSize(width: 30, height: 30)) .footnoteIconsMaxCount(4) .footnoteIconsSize(CGSize(width: 20, height: 20)) @@ -334,8 +331,7 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { Tag("103-Repair") }) .isAvatarCircular(false) - .avatarBorderWidth(2) - .avatarBorderColor(Color.yellow) + .avatarsBorder(Color.yellow, width: 2) .avatarSize(CGSize(width: 40, height: 40)) .splitPercent(0.5) .footnoteIconsMaxCount(-2) @@ -372,8 +368,7 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { Color.blue }) .isAvatarCircular(true) - .avatarBorderWidth(1) - .avatarBorderColor(Color.black) + .avatarsBorder(Color.black) .splitPercent(0.5) .footnoteIconsSpacing(2) return AnyView(oi) @@ -420,8 +415,7 @@ struct ObjectItemAvatarsExample: ObjectItemListDataProtocol { Color.blue }) .isAvatarCircular(true) - .avatarBorderWidth(3) - .avatarBorderColor(Color.blue) + .avatarsBorder(Color.blue, width: 3) .avatarSize(CGSize(width: 30, height: 30)) .footnoteIconsMaxCount(4) .footnoteIconsSize(CGSize(width: 20, height: 20)) diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index e4584351a..d123dba43 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -7,13 +7,6 @@ import SwiftUI // sourcery: add_env_props = "numberOfLines" public protocol IconStackModel: IconsComponent {} -// sourcery: generated_component_not_configurable -// sourcery: add_env_props = "avatarSize" -// sourcery: add_env_props = "isAvatarCircular" -// sourcery: add_env_props = "avatarBorderWidth" -// sourcery: add_env_props = "avatarBorderColor" -public protocol AvatarStackModel: AvatarsComponent {} - // sourcery: generated_component_not_configurable // sourcery: add_env_props = "footnoteIconsSize" // sourcery: add_env_props = "footnoteIconsSpacing" diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarListView.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarListView.swift new file mode 100644 index 000000000..661c837d0 --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarListView.swift @@ -0,0 +1,127 @@ +import SwiftUI + +struct SingleAvatar: View, AvatarList { + var count: Int { + self.isEmpty ? 0 : 1 + } + + func view(at index: Int) -> some View { + self + } + + @Environment(\.avatarBorderColor) var borderColor + @Environment(\.avatarBorderWidth) var borderWidth + @Environment(\.isAvatarCircular) var isCircular + @Environment(\.avatarSize) var avatarSize + @Environment(\.avatarsLayout) var layout + + var size: CGSize { + if let size = avatarSize { + return size + } else { + switch self.layout { + case .grouped: + return CGSize(width: 45, height: 45) + case .horizontal: + return CGSize(width: 16, height: 16) + } + } + } + + let avatar: any View + + var body: some View { + if self.isCircular { + self.avatar.typeErased + .frame(width: self.size.width, height: self.size.height) + .clipShape(Capsule()) + .overlay { + Capsule() + .inset(by: self.borderWidth / 2.0) + .stroke(self.borderColor, lineWidth: self.borderWidth) + } + } else { + self.avatar.typeErased + .frame(width: self.size.width, height: self.size.height) + .border(self.borderColor, width: self.borderWidth) + } + } +} + +struct AvatarListView: View { + @Environment(\.avatarsLayout) var layout + @Environment(\.avatarsMaxCount) var maxCount + @Environment(\.avatarsSpacing) var spacing + @Environment(\.avatarSize) var avatarSize + let avatars: T + + var size: CGSize { + if let size = avatarSize { + return size + } else { + switch self.layout { + case .grouped: + return CGSize(width: 45, height: 45) + case .horizontal: + return CGSize(width: 16, height: 16) + } + } + } + + var count: Int { + self.avatars.count + } + + // This condition check if for handle recursive builder issue. + private func checkIfIsNestingAvatars() -> Bool { + if self.count == 1 { + let typeString = String(describing: avatars.view(at: 0).self) + return typeString.contains("AvatarsListStack") + } else { + return false + } + } + + /// :nodoc: + var body: some View { + if self.count == 0 { + EmptyView() + } else if self.count == 1, self.checkIfIsNestingAvatars() { + self.avatars.view(at: 0) + } else { + self.buildAvatars() + } + } + + @ViewBuilder func buildAvatars() -> some View { + switch self.layout { + case .grouped: + // Currently group avatars support 2 avatars default. + let count = min(avatars.count, self.maxCount ?? 2) + if count > 1 { + ZStack(alignment: .topLeading) { + ForEach(0 ..< count, id: \.self) { index in + let position = CGPoint(x: CGFloat(index + 1) * self.size.width / 2, + y: CGFloat(index + 1) * self.size.height / 2) + SingleAvatar(avatar: self.avatars.view(at: index)) + .position(position) + } + } + .frame(width: self.size.width * (1 + CGFloat(count - 1) * 0.5), + height: self.size.height * (1 + CGFloat(count - 1) * 0.5)) + } else if count == 1 { + SingleAvatar(avatar: self.avatars.view(at: 0)) + } else { + EmptyView() + } + case .horizontal: + HorizontalIconsHStack(spacing: self.spacing) { + let validMaxCount = self.maxCount ?? 0 + let itemsCount = validMaxCount <= 0 ? self.count : min(self.count, validMaxCount) + ForEach(0 ..< itemsCount, id: \.self) { index in + SingleAvatar(avatar: self.avatars.view(at: index)) + } + } + } + } +} diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarStackEnvironments.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarStackEnvironments.swift new file mode 100644 index 000000000..597eb907a --- /dev/null +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarStackEnvironments.swift @@ -0,0 +1,183 @@ +import SwiftUI + +struct AvatarBorderColor: EnvironmentKey { + public static let defaultValue = Color.clear +} + +public extension EnvironmentValues { + /// The avatars border color. Default value is `clear`. + var avatarBorderColor: Color { + get { self[AvatarBorderColor.self] } + set { self[AvatarBorderColor.self] = newValue } + } +} + +struct AvatarBorderWidth: EnvironmentKey { + public static let defaultValue: CGFloat = 0 +} + +public extension EnvironmentValues { + /// Dimensions of the avatars border width. Default value is 0. + var avatarBorderWidth: CGFloat { + get { self[AvatarBorderWidth.self] } + set { self[AvatarBorderWidth.self] = newValue } + } +} + +struct IsAvatarCircular: EnvironmentKey { + public static let defaultValue: Bool = true +} + +public extension EnvironmentValues { + /// Specifies whether the `avatars` are drawn as circular. Default value is `true`. + var isAvatarCircular: Bool { + get { self[IsAvatarCircular.self] } + set { self[IsAvatarCircular.self] = newValue } + } +} + +struct AvatarSize: EnvironmentKey { + public static let defaultValue: CGSize? = nil +} + +public extension EnvironmentValues { + /// Dimensions of avatars size. Default value is `45x45` for `group`, and `16x16` for horizontal avatars. + var avatarSize: CGSize? { + get { self[AvatarSize.self] } + set { self[AvatarSize.self] = newValue } + } +} + +struct AvatarsTitlePosition: EnvironmentKey { + public static let defaultValue: AvatarStack.TextPosition = .trailing +} + +public extension EnvironmentValues { + /// Title position of avatar stack. Default value is `trailing`. + var avatarsTitlePosition: AvatarStack.TextPosition { + get { self[AvatarsTitlePosition.self] } + set { self[AvatarsTitlePosition.self] = newValue } + } +} + +struct AvatarsLayout: EnvironmentKey { + public static let defaultValue: AvatarStack.Layout = .grouped +} + +public extension EnvironmentValues { + /// Layout for avatars in the stack. Default value is `.grouped`. + var avatarsLayout: AvatarStack.Layout { + get { self[AvatarsLayout.self] } + set { self[AvatarsLayout.self] = newValue } + } +} + +struct AvatarsMaxCount: EnvironmentKey { + public static let defaultValue: Int? = nil +} + +public extension EnvironmentValues { + /// Max count for avatars in the stack. Default value is nil. + var avatarsMaxCount: Int? { + get { self[AvatarsMaxCount.self] } + set { self[AvatarsMaxCount.self] = newValue } + } +} + +struct AvatarsSpacing: EnvironmentKey { + public static let defaultValue: CGFloat = -1 +} + +public extension EnvironmentValues { + /// Spacing for avatars in horizontal stack. Default value is -1. + var avatarsSpacing: CGFloat { + get { self[AvatarsSpacing.self] } + set { self[AvatarsSpacing.self] = newValue } + } +} + +public extension View { + /// Set the avatars border. Default value is `clear`. + /// ```swift + /// _ObjectItem(title: "Object Item", + /// avatars: { + /// Image(systemName: "circle.fill") + /// }) + /// .avatarsBorder(Color.red, width: 2) + /// ``` + /// - Parameter color: Border color. + /// - Parameter width: Border width. + /// - Returns: A view with specific border of avatars. + func avatarsBorder(_ color: Color, width: CGFloat = 1) -> some View { + environment(\.avatarBorderColor, color) + .environment(\.avatarBorderWidth, width) + } + + /// Dimensions of avatars. Default value is `45x45`. + /// ```swift + /// _ObjectItem(title: "Object Item", + /// avatars: { + /// Image(systemName: "circle.fill") + /// }) + /// .avatarSize(CGSize(30, 30)) + /// ``` + /// - Parameter size: The size of the avatars. + /// - Returns: A view that limits the size of avatars. + func avatarSize(_ size: CGSize?) -> some View { + environment(\.avatarSize, size) + } + + /// Specifies whether the `avatars` are drawn as circular. Default value is `true`. + /// ```swift + /// _ObjectItem(title: "Object Item", + /// avatars: { + /// Image(systemName: "circle.fill") + /// }) + /// .isAvatarCircular(true) + /// ``` + /// - Parameter isCircular: Boolean denoting whether the avatars are circular. + /// - Returns: A view that avatars are circular or not. + func isAvatarCircular(_ isCircular: Bool) -> some View { + environment(\.isAvatarCircular, isCircular) + } + + /// :nodoc: + @available(*, deprecated, message: "Use `func avatarsBorder(_ color: Color, width: CGFloat = 1) -> some View` instead. And this will be removed in the future release.") + func avatarBorderColor(_ color: Color) -> some View { + environment(\.avatarBorderColor, color) + } + + /// :nodoc: + @available(*, deprecated, message: "Use `func avatarsBorder(_ color: Color, width: CGFloat = 1) -> some View` instead. And this will be removed in the future release.") + func avatarBorderWidth(_ borderWidth: CGFloat) -> some View { + environment(\.avatarBorderWidth, borderWidth) + } + + /// Text position for avatar stack. Default value is `.trailing`. + /// - Parameter position: Position for text in avatar stack. + /// - Returns: A view that avatar stack text with specific position. + func avatarsTitlePosition(_ position: AvatarStack.TextPosition) -> some View { + environment(\.avatarsTitlePosition, position) + } + + /// Layout for avatars in the stack. Default value is `.grouped`. + /// - Parameter layout: Layout for avatars in the stack. + /// - Returns: A view that avatar stack with specific layout. + func avatarsLayout(_ layout: AvatarStack.Layout) -> some View { + environment(\.avatarsLayout, layout) + } + + /// Max count for avatars in the stack. + /// - Parameter count: Max count for avatars in the stack. + /// - Returns: A view that avatar stack with specific max count. + func avatarsMaxCount(_ count: Int?) -> some View { + environment(\.avatarsMaxCount, count) + } + + /// Spacing for avatars in horizontal stack. + /// - Parameter spacing: Spacing for avatars in horizontal stack. + /// - Returns: A view that avatar stack with specific spacing. + func avatarsSpacing(_ spacing: CGFloat) -> some View { + environment(\.avatarsSpacing, spacing) + } +} diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarsBuilder.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarsBuilder.swift index b70241a86..7292c6257 100644 --- a/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarsBuilder.swift +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/AvatarsBuilder.swift @@ -1,189 +1,48 @@ import SwiftUI +/// :nodoc: public protocol AvatarList: View, _ViewEmptyChecking { associatedtype V: View var count: Int { get } func view(at index: Int) -> V - - var borderColor: Color { get } - var borderWidth: CGFloat { get } - var isCircular: Bool { get } - var size: CGSize { get } } +/// :nodoc: public extension AvatarList { - /// :nodoc: - @ViewBuilder func buildAvatar(_ avatar: V) -> some View { - if isCircular { - avatar - .frame(width: size.width, height: size.height) - .clipShape(Capsule()) - .overlay { - Capsule() - .inset(by: borderWidth / 2.0) - .stroke(borderColor, lineWidth: borderWidth) - } - } else { - avatar - .frame(width: size.width, height: size.height) - .border(borderColor, width: borderWidth) - } - } - - // This condition check if for handle recursive builder issue. - private func checkIsNestingAvatars() -> Bool { - let typeString = String(describing: V.self) - return typeString.contains("SingleAvatar= 2 { - ZStack(alignment: .topLeading) { - self.buildAvatar(view(at: 0)) - self.buildAvatar(view(at: 1)) - .position(x: size.width, y: size.height) - } - .frame(width: size.width * 1.5, height: size.height * 1.5) - } else { - EmptyView() - } - } + AvatarListView(avatars: self) } -} -public extension AvatarList { + /// :nodoc: var isEmpty: Bool { count == 0 } } -public struct SingleAvatar: AvatarList { - let view: Content +struct AvatarsListStack: AvatarList { + let avatars: [any View] - public var count: Int { - self.view.isEmpty ? 0 : 1 - } - - public func view(at index: Int) -> some View { - self.view - } - - @Environment(\.avatarBorderColor) var avatarBorderColor - @Environment(\.avatarBorderWidth) var avatarBorderWidth - @Environment(\.isAvatarCircular) var isAvatarCircular - @Environment(\.avatarSize) var avatarSize - - public var borderColor: Color { - self.avatarBorderColor - } - - public var borderWidth: CGFloat { - self.avatarBorderWidth - } - - public var isCircular: Bool { - self.isAvatarCircular - } - - public var size: CGSize { - self.avatarSize - } -} - -public struct ConditionalSingleAvatar: AvatarList { - let first: TrueContent? - let second: FalseContent? - - public var count: Int { - if let first, !first.isEmpty { - return 1 - } - - if let second, !second.isEmpty { - return 1 - } - - return 0 - } - - public func view(at index: Int) -> some View { - Group { - if self.first == nil { - self.second - } else { - self.first - } + init(_ avatars: [TextOrIcon]) { + self.avatars = avatars.map { + TextOrIconView($0) } } - @Environment(\.avatarBorderColor) var avatarBorderColor - @Environment(\.avatarBorderWidth) var avatarBorderWidth - @Environment(\.isAvatarCircular) var isAvatarCircular - @Environment(\.avatarSize) var avatarSize - - public var borderColor: Color { - self.avatarBorderColor - } - - public var borderWidth: CGFloat { - self.avatarBorderWidth - } - - public var isCircular: Bool { - self.isAvatarCircular + init(avatars: [any View]) { + self.avatars = avatars } - - public var size: CGSize { - self.avatarSize - } -} - -public struct PairAvatar: AvatarList { - let first: First - let remainder: Second public var count: Int { - let firstCount = self.first.isEmpty ? 0 : 1 - return self.remainder.count + firstCount + self.avatars.count } public func view(at index: Int) -> some View { - Group { - if index == 0 { - self.first - } else { - self.remainder.view(at: index - 1) - } - } + self.avatars[index].typeErased } - @Environment(\.avatarBorderColor) var avatarBorderColor - @Environment(\.avatarBorderWidth) var avatarBorderWidth - @Environment(\.isAvatarCircular) var isAvatarCircular - @Environment(\.avatarSize) var avatarSize - - public var borderColor: Color { - self.avatarBorderColor - } - - public var borderWidth: CGFloat { - self.avatarBorderWidth - } - - public var isCircular: Bool { - self.isAvatarCircular - } - - public var size: CGSize { - self.avatarSize + var body: some View { + AvatarListView(avatars: self) } } @@ -191,182 +50,54 @@ public struct PairAvatar: AvatarList { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @resultBuilder public enum AvatarsBuilder { - /// Builds an empty view from a block containing no statements. - public static func buildBlock() -> EmptyView { - EmptyView() - } - - /// Passes a single view written as a child view through unmodified. - /// - /// An example of a single view written as a child view is - /// `{ Text("Hello") }` - public static func buildBlock(_ content: some View) -> some AvatarList { - SingleAvatar(view: content) - } - /// :nodoc: - public static func buildBlock(_ c0: some View, _ c1: some View) -> some AvatarList { - PairAvatar(first: c0, remainder: SingleAvatar(view: c1)) - } - - /// Provides support for “if” statements in multi-statement closures, - /// producing an optional view that is visible only when the condition - /// evaluates to `true`. - public static func buildIf(_ content: (some View)?) -> some AvatarList { - SingleAvatar(view: content == nil ? AnyView(EmptyView()) : AnyView(content!)) + public static func buildBlock(_ components: any View...) -> some AvatarList { + let flatAvatars = components.flatMap { component -> [any View] in + if let c = component as? AvatarsListStack { + return c.avatars + } else { + return [component] + } + } + return AvatarsListStack(avatars: flatAvatars) } - /// Provides support for "if" statements in multi-statement closures, - /// producing conditional content for the "then" branch. - public static func buildEither(first: TrueContent) -> ConditionalSingleAvatar where TrueContent: View, FalseContent: View { - ConditionalSingleAvatar(first: first, second: nil) + /// :nodoc: + public static func buildBlock() -> EmptyView { + EmptyView() } - /// Provides support for "if-else" statements in multi-statement closures, - /// producing conditional content for the "else" branch. - public static func buildEither(second: FalseContent) -> ConditionalSingleAvatar where TrueContent: View, FalseContent: View { - ConditionalSingleAvatar(first: nil, second: second) - } -} - -extension AvatarStack: AvatarList { - public var count: Int { - let tmpIcons: [TextOrIcon] = _avatars == nil ? [] : _avatars! - - return tmpIcons.count + /// :nodoc: + public static func buildExpression(_ expression: some View) -> some View { + expression } - public func view(at index: Int) -> some View { - let tmpIcons: [TextOrIcon] = _avatars == nil ? [] : _avatars! - return Group { - switch tmpIcons[index] { - case .text(let txt): - Text(txt) - case .icon(let icon): - icon - } - } + /// :nodoc: + public static func buildExpression( + _ expression: ForEach + ) -> some AvatarList { + AvatarsListStack(avatars: expression.data.map { item in + expression.content(item) + }) } - public var borderColor: Color { - avatarBorderColor - } - - public var borderWidth: CGFloat { - avatarBorderWidth - } - - public var isCircular: Bool { - isAvatarCircular - } - - public var size: CGSize { - avatarSize - } -} - -struct AvatarBorderColor: EnvironmentKey { - public static let defaultValue = Color.clear -} - -public extension EnvironmentValues { - /// The avatars border color. Default value is `clear`. - var avatarBorderColor: Color { - get { self[AvatarBorderColor.self] } - set { self[AvatarBorderColor.self] = newValue } - } -} - -struct AvatarBorderWidth: EnvironmentKey { - public static let defaultValue: CGFloat = 0 -} - -public extension EnvironmentValues { - /// Dimensions of the avatars border width. Default value is 0. - var avatarBorderWidth: CGFloat { - get { self[AvatarBorderWidth.self] } - set { self[AvatarBorderWidth.self] = newValue } - } -} - -struct IsAvatarCircular: EnvironmentKey { - public static let defaultValue: Bool = true -} - -public extension EnvironmentValues { - /// Specifies whether the `avatars` are drawn as circular. Default value is `true`. - var isAvatarCircular: Bool { - get { self[IsAvatarCircular.self] } - set { self[IsAvatarCircular.self] = newValue } - } -} - -struct AvatarSize: EnvironmentKey { - public static let defaultValue = CGSize(width: 45, height: 45) -} - -public extension EnvironmentValues { - /// Dimensions of avatars size. Default value is `45x45`. - var avatarSize: CGSize { - get { self[AvatarSize.self] } - set { self[AvatarSize.self] = newValue } - } -} - -public extension View { - /// Set the avatars border color. Default value is `clear`. - /// ```swift - /// _ObjectItem(title: "Object Item", - /// avatars: { - /// Image(systemName: "circle.fill") - /// }) - /// .avatarBorderColor(Color.red) - /// ``` - /// - Parameter color: Border color. - /// - Returns: A view with specific border color of avatars. - func avatarBorderColor(_ color: Color) -> some View { - environment(\.avatarBorderColor, color) + /// :nodoc: + public static func buildEither(first: any View) -> some AvatarList { + AvatarsListStack(avatars: [first]) } - /// Dimensions of avatars. Default value is `45x45`. - /// ```swift - /// _ObjectItem(title: "Object Item", - /// avatars: { - /// Image(systemName: "circle.fill") - /// }) - /// .avatarSize(CGSize(30, 30)) - /// ``` - /// - Parameter size: The size of the avatars. - /// - Returns: A view that limits the size of avatars. - func avatarSize(_ size: CGSize) -> some View { - environment(\.avatarSize, size) + /// :nodoc: + public static func buildEither(second: any View) -> some AvatarList { + AvatarsListStack(avatars: [second]) } - /// Specifies whether the `avatars` are drawn as circular. Default value is `true`. - /// ```swift - /// _ObjectItem(title: "Object Item", - /// avatars: { - /// Image(systemName: "circle.fill") - /// }) - /// .isAvatarCircular(true) - /// ``` - /// - Parameter isCircular: Boolean denoting whether the avatars are circular. - /// - Returns: A view that avatars are cirlcular or not. - func isAvatarCircular(_ isCircular: Bool) -> some View { - environment(\.isAvatarCircular, isCircular) + /// :nodoc: + public static func buildOptional(_ component: (any View)?) -> some AvatarList { + AvatarsListStack(avatars: component.map { [$0] } ?? []) } - /// Dimensions of the avatars border width. Default value is 0. - /// ```swift - /// _ObjectItem(title: "Object Item", - /// avatars: { - /// Image(systemName: "circle.fill") - /// }) - /// .avatarBorderWidth(2) - /// ``` - /// - Parameter borderWidth: Dimensions of the avatars border width. - /// - Returns: A view that avatars with specific boreder width. - func avatarBorderWidth(_ borderWidth: CGFloat) -> some View { - environment(\.avatarBorderWidth, borderWidth) + /// :nodoc: + public static func buildArray(_ components: [any View]) -> some AvatarList { + AvatarsListStack(avatars: components) } } diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsBuilder.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsBuilder.swift index a42f6b483..babf43b48 100644 --- a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsBuilder.swift +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsBuilder.swift @@ -312,12 +312,12 @@ public extension EnvironmentValues { } struct FootnoteIconsTextPosition: EnvironmentKey { - static let defaultValue: TextPosition = .trailing + static let defaultValue: AvatarStack.TextPosition = .trailing } public extension EnvironmentValues { /// Text position for footnote icons. - var footnoteIconsTextPosition: TextPosition { + var footnoteIconsTextPosition: AvatarStack.TextPosition { get { self[FootnoteIconsTextPosition.self] } set { self[FootnoteIconsTextPosition.self] = newValue } } @@ -327,7 +327,7 @@ public extension View { /// Specific the position of the text that drawn for footnote icons. Default value is `.trailing`. /// - Parameter position: Text position. /// - Returns: A view that footnote icons text with specific position. - func footnoteIconsTextPosition(_ position: TextPosition) -> some View { + func footnoteIconsTextPosition(_ position: AvatarStack.TextPosition) -> some View { environment(\.footnoteIconsTextPosition, position) } @@ -392,6 +392,7 @@ public extension View { } } +@available(*, deprecated, message: "Use AvatarStack.TextPosition instead. And this will be removed in the future release.") /// Text position for icons. public enum TextPosition { /// Top position for text. diff --git a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift index d7fcc14e9..bb2a40e55 100644 --- a/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift +++ b/Sources/FioriSwiftUICore/Views/CustomBuilder/FootnoteIconsListView.swift @@ -38,8 +38,8 @@ struct FootnoteIconsListView: View { } @ViewBuilder - func avatarsView(withText: Bool = false) -> some View { - FootnoteIconsHStack(spacing: self.spacing) { + func avatarsView() -> some View { + HorizontalIconsHStack(spacing: self.spacing) { let itemsCount = self.maxCount <= 0 ? self.count : min(self.count, self.maxCount) ForEach(0 ..< itemsCount, id: \.self) { index in self.icons.view(at: index) @@ -65,7 +65,7 @@ struct FootnoteIconsListView: View { } } -struct FootnoteIconsHStack: Layout { +struct HorizontalIconsHStack: Layout { struct CacheData { var width: CGFloat var count: Int diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift index 1a6028652..928af4edb 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift @@ -97,7 +97,7 @@ protocol _FootnoteIconsTextComponent { // sourcery: BaseComponent protocol _AvatarsComponent { - // sourcery: resultBuilder.name = @AvatarsBuilder, resultBuilder.backingComponent = AvatarStack + // sourcery: resultBuilder.name = @AvatarsBuilder, resultBuilder.backingComponent = AvatarsListStack var avatars: [TextOrIcon] { get } } @@ -333,3 +333,9 @@ protocol _ValueLabelComponent { // sourcery: @ViewBuilder var valueLabel: AttributedString? { get } } + +// sourcery: BaseComponent +protocol _AvatarsTitleComponent { + // sourcery: @ViewBuilder + var avatarsTitle: AttributedString? { get } +} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index ca26d7589..f3cd209f0 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -498,3 +498,6 @@ protocol _DateTimePickerComponent: _TitleComponent, _ValueLabelComponent { // sourcery: defaultValue = [.date, .hourAndMinute] var pickerComponents: DatePicker.Components { get } } + +// sourcery: CompositeComponent +protocol _AvatarStackComponent: _AvatarsComponent, _AvatarsTitleComponent {} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/AvatarStackStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/AvatarStackStyle.fiori.swift new file mode 100644 index 000000000..da5ecd95e --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/AvatarStackStyle.fiori.swift @@ -0,0 +1,82 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +@available(*, deprecated, message: "Use AvatarStack to create an avatar stack. We will remove this model in the future.") +public protocol AvatarStackModel: AvatarsComponent {} + +/// :nodoc: +public extension AvatarStack { + /// Layout for avatars in the stack. + enum Layout { + /// Horizontal layout for avatars. + case horizontal + /// Grouped layout for avatars. + case grouped + } + + /// Text position for icons. + enum TextPosition { + /// Top position for text. + case top + /// Bottom position for text. + case bottom + /// Leading position for text. + case leading + /// Trailing position for text. + case trailing + + var alignment: Alignment { + switch self { + case .top: + return .top + case .bottom: + return .bottom + case .leading: + return .leading + case .trailing: + return .trailing + } + } + } +} + +// Base Layout style +public struct AvatarStackBaseStyle: AvatarStackStyle { + @Environment(\.avatarsTitlePosition) var titlePosition + + public func makeBody(_ configuration: AvatarStackConfiguration) -> some View { + AvatarsAndTextLayout(textPosition: self.titlePosition) { + configuration.avatarsTitle + configuration.avatars + } + } +} + +// Default fiori styles +extension AvatarStackFioriStyle { + struct ContentFioriStyle: AvatarStackStyle { + func makeBody(_ configuration: AvatarStackConfiguration) -> some View { + AvatarStack(configuration) + } + } + + struct AvatarsFioriStyle: AvatarsStyle { + let avatarStackConfiguration: AvatarStackConfiguration + + func makeBody(_ configuration: AvatarsConfiguration) -> some View { + Avatars(configuration) + } + } + + struct AvatarsTitleFioriStyle: AvatarsTitleStyle { + let avatarStackConfiguration: AvatarStackConfiguration + + func makeBody(_ configuration: AvatarsTitleConfiguration) -> some View { + AvatarsTitle(configuration) + .font(.fiori(forTextStyle: .subheadline)) + .lineLimit(1) + .foregroundStyle(Color.preferredColor(.secondaryLabel)) + } + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/AvatarsTitleStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/AvatarsTitleStyle.fiori.swift new file mode 100644 index 000000000..e3c695ec5 --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/AvatarsTitleStyle.fiori.swift @@ -0,0 +1,20 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct AvatarsTitleBaseStyle: AvatarsTitleStyle { + @ViewBuilder + public func makeBody(_ configuration: AvatarsTitleConfiguration) -> some View { + // Add default layout here + configuration.avatarsTitle + } +} + +// Default fiori styles +public struct AvatarsTitleFioriStyle: AvatarsTitleStyle { + @ViewBuilder + public func makeBody(_ configuration: AvatarsTitleConfiguration) -> some View { + AvatarsTitle(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift index 4b7c51541..badc511b0 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/ObjectItemStyle.fiori.swift @@ -497,7 +497,7 @@ extension ObjectItemBaseStyle { @ViewBuilder func footnoteIconsView(_ context: Context) -> some View { - FootnoteIconsAndTextLayout(textPosition: self.footnoteIconsTextPosition) { + AvatarsAndTextLayout(textPosition: self.footnoteIconsTextPosition) { context.configuration.footnoteIconsText context.configuration.footnoteIcons } @@ -753,8 +753,8 @@ public struct ObjectItemBorderedAction: ActionStyle { } } -struct FootnoteIconsAndTextLayout: Layout { - let textPosition: TextPosition +struct AvatarsAndTextLayout: Layout { + let textPosition: AvatarStack.TextPosition let margin = NSDirectionalEdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0) let textAndIconsSpacing: CGFloat = 6 let textMinimumWidth: CGFloat = 60 @@ -820,7 +820,7 @@ struct FootnoteIconsAndTextLayout: Layout { case .bottom: iconsView.place(at: bounds.origin, proposal: .unspecified) let nextOrigin = CGPoint(x: bounds.minX, - y: bounds.minY + textView.sizeThatFits(.unspecified).height + self.textAndIconsSpacing) + y: bounds.minY + iconsView.sizeThatFits(.unspecified).height + self.textAndIconsSpacing) textView.place(at: nextOrigin, proposal: .unspecified) case .leading: let textActualSize = textView.sizeThatFits(.unspecified) diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStack.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStack.generated.swift new file mode 100644 index 000000000..6f967cc69 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStack.generated.swift @@ -0,0 +1,70 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct AvatarStack { + let avatars: any View + let avatarsTitle: any View + + @Environment(\.avatarStackStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@AvatarsBuilder avatars: () -> any View = { EmptyView() }, + @ViewBuilder avatarsTitle: () -> any View = { EmptyView() }) + { + self.avatars = Avatars { avatars() } + self.avatarsTitle = AvatarsTitle { avatarsTitle() } + } +} + +public extension AvatarStack { + init(avatars: [TextOrIcon] = [], + avatarsTitle: AttributedString? = nil) + { + self.init(avatars: { AvatarsListStack(avatars) }, avatarsTitle: { OptionalText(avatarsTitle) }) + } +} + +public extension AvatarStack { + init(_ configuration: AvatarStackConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: AvatarStackConfiguration, shouldApplyDefaultStyle: Bool) { + self.avatars = configuration.avatars + self.avatarsTitle = configuration.avatarsTitle + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension AvatarStack: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(avatars: .init(self.avatars), avatarsTitle: .init(self.avatarsTitle))).typeErased + .transformEnvironment(\.avatarStackStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension AvatarStack { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + AvatarStack(.init(avatars: .init(self.avatars), avatarsTitle: .init(self.avatarsTitle))) + .shouldApplyDefaultStyle(false) + .avatarStackStyle(AvatarStackFioriStyle.ContentFioriStyle()) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStackStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStackStyle.generated.swift new file mode 100644 index 000000000..c250af57a --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStackStyle.generated.swift @@ -0,0 +1,38 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol AvatarStackStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: AvatarStackConfiguration) -> Body +} + +struct AnyAvatarStackStyle: AvatarStackStyle { + let content: (AvatarStackConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (AvatarStackConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: AvatarStackConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct AvatarStackConfiguration { + public let avatars: Avatars + public let avatarsTitle: AvatarsTitle + + public typealias Avatars = ConfigurationViewWrapper + public typealias AvatarsTitle = ConfigurationViewWrapper +} + +public struct AvatarStackFioriStyle: AvatarStackStyle { + public func makeBody(_ configuration: AvatarStackConfiguration) -> some View { + AvatarStack(configuration) + .avatarsStyle(AvatarsFioriStyle(avatarStackConfiguration: configuration)) + .avatarsTitleStyle(AvatarsTitleFioriStyle(avatarStackConfiguration: configuration)) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Avatars/Avatars.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Avatars/Avatars.generated.swift index 974301e95..9d08e4f0d 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/Avatars/Avatars.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/Avatars/Avatars.generated.swift @@ -17,7 +17,7 @@ public struct Avatars { public extension Avatars { init(avatars: [TextOrIcon] = []) { - self.init(avatars: { AvatarStack(avatars) }) + self.init(avatars: { AvatarsListStack(avatars) }) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitle.generated.swift new file mode 100644 index 000000000..ac1d53feb --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitle.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct AvatarsTitle { + let avatarsTitle: any View + + @Environment(\.avatarsTitleStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder avatarsTitle: () -> any View = { EmptyView() }) { + self.avatarsTitle = avatarsTitle() + } +} + +public extension AvatarsTitle { + init(avatarsTitle: AttributedString? = nil) { + self.init(avatarsTitle: { OptionalText(avatarsTitle) }) + } +} + +public extension AvatarsTitle { + init(_ configuration: AvatarsTitleConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: AvatarsTitleConfiguration, shouldApplyDefaultStyle: Bool) { + self.avatarsTitle = configuration.avatarsTitle + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension AvatarsTitle: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(avatarsTitle: .init(self.avatarsTitle))).typeErased + .transformEnvironment(\.avatarsTitleStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension AvatarsTitle { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + AvatarsTitle(avatarsTitle: { self.avatarsTitle }) + .shouldApplyDefaultStyle(false) + .avatarsTitleStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitleStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitleStyle.generated.swift new file mode 100644 index 000000000..758122259 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitleStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol AvatarsTitleStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: AvatarsTitleConfiguration) -> Body +} + +struct AnyAvatarsTitleStyle: AvatarsTitleStyle { + let content: (AvatarsTitleConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (AvatarsTitleConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: AvatarsTitleConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct AvatarsTitleConfiguration { + public let avatarsTitle: AvatarsTitle + + public typealias AvatarsTitle = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift index cbfea9124..e9525541b 100755 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ObjectItem/ObjectItem.generated.swift @@ -68,7 +68,7 @@ public extension ObjectItem { tags: [AttributedString] = [], action: FioriButton? = nil) { - self.init(title: { Text(title) }, subtitle: { OptionalText(subtitle) }, footnote: { OptionalText(footnote) }, description: { OptionalText(description) }, status: { TextOrIconView(status) }, substatus: { TextOrIconView(substatus) }, detailImage: { detailImage }, icons: { IconStack(icons) }, avatars: { AvatarStack(avatars) }, footnoteIcons: { FootnoteIconStack(footnoteIcons) }, footnoteIconsText: { OptionalText(footnoteIconsText) }, tags: { TagStack(tags) }, action: { action }) + self.init(title: { Text(title) }, subtitle: { OptionalText(subtitle) }, footnote: { OptionalText(footnote) }, description: { OptionalText(description) }, status: { TextOrIconView(status) }, substatus: { TextOrIconView(substatus) }, detailImage: { detailImage }, icons: { IconStack(icons) }, avatars: { AvatarsListStack(avatars) }, footnoteIcons: { FootnoteIconStack(footnoteIcons) }, footnoteIconsText: { OptionalText(footnoteIconsText) }, tags: { TagStack(tags) }, action: { action }) } } diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift index dc1cfa43a..0390dfb2b 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift @@ -45,6 +45,62 @@ public extension AttributeStyle where Self == AttributeFioriStyle { } } +// MARK: AvatarStackStyle + +public extension AvatarStackStyle where Self == AvatarStackBaseStyle { + static var base: AvatarStackBaseStyle { + AvatarStackBaseStyle() + } +} + +public extension AvatarStackStyle where Self == AvatarStackFioriStyle { + static var fiori: AvatarStackFioriStyle { + AvatarStackFioriStyle() + } +} + +public struct AvatarStackAvatarsStyle: AvatarStackStyle { + let style: any AvatarsStyle + + public func makeBody(_ configuration: AvatarStackConfiguration) -> some View { + AvatarStack(configuration) + .avatarsStyle(self.style) + .typeErased + } +} + +public extension AvatarStackStyle where Self == AvatarStackAvatarsStyle { + static func avatarsStyle(_ style: some AvatarsStyle) -> AvatarStackAvatarsStyle { + AvatarStackAvatarsStyle(style: style) + } + + static func avatarsStyle(@ViewBuilder content: @escaping (AvatarsConfiguration) -> some View) -> AvatarStackAvatarsStyle { + let style = AnyAvatarsStyle(content) + return AvatarStackAvatarsStyle(style: style) + } +} + +public struct AvatarStackAvatarsTitleStyle: AvatarStackStyle { + let style: any AvatarsTitleStyle + + public func makeBody(_ configuration: AvatarStackConfiguration) -> some View { + AvatarStack(configuration) + .avatarsTitleStyle(self.style) + .typeErased + } +} + +public extension AvatarStackStyle where Self == AvatarStackAvatarsTitleStyle { + static func avatarsTitleStyle(_ style: some AvatarsTitleStyle) -> AvatarStackAvatarsTitleStyle { + AvatarStackAvatarsTitleStyle(style: style) + } + + static func avatarsTitleStyle(@ViewBuilder content: @escaping (AvatarsTitleConfiguration) -> some View) -> AvatarStackAvatarsTitleStyle { + let style = AnyAvatarsTitleStyle(content) + return AvatarStackAvatarsTitleStyle(style: style) + } +} + // MARK: AvatarsStyle public extension AvatarsStyle where Self == AvatarsBaseStyle { @@ -59,6 +115,20 @@ public extension AvatarsStyle where Self == AvatarsFioriStyle { } } +// MARK: AvatarsTitleStyle + +public extension AvatarsTitleStyle where Self == AvatarsTitleBaseStyle { + static var base: AvatarsTitleBaseStyle { + AvatarsTitleBaseStyle() + } +} + +public extension AvatarsTitleStyle where Self == AvatarsTitleFioriStyle { + static var fiori: AvatarsTitleFioriStyle { + AvatarsTitleFioriStyle() + } +} + // MARK: BannerMessageStyle public extension BannerMessageStyle where Self == BannerMessageBaseStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift index d284153f7..25c8eca93 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift @@ -66,6 +66,27 @@ extension EnvironmentValues { } } +// MARK: AvatarStackStyle + +struct AvatarStackStyleStackKey: EnvironmentKey { + static let defaultValue: [any AvatarStackStyle] = [] +} + +extension EnvironmentValues { + var avatarStackStyle: any AvatarStackStyle { + self.avatarStackStyleStack.last ?? .base.concat(.fiori) + } + + var avatarStackStyleStack: [any AvatarStackStyle] { + get { + self[AvatarStackStyleStackKey.self] + } + set { + self[AvatarStackStyleStackKey.self] = newValue + } + } +} + // MARK: AvatarsStyle struct AvatarsStyleStackKey: EnvironmentKey { @@ -87,6 +108,27 @@ extension EnvironmentValues { } } +// MARK: AvatarsTitleStyle + +struct AvatarsTitleStyleStackKey: EnvironmentKey { + static let defaultValue: [any AvatarsTitleStyle] = [] +} + +extension EnvironmentValues { + var avatarsTitleStyle: any AvatarsTitleStyle { + self.avatarsTitleStyleStack.last ?? .base + } + + var avatarsTitleStyleStack: [any AvatarsTitleStyle] { + get { + self[AvatarsTitleStyleStackKey.self] + } + set { + self[AvatarsTitleStyleStackKey.self] = newValue + } + } +} + // MARK: BannerMessageStyle struct BannerMessageStyleStackKey: EnvironmentKey { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift index 3d88f099e..fe1c3bd6e 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift @@ -92,6 +92,34 @@ public extension AttributeStyle { } } +// MARK: AvatarStackStyle + +extension ModifiedStyle: AvatarStackStyle where Style: AvatarStackStyle { + public func makeBody(_ configuration: AvatarStackConfiguration) -> some View { + AvatarStack(configuration) + .avatarStackStyle(self.style) + .modifier(self.modifier) + } +} + +public struct AvatarStackStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.avatarStackStyle(self.style) + } +} + +public extension AvatarStackStyle { + func modifier(_ modifier: some ViewModifier) -> some AvatarStackStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some AvatarStackStyle) -> some AvatarStackStyle { + style.modifier(AvatarStackStyleModifier(style: self)) + } +} + // MARK: AvatarsStyle extension ModifiedStyle: AvatarsStyle where Style: AvatarsStyle { @@ -120,6 +148,34 @@ public extension AvatarsStyle { } } +// MARK: AvatarsTitleStyle + +extension ModifiedStyle: AvatarsTitleStyle where Style: AvatarsTitleStyle { + public func makeBody(_ configuration: AvatarsTitleConfiguration) -> some View { + AvatarsTitle(configuration) + .avatarsTitleStyle(self.style) + .modifier(self.modifier) + } +} + +public struct AvatarsTitleStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.avatarsTitleStyle(self.style) + } +} + +public extension AvatarsTitleStyle { + func modifier(_ modifier: some ViewModifier) -> some AvatarsTitleStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some AvatarsTitleStyle) -> some AvatarsTitleStyle { + style.modifier(AvatarsTitleStyleModifier(style: self)) + } +} + // MARK: BannerMessageStyle extension ModifiedStyle: BannerMessageStyle where Style: BannerMessageStyle { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift index 436f892d4..79cc672e9 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift @@ -51,6 +51,22 @@ extension AttributeStyle { } } +// MARK: AvatarStackStyle + +struct ResolvedAvatarStackStyle: View { + let style: Style + let configuration: AvatarStackConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension AvatarStackStyle { + func resolve(configuration: AvatarStackConfiguration) -> some View { + ResolvedAvatarStackStyle(style: self, configuration: configuration) + } +} + // MARK: AvatarsStyle struct ResolvedAvatarsStyle: View { @@ -67,6 +83,22 @@ extension AvatarsStyle { } } +// MARK: AvatarsTitleStyle + +struct ResolvedAvatarsTitleStyle: View { + let style: Style + let configuration: AvatarsTitleConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension AvatarsTitleStyle { + func resolve(configuration: AvatarsTitleConfiguration) -> some View { + ResolvedAvatarsTitleStyle(style: self, configuration: configuration) + } +} + // MARK: BannerMessageStyle struct ResolvedBannerMessageStyle: View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift index 89a312333..d748c32af 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift @@ -54,6 +54,23 @@ public extension View { } } +// MARK: AvatarStackStyle + +public extension View { + func avatarStackStyle(_ style: some AvatarStackStyle) -> some View { + self.transformEnvironment(\.avatarStackStyleStack) { stack in + stack.append(style) + } + } + + func avatarStackStyle(@ViewBuilder content: @escaping (AvatarStackConfiguration) -> some View) -> some View { + self.transformEnvironment(\.avatarStackStyleStack) { stack in + let style = AnyAvatarStackStyle(content) + stack.append(style) + } + } +} + // MARK: AvatarsStyle public extension View { @@ -71,6 +88,23 @@ public extension View { } } +// MARK: AvatarsTitleStyle + +public extension View { + func avatarsTitleStyle(_ style: some AvatarsTitleStyle) -> some View { + self.transformEnvironment(\.avatarsTitleStyleStack) { stack in + stack.append(style) + } + } + + func avatarsTitleStyle(@ViewBuilder content: @escaping (AvatarsTitleConfiguration) -> some View) -> some View { + self.transformEnvironment(\.avatarsTitleStyleStack) { stack in + let style = AnyAvatarsTitleStyle(content) + stack.append(style) + } + } +} + // MARK: BannerMessageStyle public extension View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift index c9234ccbf..39735d69f 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift @@ -21,12 +21,25 @@ extension Attribute: _ViewEmptyChecking { } } +extension AvatarStack: _ViewEmptyChecking { + public var isEmpty: Bool { + avatars.isEmpty && + avatarsTitle.isEmpty + } +} + extension Avatars: _ViewEmptyChecking { public var isEmpty: Bool { avatars.isEmpty } } +extension AvatarsTitle: _ViewEmptyChecking { + public var isEmpty: Bool { + avatarsTitle.isEmpty + } +} + extension BannerMessage: _ViewEmptyChecking { public var isEmpty: Bool { icon.isEmpty && diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/AvatarStack+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/AvatarStack+API.generated.swift deleted file mode 100644 index d60793f65..000000000 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/AvatarStack+API.generated.swift +++ /dev/null @@ -1,21 +0,0 @@ -// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery -// DO NOT EDIT -import SwiftUI - -public struct AvatarStack { - @Environment(\.avatarsModifier) private var avatarsModifier - @Environment(\.avatarBorderColor) var avatarBorderColor - @Environment(\.avatarBorderWidth) var avatarBorderWidth - @Environment(\.isAvatarCircular) var isAvatarCircular - @Environment(\.avatarSize) var avatarSize - - var _avatars: [TextOrIcon]? = nil - - public init(model: AvatarStackModel) { - self.init(avatars: model.avatars) - } - - public init(avatars: [TextOrIcon]? = nil) { - self._avatars = avatars - } -} From d590ad7468f38c978583fa786be312d9a709ff2f Mon Sep 17 00:00:00 2001 From: Xiaoyu Liu Date: Wed, 11 Sep 2024 11:15:05 +0800 Subject: [PATCH 21/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:=20HCPSDKFIO?= =?UTF-8?q?RIUIKIT-2348]=20shadow=20effect=20api=20(#791)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA: HCPSDKFIORIUIKIT-2348] shadow effect api * fix: 🐛 rename shadow name * fix: 🐛 shadow radius and offset issue --- .../Examples.xcodeproj/project.pbxproj | 4 + .../FioriSwiftUICore/CoreContentView.swift | 8 ++ .../Views/ShadowEffectExample.swift | 46 ++++++++++++ .../FioriSwiftUICore/Utils/ShadowEffect.swift | 74 +++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 Apps/Examples/Examples/FioriSwiftUICore/Views/ShadowEffectExample.swift create mode 100644 Sources/FioriSwiftUICore/Utils/ShadowEffect.swift diff --git a/Apps/Examples/Examples.xcodeproj/project.pbxproj b/Apps/Examples/Examples.xcodeproj/project.pbxproj index 2502be8b9..50ff51ec3 100644 --- a/Apps/Examples/Examples.xcodeproj/project.pbxproj +++ b/Apps/Examples/Examples.xcodeproj/project.pbxproj @@ -115,6 +115,7 @@ B18D2E9F2988B07B000A1821 /* KPIHeaderExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18D2E9E2988B07B000A1821 /* KPIHeaderExample.swift */; }; B18D593C2B0C52C700ABB1AD /* TabViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18D593B2B0C52C700ABB1AD /* TabViewExample.swift */; }; B190065A2C201BBE000C8B10 /* ProfileHeaderExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19006592C201BBE000C8B10 /* ProfileHeaderExample.swift */; }; + B1A78D982C88388B00432B0D /* ShadowEffectExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A78D972C88388B00432B0D /* ShadowEffectExample.swift */; }; B1A98FF22C11592B00FC9998 /* BannerMessageExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A98FF12C11592B00FC9998 /* BannerMessageExample.swift */; }; B1A98FF52C12E9A000FC9998 /* BannerMessageModifierExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A98FF42C12E9A000FC9998 /* BannerMessageModifierExample.swift */; }; B1A98FF72C12EA1600FC9998 /* BannerMessageCustomInitExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A98FF62C12EA1600FC9998 /* BannerMessageCustomInitExample.swift */; }; @@ -324,6 +325,7 @@ B18D2E9E2988B07B000A1821 /* KPIHeaderExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KPIHeaderExample.swift; sourceTree = ""; }; B18D593B2B0C52C700ABB1AD /* TabViewExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewExample.swift; sourceTree = ""; }; B19006592C201BBE000C8B10 /* ProfileHeaderExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderExample.swift; sourceTree = ""; }; + B1A78D972C88388B00432B0D /* ShadowEffectExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowEffectExample.swift; sourceTree = ""; }; B1A98FF12C11592B00FC9998 /* BannerMessageExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerMessageExample.swift; sourceTree = ""; }; B1A98FF42C12E9A000FC9998 /* BannerMessageModifierExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerMessageModifierExample.swift; sourceTree = ""; }; B1A98FF62C12EA1600FC9998 /* BannerMessageCustomInitExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerMessageCustomInitExample.swift; sourceTree = ""; }; @@ -727,6 +729,7 @@ children = ( 9DEC27B42C3F3DB30070B571 /* KeyValueItemExample.swift */, 9DEC27B62C3F3DE70070B571 /* OtherViewExamples.swift */, + B1A78D972C88388B00432B0D /* ShadowEffectExample.swift */, ); path = Views; sourceTree = ""; @@ -1100,6 +1103,7 @@ 8A6DE30B28DD27F9003222E3 /* Colors.swift in Sources */, 8AD9DFB225D49967007448EC /* StylingModifierExample.swift in Sources */, 9D0B260A2B9BA5C0004278A5 /* NoteFormViewExample.swift in Sources */, + B1A78D982C88388B00432B0D /* ShadowEffectExample.swift in Sources */, 9DEC27B72C3F3DE70070B571 /* OtherViewExamples.swift in Sources */, 87F492332C73ADA1002B8703 /* SimpleTimelinePreviewExample.swift in Sources */, 8A5579D124C1293C0098003A /* Settings.swift in Sources */, diff --git a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift index 8158ba9be..3e68cf6c6 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift @@ -267,6 +267,14 @@ struct CoreContentView: View { } } + Section(header: Text("Utilities")) { + NavigationLink( + destination: ShadowEffectExample()) + { + Text("Shadow Effect") + } + } + Section(header: Text("ExperimentalContentView")) { NavigationLink( destination: ExperimentalContentView()) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/Views/ShadowEffectExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/Views/ShadowEffectExample.swift new file mode 100644 index 000000000..7c64642b8 --- /dev/null +++ b/Apps/Examples/Examples/FioriSwiftUICore/Views/ShadowEffectExample.swift @@ -0,0 +1,46 @@ +import FioriSwiftUICore +import SwiftUI + +struct ShadowEffectExample: View { + var body: some View { + ScrollView { + VStack(spacing: 20) { + ForEach(FioriShadowStyle.allCases, id: \.self) { style in + ZStack { + Color.preferredColor(.secondaryGroupedBackground) + .frame(width: 300, height: 100) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .shadow(style) + .padding() + + self.styleDescription(style) + .bold() + .font(.fiori(forTextStyle: .title1)) + } + } + }.frame(maxWidth: .infinity) + } + .frame(maxWidth: .infinity) + .background(Color.preferredColor(.primaryGroupedBackground)) + .navigationTitle("Shadow Effect") + } + + @ViewBuilder func styleDescription(_ style: FioriShadowStyle) -> some View { + switch style { + case .level0: + Text("Level 0") + case .level1: + Text("Level 1") + case .level2: + Text("Level 2") + case .level3: + Text("Level 3") + case .level4: + Text("Level 3") + case .smallElement: + Text("Small Shape") + case .mediumElement: + Text("Medium Shape") + } + } +} diff --git a/Sources/FioriSwiftUICore/Utils/ShadowEffect.swift b/Sources/FioriSwiftUICore/Utils/ShadowEffect.swift new file mode 100644 index 000000000..f76dede29 --- /dev/null +++ b/Sources/FioriSwiftUICore/Utils/ShadowEffect.swift @@ -0,0 +1,74 @@ +import FioriThemeManager +import SwiftUI + +/// Fiori style shadow. +public enum FioriShadowStyle: CaseIterable { + /// Shadow level 0. Most used in banner, header or previews. + case level0 + /// Shadow Level 1. + case level1 + /// Shadow Level 2. Most used in cards or modal views. + case level2 + /// Shadow Level 3. Most used in toast message or quick sort. + case level3 + /// Shadow Level 4. Most used in popovers. + case level4 + + /// Shadow for small element. Most used in switch or slider. + case smallElement + /// Shadow for medium element. Most used in segmented control. + case mediumElement +} + +public extension View { + /// Apply a shadow with the specified fiori style. + /// - Parameter style: Shadow style. + /// - Returns: A new view with the specified shadow style. + func shadow(_ style: FioriShadowStyle) -> some View { + self.modifier(FioriShadowModifier(style: style)) + } +} + +struct FioriShadowModifier: ViewModifier { + let style: FioriShadowStyle + + let color1 = Color(white: 0, opacity: 0.13) + let color2 = Color(white: 0, opacity: 0.04) + let color3 = Color(white: 0, opacity: 0.2) + + let color4 = Color(white: 0, opacity: 0.12) + + func body(content: Content) -> some View { + switch self.style { + case .level0: + content + .shadow(color: self.color1, radius: 1) + case .level1: + content + .shadow(color: self.color1, radius: 1) + .shadow(color: self.color2, radius: 2, y: 1) + case .level2: + content + .shadow(color: self.color1, radius: 1) + .shadow(color: self.color2, radius: 4, y: 2) + case .level3: + content + .shadow(color: self.color1, radius: 1) + .shadow(color: self.color2, radius: 8, y: 8) + .shadow(color: self.color2, radius: 16, y: 16) + case .level4: + content + .shadow(color: self.color2, radius: 1) + .shadow(color: self.color2, radius: 8, y: 8) + .shadow(color: self.color3, radius: 50, y: 10) + case .smallElement: + content + .shadow(color: self.color4, radius: 6.5, y: 6) + .shadow(color: self.color4, radius: 2, y: 0.5) + case .mediumElement: + content + .shadow(color: self.color2, radius: 0.5, y: 3) + .shadow(color: self.color4, radius: 4, y: 3) + } + } +} From 692375dda1c81d96f75f25b0a4b3c0b78a9f9439 Mon Sep 17 00:00:00 2001 From: Xiaoyu Liu Date: Thu, 12 Sep 2024 02:39:32 +0800 Subject: [PATCH 22/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[JIRA:=200]=20remov?= =?UTF-8?q?e=20unnecessary=20enviroments=20tag=20(#799)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- Sources/FioriSwiftUICore/Models/ModelDefinitions.swift | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e127ed96f..64722b265 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: - name: Create code coverage report run: ./scripts/xccov-to-sonarqube-generic.sh /Users/runner/Library/Developer/Xcode/DerivedData/cloud-sdk-ios-fiori*/Logs/Test/*.xcresult/ > sonarqube-generic-coverage.xml - name: Store coverage for sonar job - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: coverage path: sonarqube-generic-coverage.xml diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index d123dba43..62b2d7857 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -12,8 +12,6 @@ public protocol IconStackModel: IconsComponent {} // sourcery: add_env_props = "footnoteIconsSpacing" // sourcery: add_env_props = "isFootnoteIconsCircular" // sourcery: add_env_props = "footnoteIconsMaxCount" -// sourcery: add_env_props = "footnoteIconsTextPosition" -// sourcery: add_env_props = "footnoteIconsText" public protocol FootnoteIconStackModel: FootnoteIconsComponent {} // sourcery: add_env_props = "horizontalSizeClass" From eec8d9c81c10bdb7db8752d02b44bb40da934487 Mon Sep 17 00:00:00 2001 From: janhuachu <48767307+janhuachu@users.noreply.github.com> Date: Wed, 11 Sep 2024 19:43:59 -0700 Subject: [PATCH 23/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:HCPSDKFIORIU?= =?UTF-8?q?IKIT-2707]=20SwiftUI=20RatingControl=20Enhancement=20(#794)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2707] SwiftUI RatingControl Enh SwiftUI RatingControl enhancement with value label and review count label ✅ Closes: HCPSDKFIORIUIKIT-2707 * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2707] Resolve review comments ✅ Closes: JIRA:HCPSDKFIORIUIKIT-2707 --- .../RatingControlFormViewExample.swift | 276 ++++++++++++++---- .../RatingControl/RatingControlExample.swift | 236 +++++++++++++-- .../DataTypes/RatingControl+DataType.swift | 120 ++++---- .../BaseComponentProtocols.swift | 33 +++ .../CompositeComponentProtocols.swift | 55 +++- .../HalfStarImageStyle.fiori.swift | 20 ++ .../OffStarImageStyle.fiori.swift | 20 ++ .../_FioriStyles/OnStarImageStyle.fiori.swift | 20 ++ .../RatingControlFormViewStyle.fiori.swift | 95 +++--- .../RatingControlStyle.fiori.swift | 213 +++++++++++--- .../ReviewCountLabelStyle.fiori.swift | 20 ++ .../HalfStarImage.generated.swift | 66 +++++ .../HalfStarImageStyle.generated.swift | 28 ++ .../OffStarImage/OffStarImage.generated.swift | 66 +++++ .../OffStarImageStyle.generated.swift | 28 ++ .../OnStarImage/OnStarImage.generated.swift | 66 +++++ .../OnStarImageStyle.generated.swift | 28 ++ .../RatingControl.generated.swift | 124 ++++++-- .../RatingControlStyle.generated.swift | 29 +- .../RatingControlFormView.generated.swift | 127 +++++--- ...RatingControlFormViewStyle.generated.swift | 31 +- .../ReviewCountLabel.generated.swift | 63 ++++ .../ReviewCountLabelStyle.generated.swift | 28 ++ ...entStyleProtocol+Extension.generated.swift | 273 ++++++++++++++++- .../EnvironmentVariables.generated.swift | 84 ++++++ .../ModifiedStyle.generated.swift | 112 +++++++ .../ResolvedStyle.generated.swift | 64 ++++ ...yleConfiguration+Extension.generated.swift | 6 +- .../View+Extension_.generated.swift | 68 +++++ ...iewEmptyChecking+Extension.generated.swift | 35 ++- .../en.lproj/FioriSwiftUICore.strings | 9 + .../RatingControlDataTests.swift | 31 +- 32 files changed, 2132 insertions(+), 342 deletions(-) create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/HalfStarImageStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/OffStarImageStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/OnStarImageStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_FioriStyles/ReviewCountLabelStyle.fiori.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/HalfStarImage/HalfStarImage.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/HalfStarImage/HalfStarImageStyle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/OffStarImage/OffStarImage.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/OffStarImage/OffStarImageStyle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/OnStarImage/OnStarImage.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/OnStarImage/OnStarImageStyle.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/ReviewCountLabel/ReviewCountLabel.generated.swift create mode 100644 Sources/FioriSwiftUICore/_generated/StyleableComponents/ReviewCountLabel/ReviewCountLabelStyle.generated.swift diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FormViews/RatingControlFormViewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FormViews/RatingControlFormViewExample.swift index dceb875f7..1ec67ffae 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/FormViews/RatingControlFormViewExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/FormViews/RatingControlFormViewExample.swift @@ -3,97 +3,247 @@ import SwiftUI struct RatingControlFormViewExample: View { @State var rating1: Int = 1 - @State var rating2: Int = 2 - @State var rating3: Int = 3 - @State var rating4: Int = 4 - - @State var rating5: Int = 1 - - @State var rating6: Int = 2 - - @State var rating7: Int = 3 - - @State var rating8: Int = 4 - - @State var rating9: Int = 1 - - @State var rating10: Int = 2 - - @State var rating11: Int = 3 - - @State var rating12: Int = 4 - - @State var showsErrorMessage = false - @State var usesCustomColors = false - @State var usesCustomImages = false + @State var rating5: Int = 5 + @State var rating6: Int = 1 + @State var rating7: Int = 2 + @State var rating8: Int = 3 + @State var rating9: Int = 4 + @State var rating10: Int = 5 + @State var rating11: Int = 1 + @State var rating12: Int = 2 + @State var rating13: Int = 3 + @State var rating14: Int = 4 + @State var rating15: Int = 5 + @State var rating16: Int = 1 + @State var rating17: Int = 2 + @State var rating18: Int = 3 + @State var rating19: Int = 3 + @State var rating20: Int = 3 + @State var rating21: Int = 3 + @State var rating22: Int = 3 + @State var rating23: Int = 3 + @State var rating24: Int = 3 + + @State var showsHintMessage = false var errorMessage = AttributedString("Error Message") + @State var showsValueLabel = false + @State var showsReviewCountLabel = false + @State var setsAverageRating = false + @State var setsReviewCount = false + @State var setsReviewCountCeiling = false + var body: some View { List { - Text("RatingControlFormView Example") - Toggle("Shows Error Message", isOn: self.$showsErrorMessage) + Text("RatingControlFormView Examples") + Toggle("Shows Hint Message", isOn: self.$showsHintMessage) + .padding(.leading, 16) + .padding(.trailing, 16) + Toggle("Shows Value Label", isOn: self.$showsValueLabel) .padding(.leading, 16) .padding(.trailing, 16) - Toggle("Custom Colors", isOn: self.$usesCustomColors) + Toggle("Shows Review Count Label", isOn: self.$showsReviewCountLabel) .padding(.leading, 16) .padding(.trailing, 16) - Toggle("Custom Images", isOn: self.$usesCustomImages) + Toggle("Sets Average Rating", isOn: self.$setsAverageRating) .padding(.leading, 16) .padding(.trailing, 16) + Toggle("Sets Review Count", isOn: self.$setsReviewCount) + .padding(.leading, 16) + .padding(.trailing, 16) + Toggle("Sets Review Count Ceiling", isOn: self.$setsReviewCountCeiling) + .padding(.leading, 16) + .padding(.trailing, 16) + Text("Default Examples") - RatingControlFormView(title: "Rating 1", rating: self.$rating1, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.normal), offColor: self.getOffColor(), errorMessage: self.getErrorMessage()) - RatingControlFormView(title: "Rating 2 (Disabled)", rating: self.$rating2, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.disabled), offColor: self.getOffColor(), controlState: .disabled, errorMessage: self.getErrorMessage()) - RatingControlFormView(title: "Rating 3 (Read Only) Rating 3 (Read Only)", rating: self.$rating3, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.readOnly), offColor: self.getOffColor(), controlState: .readOnly, errorMessage: self.getErrorMessage()) - RatingControlFormView(title: "Rating 4 (Highlighted)", rating: self.$rating4, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.highlighted), offColor: self.getOffColor(), controlState: .highlighted, errorMessage: self.getErrorMessage()) + RatingControlFormView(title: "Rating 1", rating: self.$rating1, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 2 (Disabled)", rating: self.$rating2, ratingControlStyle: .editableDisabled, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 3 (Read Only) Rating 3 (Read Only)", rating: self.$rating3, ratingControlStyle: .standard, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 4 (Read Only Large) Rating 4 (Read Only Large)", rating: self.$rating4, ratingControlStyle: .standardLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 5 (Accented)", rating: self.$rating5, ratingControlStyle: .accented, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 6 (Accented Large)", rating: self.$rating6, ratingControlStyle: .accentedLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) Text("Stacked") - RatingControlFormView(title: "Rating 5", rating: self.$rating5, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.normal), offColor: self.getOffColor(), errorMessage: self.getErrorMessage(), axis: .vertical) - RatingControlFormView(title: "Rating 6 (Disabled)", rating: self.$rating6, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.disabled), offColor: self.getOffColor(), controlState: .disabled, errorMessage: self.getErrorMessage(), axis: .vertical) - RatingControlFormView(title: "Rating 7 (Read Only)", rating: self.$rating7, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.readOnly), offColor: self.getOffColor(), controlState: .readOnly, errorMessage: self.getErrorMessage(), axis: .vertical) - RatingControlFormView(title: "Rating 8 (Highlighted)", rating: self.$rating8, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.highlighted), offColor: self.getOffColor(), controlState: .highlighted, errorMessage: self.getErrorMessage(), axis: .vertical) + RatingControlFormView(title: "Rating 7", rating: self.$rating7, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, axis: .vertical) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 8 (Disabled)", rating: self.$rating8, ratingControlStyle: .editableDisabled, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, axis: .vertical) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 9 (Read Only)", rating: self.$rating9, ratingControlStyle: .standard, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, axis: .vertical) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 10 (Read Only Large)", rating: self.$rating10, ratingControlStyle: .standardLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, axis: .vertical) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 11 (Accented)", rating: self.$rating11, ratingControlStyle: .accented, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, axis: .vertical) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 12 (Accented Large)", rating: self.$rating12, ratingControlStyle: .accentedLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, axis: .vertical) + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) Text("With Subtitle") - RatingControlFormView(title: "Rating 9", rating: self.$rating9, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.normal), offColor: self.getOffColor(), subtitle: "Rating 9 Subtitle", errorMessage: self.getErrorMessage()) - RatingControlFormView(title: "Rating 10 (Disabled)", rating: self.$rating10, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.disabled), offColor: self.getOffColor(), subtitle: "Rating 10 Subtitle", controlState: .disabled, errorMessage: self.getErrorMessage()) - RatingControlFormView(title: "Rating 11 (Read Only)", rating: self.$rating11, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.readOnly), offColor: self.getOffColor(), subtitle: "Rating 11 Subtitle", controlState: .readOnly, errorMessage: self.getErrorMessage()) - RatingControlFormView(title: "Rating 12 (Highlighted)", rating: self.$rating12, onImage: self.getOnImage(), offImage: self.getOffImage(), onColor: self.getOnColor(.highlighted), offColor: self.getOffColor(), subtitle: "Rating 12 Subtitle", controlState: .highlighted, errorMessage: self.getErrorMessage()) + RatingControlFormView(title: "Rating 13", rating: self.$rating13, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, subtitle: "Rating 13 Subtitle") + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 14 (Disabled)", rating: self.$rating14, ratingControlStyle: .editableDisabled, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, subtitle: "Rating 14 Subtitle") + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 15 (Read Only)", rating: self.$rating15, ratingControlStyle: .standard, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, subtitle: "Rating 15 Subtitle") + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 16 (Read Only Large)", rating: self.$rating16, ratingControlStyle: .standardLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, subtitle: "Rating 16 Subtitle") + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 17 (Accented)", rating: self.$rating17, ratingControlStyle: .accented, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, subtitle: "Rating 17 Subtitle") + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 18 (Accented Large)", rating: self.$rating18, ratingControlStyle: .accentedLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel, subtitle: "Rating 18 Subtitle") + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + + Text("Customized") + RatingControlFormView(title: "Rating 19", onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating19, itemSize: CGSize(width: 40, height: 40), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 20 (Disabled)", onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating20, ratingControlStyle: .editableDisabled, itemSize: CGSize(width: 30, height: 30), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 21 (Read Only) Rating 21 (Read Only)", onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating21, ratingControlStyle: .standard, itemSize: CGSize(width: 10, height: 10), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 22 (Read Only Large) Rating 22 (Read Only Large)", onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating22, ratingControlStyle: .standardLarge, itemSize: CGSize(width: 20, height: 20), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 23 (Accented)", onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating23, ratingControlStyle: .accented, itemSize: CGSize(width: 5, height: 5), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) + RatingControlFormView(title: "Rating 24 (Accented Large)", onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating24, ratingControlStyle: .accentedLarge, itemSize: CGSize(width: 10, height: 10), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .informationView(isPresented: self.$showsHintMessage, description: AttributedString("hint message")) } } - func getErrorMessage() -> AttributedString? { - self.showsErrorMessage ? self.errorMessage : nil - } - - func getOnColor(_ controlState: ControlState) -> Color? { - guard self.usesCustomColors else { + func getAverageRatingValue() -> CGFloat? { + guard self.setsAverageRating else { return nil } - switch controlState { - case .disabled: - return .orange - case .readOnly: - return .brown - case .highlighted: - return .black - default: - return .red - } - } - - func getOffColor() -> Color? { - self.usesCustomColors ? .yellow : nil + return 2.6 } - func getOnImage() -> Image? { - self.usesCustomImages ? Image(systemName: "hand.thumbsup.fill") : nil + func getReviewCount() -> Int? { + guard self.setsReviewCount else { + return nil + } + return 1234 } - func getOffImage() -> Image? { - self.usesCustomImages ? Image(systemName: "hand.thumbsdown.fill") : nil + func getReviewCountCeiling() -> Int? { + guard self.setsReviewCountCeiling else { + return nil + } + return 1000 } } diff --git a/Apps/Examples/Examples/FioriSwiftUICore/RatingControl/RatingControlExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/RatingControl/RatingControlExample.swift index 84d20d253..9fff27102 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/RatingControl/RatingControlExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/RatingControl/RatingControlExample.swift @@ -42,38 +42,216 @@ struct RatingControlExample: View { @State var rating20: Int = 4 + @State var rating21: Int = 1 + + @State var rating22: Int = 2 + + @State var rating23: Int = 3 + + @State var rating24: Int = 4 + + @State var showsValueLabel = false + @State var showsReviewCountLabel = false + @State var setsAverageRating = false + @State var setsReviewCount = false + @State var setsReviewCountCeiling = false + var body: some View { List { - Text("RatingControl Default Example") - RatingControl(rating: self.$rating1, ratingControlStyle: .editable) - RatingControl(rating: self.$rating2, ratingControlStyle: .editableDisabled) - RatingControl(rating: self.$rating3, ratingControlStyle: .standard) - RatingControl(rating: self.$rating4, ratingControlStyle: .accented) - - Text("Custom Color Example") - RatingControl(rating: self.$rating5, ratingControlStyle: .editable, onColor: .red, offColor: .yellow) - RatingControl(rating: self.$rating6, ratingControlStyle: .editableDisabled, onColor: .orange, offColor: .yellow) - RatingControl(rating: self.$rating7, ratingControlStyle: .standard, onColor: .brown, offColor: .yellow) - RatingControl(rating: self.$rating8, ratingControlStyle: .accented, onColor: .black, offColor: .yellow) - - Text("Larger Size Example") - RatingControl(rating: self.$rating9, ratingControlStyle: .editable, itemSize: CGSize(width: 50, height: 50)) - RatingControl(rating: self.$rating10, ratingControlStyle: .editableDisabled, itemSize: CGSize(width: 40, height: 40)) - RatingControl(rating: self.$rating11, ratingControlStyle: .standard, itemSize: CGSize(width: 10, height: 10)) - RatingControl(rating: self.$rating12, ratingControlStyle: .accented, itemSize: CGSize(width: 5, height: 5)) - - Text("Custom Image Example") - RatingControl(rating: self.$rating13, ratingControlStyle: .editable, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill")) - RatingControl(rating: self.$rating14, ratingControlStyle: .editableDisabled, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill")) - RatingControl(rating: self.$rating15, ratingControlStyle: .standard, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill")) - RatingControl(rating: self.$rating16, ratingControlStyle: .accented, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill")) - - Text("Custom Number of Stars Example") - RatingControl(rating: self.$rating17, ratingControlStyle: .editable, ratingBounds: -5 ... 5) - RatingControl(rating: self.$rating18, ratingControlStyle: .editableDisabled, ratingBounds: -5 ... 5) - RatingControl(rating: self.$rating19, ratingControlStyle: .standard, ratingBounds: -5 ... 5) - RatingControl(rating: self.$rating20, ratingControlStyle: .accented, ratingBounds: -5 ... 5) + Text("RatingControl Examples") + Toggle("Shows Value Label", isOn: self.$showsValueLabel) + .padding(.leading, 16) + .padding(.trailing, 16) + Toggle("Shows Review Count Label", isOn: self.$showsReviewCountLabel) + .padding(.leading, 16) + .padding(.trailing, 16) + Toggle("Sets Average Rating", isOn: self.$setsAverageRating) + .padding(.leading, 16) + .padding(.trailing, 16) + Toggle("Sets Review Count", isOn: self.$setsReviewCount) + .padding(.leading, 16) + .padding(.trailing, 16) + Toggle("Sets Review Count Ceiling", isOn: self.$setsReviewCountCeiling) + .padding(.leading, 16) + .padding(.trailing, 16) + + Text("RatingControl Default Examples") + RatingControl(rating: self.$rating1, ratingControlStyle: .editable, showsValueLabel: self.showsValueLabel) + RatingControl(rating: self.$rating2, ratingControlStyle: .editableDisabled, showsValueLabel: self.showsValueLabel) + RatingControl(rating: self.$rating3, ratingControlStyle: .standard, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(rating: self.$rating4, ratingControlStyle: .standardLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(rating: self.$rating5, ratingControlStyle: .accented, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(rating: self.$rating6, ratingControlStyle: .accentedLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + + Text("Custom Color and Font Examples") + RatingControl(rating: self.$rating7, ratingControlStyle: .editable, showsValueLabel: self.showsValueLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + RatingControl(rating: self.$rating8, ratingControlStyle: .editableDisabled, showsValueLabel: self.showsValueLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + RatingControl(rating: self.$rating9, ratingControlStyle: .standard, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .halfStarImageStyle { style in + HalfStarImage(style) + .foregroundStyle(.green) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + RatingControl(rating: self.$rating10, ratingControlStyle: .standardLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .halfStarImageStyle { style in + HalfStarImage(style) + .foregroundStyle(.green) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + RatingControl(rating: self.$rating11, ratingControlStyle: .accented, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { style in + OnStarImage(style) + .foregroundStyle(.red) + } + .offStarImageStyle { style in + OffStarImage(style) + .foregroundStyle(.yellow) + } + .halfStarImageStyle { style in + HalfStarImage(style) + .foregroundStyle(.green) + } + .valueLabelStyle { style in + ValueLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { style in + ReviewCountLabel(style) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + RatingControl(rating: self.$rating12, ratingControlStyle: .accentedLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + .onStarImageStyle { + OnStarImage($0) + .foregroundStyle(.red) + } + .offStarImageStyle { config in + OffStarImage(config) + .foregroundStyle(.yellow) + } + .halfStarImageStyle { config in + HalfStarImage(config) + .foregroundStyle(.green) + } + .valueLabelStyle { config in + ValueLabel(config) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + .reviewCountLabelStyle { config in + ReviewCountLabel(config) + .font(.fiori(forTextStyle: .headline)) + .foregroundStyle(.brown) + } + + Text("Custom Size Examples") + RatingControl(rating: self.$rating13, ratingControlStyle: .editable, itemSize: CGSize(width: 40, height: 40), showsValueLabel: self.showsValueLabel) + RatingControl(rating: self.$rating14, ratingControlStyle: .editableDisabled, itemSize: CGSize(width: 30, height: 30), showsValueLabel: self.showsValueLabel) + RatingControl(rating: self.$rating15, ratingControlStyle: .standard, itemSize: CGSize(width: 10, height: 10), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(rating: self.$rating16, ratingControlStyle: .standardLarge, itemSize: CGSize(width: 20, height: 20), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(rating: self.$rating17, ratingControlStyle: .accented, itemSize: CGSize(width: 5, height: 5), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(rating: self.$rating18, ratingControlStyle: .accentedLarge, itemSize: CGSize(width: 10, height: 10), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + + Text("Custom Image Examples") + RatingControl(onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating19, ratingControlStyle: .editable, showsValueLabel: self.showsValueLabel) + RatingControl(onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating20, ratingControlStyle: .editableDisabled, showsValueLabel: self.showsValueLabel) + RatingControl(onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating21, ratingControlStyle: .standard, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating22, ratingControlStyle: .standardLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating23, ratingControlStyle: .accented, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + RatingControl(onStarImage: Image(systemName: "hand.thumbsup.fill").renderingMode(.template).resizable(), offStarImage: Image(systemName: "hand.thumbsdown.fill").renderingMode(.template).resizable(), halfStarImage: Image(systemName: "hand.thumbsup.circle").renderingMode(.template).resizable(), rating: self.$rating24, ratingControlStyle: .accentedLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel) + } + } + + func getAverageRatingValue() -> CGFloat? { + guard self.setsAverageRating else { + return nil + } + return 2.6 + } + + func getReviewCount() -> Int? { + guard self.setsReviewCount else { + return nil + } + return 1234 + } + + func getReviewCountCeiling() -> Int? { + guard self.setsReviewCountCeiling else { + return nil } + return 1000 } } diff --git a/Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift index 71b81ae2e..5990d4a58 100644 --- a/Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift +++ b/Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift @@ -1,14 +1,15 @@ +import FioriThemeManager import SwiftUI public extension RatingControl { /** - The available styles for the `FUIRatingControl`. + The available styles for the `RatingControl`. */ enum Style { /** Editable style. - Each rating star is a SF Symbol body light style (large scale) with tint color. + Each rating star is large scale with tint color. This is the default style. */ case editable @@ -16,23 +17,37 @@ public extension RatingControl { /** Disabled editable style. - Each rating star is the same as `Editable` style but with grey color. + Each rating star is the same as `Editable` style but with grey color. User interaction is disabled. */ case editableDisabled /** Standard style. - This `FUIRatingControl` is read-only. Each rating star is a SF Symbol body light style (small scale). + This `RatingControl` is read-only. Each rating star is in small scale. */ case standard + /** + Standard large style. + + This `RatingControl` is read-only. Each rating star is in large scale. + */ + case standardLarge + /** Accented read-only style. - This `FUIRatingControl` is read-only with accented color. Each rating star is the same as in `standard` style. + This `RatingControl` is read-only. Each rating star is in small scale with accented color. */ case accented + + /** + Accented read-only style. + + This `RatingControl` is read-only. Each rating star is in large scale with accented color. + */ + case accentedLarge } internal static func getAccessibilityLabelString(_ rating: Int, ratingBounds: ClosedRange) -> String { @@ -45,6 +60,7 @@ extension RatingControlConfiguration { struct RatingItem: Identifiable { public let id = UUID() let isOn: Bool + let isHalf: Bool } func ratingItems(_ rating: Int) -> [RatingItem] { @@ -53,80 +69,78 @@ extension RatingControlConfiguration { guard i != self.ratingBounds.upperBound else { continue } - items.append(RatingItem(isOn: i < rating)) + items.append(RatingItem(isOn: i < rating, isHalf: false)) } return items } - func getOnColor() -> Color { - if let onColor { - return onColor + func ratingItems(_ averageRating: CGFloat) -> [RatingItem] { + var items: [RatingItem] = [] + for i in self.ratingBounds { + guard i != self.ratingBounds.upperBound else { + continue + } + let diff = averageRating - CGFloat(i) + if diff < 0.25 { + items.append(RatingItem(isOn: false, isHalf: false)) + } else if diff < 0.75 { + items.append(RatingItem(isOn: false, isHalf: true)) + } else { + items.append(RatingItem(isOn: true, isHalf: false)) + } } + return items + } + + func getOnColor() -> Color { switch self.ratingControlStyle { case .editable: return .preferredColor(.tintColor) case .editableDisabled: return .preferredColor(.quaternaryLabel) - case .standard: + case .standard, .standardLarge: return .preferredColor(.tertiaryLabel) - case .accented: - return .preferredColor(.mango3) + case .accented, .accentedLarge: + return .preferredColor(.mango8) } } func getOffColor() -> Color { - if let offColor { - return offColor - } switch self.ratingControlStyle { case .editable: return .preferredColor(.tintColor) case .editableDisabled: return .preferredColor(.quaternaryLabel) - case .standard: + case .standard, .standardLarge: return .preferredColor(.tertiaryLabel) - case .accented: - return .preferredColor(.mango4) + case .accented, .accentedLarge: + return .preferredColor(.mango8) } } - func getOnImageView() -> some View { - self.getOnImage() - .resizable() - .scaledToFit() - .frame(width: self.getItemSize().width, height: self.getItemSize().height) - .font(.body) - .fontWeight(.light) - .imageScale(self.getScale()) - .foregroundColor(self.getOnColor()) - } - - func getOffImageView() -> some View { - self.getOffImage() - .resizable() - .scaledToFit() - .frame(width: self.getItemSize().width, height: self.getItemSize().height) - .font(.body) - .fontWeight(.light) - .imageScale(self.getScale()) - .foregroundColor(self.getOffColor()) - } - - func getOnImage() -> Image { - let image: Image = (onImage ?? Image(systemName: "star.fill")) - .renderingMode(.template) - return image + func getDefaultLabelFont() -> Font { + switch ratingControlStyle { + case .editable, .editableDisabled, .standardLarge, .accentedLarge: + return .fiori(forTextStyle: .body) + case .standard, .accented: + return .fiori(forTextStyle: .subheadline) + } } - func getOffImage() -> Image { - let image: Image = (offImage ?? Image(systemName: "star")) - .renderingMode(.template) - return image + func getDefaultLabelColor() -> Color { + switch ratingControlStyle { + case .editable: + return .preferredColor(.primaryLabel) + case .editableDisabled: + return .preferredColor(.quaternaryLabel) + case .standard, .standardLarge, .accented, .accentedLarge: + return .preferredColor(.tertiaryLabel) + } } func getScale() -> Image.Scale { switch self.ratingControlStyle { - case .editable, .editableDisabled: + case .editable, .editableDisabled, .standardLarge, .accentedLarge: return .large case .standard, .accented: return .small @@ -138,7 +152,7 @@ extension RatingControlConfiguration { return itemSize } switch self.ratingControlStyle { - case .editable, .editableDisabled: + case .editable, .editableDisabled, .standardLarge, .accentedLarge: return CGSize(width: 28, height: 28) case .standard, .accented: return CGSize(width: 16, height: 16) @@ -150,10 +164,10 @@ extension RatingControlConfiguration { return interItemSpacing } switch self.ratingControlStyle { - case .editable, .editableDisabled: - return CGFloat(4) + case .editable, .editableDisabled, .standardLarge, .accentedLarge: + return CGFloat(8) case .standard, .accented: - return CGFloat(2) + return CGFloat(6) } } diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift index 928af4edb..37d475344 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/BaseComponentProtocols.swift @@ -339,3 +339,36 @@ protocol _AvatarsTitleComponent { // sourcery: @ViewBuilder var avatarsTitle: AttributedString? { get } } + +// sourcery: BaseComponent +protocol _ReviewCountLabelComponent { + // sourcery: @ViewBuilder + var reviewCountLabel: AttributedString? { get } +} + +// sourcery: BaseComponent +// sourcery: importFrameworks = ["FioriThemeManager"] +protocol _OnStarImageComponent { + /// The image to be used for "On" rating star. + // sourcery: @ViewBuilder + // sourcery: defaultValue = "FioriIcon.actions.favorite.renderingMode(.template).resizable()" + var onStarImage: Image { get } +} + +// sourcery: BaseComponent +// sourcery: importFrameworks = ["FioriThemeManager"] +protocol _OffStarImageComponent { + //// The image to be used for "Off" rating star. + // sourcery: @ViewBuilder + // sourcery: defaultValue = "FioriIcon.actions.unfavorite.renderingMode(.template).resizable()" + var offStarImage: Image { get } +} + +// sourcery: BaseComponent +// sourcery: importFrameworks = ["FioriThemeManager"] +protocol _HalfStarImageComponent { + //// The image to be used for "half" rating star. + // sourcery: @ViewBuilder + // sourcery: defaultValue = "FioriIcon.actions.halfStar.renderingMode(.template).resizable()" + var halfStarImage: Image { get } +} diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index f3cd209f0..e3c1cef86 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -295,7 +295,8 @@ protocol _BannerMessageComponent: _IconComponent, _TitleComponent, _CloseActionC /// The default "On" image is a filled star while the default "Off" inmage /// is an unfilled star. // sourcery: CompositeComponent -protocol _RatingControlComponent { +// sourcery: importFrameworks = ["FioriThemeManager"] +protocol _RatingControlComponent: _ValueLabelComponent, _OnStarImageComponent, _OffStarImageComponent, _HalfStarImageComponent, _ReviewCountLabelComponent { // sourcery: @Binding /// The rating value. var rating: Int { get } @@ -308,23 +309,48 @@ protocol _RatingControlComponent { // sourcery: defaultValue = 0...5 var ratingBounds: ClosedRange { get } - /// The custom image to be used for "On". - var onImage: Image? { get } - - /// The custom image to be used for "Off". - var offImage: Image? { get } - /// The custom fixed size of each item image view. var itemSize: CGSize? { get } - /// The custom color for the ON image. - var onColor: Color? { get } - - /// The custom color for the OFF image. - var offColor: Color? { get } - /// The custom spacing between images. var interItemSpacing: CGFloat? { get } + + /// The rating format for displaying rating value. + /// When this is `nil`, the default format is "%d of %d" where "of" is the localized "of". The first parameter is the rating value while the second parameter is the total number of stars. + // sourcery: defaultValue = "nil" + var ratingValueFormat: String? { get } + + /// This property indicates if the value label is to be displayed or not. The default value is `false` for backward compatibility. + // sourcery: defaultValue = "false" + var showsValueLabel: Bool { get } + + /// The average rating for read-only style. + // sourcery: defaultValue = "nil" + var averageRating: CGFloat? { get } + + /// The format for display the average rating. The default is "%.1f" + // sourcery: defaultValue = ""%.1f"" + var averageRatingFormat: String { get } + + /// The number of reviews. + // sourcery: defaultValue = "nil" + var reviewCount: Int? { get } + + /// The format for the review count string. The default is "%d reviews" where "reviews" is the localized "reviews" string. + // sourcery: defaultValue = "nil" + var reviewCountFormat: String? { get } + + /// The ceiling number to be displayed for review count. If the `reviewCount` is larger than this number, this number will be displayed with a "+" sign after the number. + // sourcery: defaultValue = "nil" + var reviewCountCeiling: Int? { get } + + /// The format for the review count string when the count is over the ceiling. The default is "%d+ reviews" where "reviews" is the localized "reviews" string. + // sourcery: defaultValue = "nil" + var reviewCountCeilingFormat: String? { get } + + /// This property indicates if the review count label is to be displayed or not. The default value is `false` for backward compatibility. + // sourcery: defaultValue = "false" + var showsReviewCountLabel: Bool { get } } /// `TimelineMarker` is a non-selectable view intended for timelineMarkers that require beforeStart, start, beforeEnd and end status that displays timelineMarker details. @@ -392,7 +418,8 @@ protocol _TimelineNowIndicatorComponent: _NowIndicatorNodeComponent {} /// The form view which contains a title, rating control, and a subtitle // sourcery: CompositeComponent -protocol _RatingControlFormViewComponent: _TitleComponent, _RatingControlComponent, _SubtitleComponent, _FormViewComponent { +// sourcery: importFrameworks = ["FioriThemeManager"] +protocol _RatingControlFormViewComponent: _TitleComponent, _RatingControlComponent, _SubtitleComponent { /// Indicates if the axis for displaying the title and rating control. // sourcery: defaultValue = .horizontal var axis: Axis { get } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/HalfStarImageStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/HalfStarImageStyle.fiori.swift new file mode 100644 index 000000000..c541db53b --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/HalfStarImageStyle.fiori.swift @@ -0,0 +1,20 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct HalfStarImageBaseStyle: HalfStarImageStyle { + @ViewBuilder + public func makeBody(_ configuration: HalfStarImageConfiguration) -> some View { + // Add default layout here + configuration.halfStarImage + } +} + +// Default fiori styles +public struct HalfStarImageFioriStyle: HalfStarImageStyle { + @ViewBuilder + public func makeBody(_ configuration: HalfStarImageConfiguration) -> some View { + HalfStarImage(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/OffStarImageStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/OffStarImageStyle.fiori.swift new file mode 100644 index 000000000..c00a5bd5f --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/OffStarImageStyle.fiori.swift @@ -0,0 +1,20 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct OffStarImageBaseStyle: OffStarImageStyle { + @ViewBuilder + public func makeBody(_ configuration: OffStarImageConfiguration) -> some View { + // Add default layout here + configuration.offStarImage + } +} + +// Default fiori styles +public struct OffStarImageFioriStyle: OffStarImageStyle { + @ViewBuilder + public func makeBody(_ configuration: OffStarImageConfiguration) -> some View { + OffStarImage(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/OnStarImageStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/OnStarImageStyle.fiori.swift new file mode 100644 index 000000000..77a95db5b --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/OnStarImageStyle.fiori.swift @@ -0,0 +1,20 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct OnStarImageBaseStyle: OnStarImageStyle { + @ViewBuilder + public func makeBody(_ configuration: OnStarImageConfiguration) -> some View { + // Add default layout here + configuration.onStarImage + } +} + +// Default fiori styles +public struct OnStarImageFioriStyle: OnStarImageStyle { + @ViewBuilder + public func makeBody(_ configuration: OnStarImageConfiguration) -> some View { + OnStarImage(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_FioriStyles/RatingControlFormViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/RatingControlFormViewStyle.fiori.swift index 5fa011e9c..a9a9b28a8 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/RatingControlFormViewStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/RatingControlFormViewStyle.fiori.swift @@ -9,7 +9,7 @@ public struct RatingControlFormViewBaseStyle: RatingControlFormViewStyle { VStack { if !configuration.subtitle.isEmpty { VStack(spacing: 4.0) { - self.getRatingControl(configuration) + configuration._ratingControl .frame(maxWidth: .infinity) .frame(alignment: .center) configuration.subtitle @@ -24,23 +24,22 @@ public struct RatingControlFormViewBaseStyle: RatingControlFormViewStyle { } HStack { Spacer() - self.getRatingControl(configuration) + configuration._ratingControl } } } else { HStack { configuration.title Spacer() - self.getRatingControl(configuration) + configuration._ratingControl + .layoutPriority(2) } } } - .informationView(isPresented: Binding(get: { self.isInfoViewNeeded(configuration) }, set: { _ in }), description: configuration.errorMessage ?? "") - .informationViewStyle(.error) - .padding(.bottom, self.isInfoViewNeeded(configuration) ? -11.0 : 0) + .padding(.bottom, 0) .accessibilityElement(children: .combine) - .setAccessibilityAdjustable(self.getRatingControl(configuration).ratingControlStyle == .editable) { direction in - if self.getRatingControl(configuration).ratingControlStyle == .editable { + .setAccessibilityAdjustable(configuration.ratingControlStyle == .editable) { direction in + if configuration.ratingControlStyle == .editable { switch direction { case .increment: let incrementValue = configuration.rating + 1 @@ -57,30 +56,6 @@ public struct RatingControlFormViewBaseStyle: RatingControlFormViewStyle { } } - func getRatingControl(_ configuration: RatingControlFormViewConfiguration) -> RatingControl { - // the rating control style is based on the controlState - let ratingControlStyle: RatingControl.Style - switch configuration.controlState { - case .disabled: - ratingControlStyle = .editableDisabled - case .readOnly: - ratingControlStyle = .standard - case .highlighted: - ratingControlStyle = .accented - default: - ratingControlStyle = .editable - } - - return RatingControl(rating: configuration.$rating, ratingControlStyle: ratingControlStyle, ratingBounds: configuration.ratingBounds, onImage: configuration.onImage, offImage: configuration.offImage, itemSize: configuration.itemSize, onColor: configuration.onColor, offColor: configuration.offColor, interItemSpacing: configuration.interItemSpacing) - } - - func isInfoViewNeeded(_ configuration: RatingControlFormViewConfiguration) -> Bool { - if let errorMessage = configuration.errorMessage, !errorMessage.characters.isEmpty { - return true - } - return false - } - func setRatingValue(_ configuration: RatingControlFormViewConfiguration, newRating: Int) { if configuration.rating != newRating { configuration.rating = newRating @@ -107,8 +82,8 @@ extension RatingControlFormViewFioriStyle { } func getTitleColor() -> Color { - switch self.ratingControlFormViewConfiguration.controlState { - case .disabled: + switch self.ratingControlFormViewConfiguration.ratingControlStyle { + case .editableDisabled: return .preferredColor(.separator) default: return .preferredColor(.primaryLabel) @@ -116,6 +91,46 @@ extension RatingControlFormViewFioriStyle { } } + struct ValueLabelFioriStyle: ValueLabelStyle { + let ratingControlFormViewConfiguration: RatingControlFormViewConfiguration + + func makeBody(_ configuration: ValueLabelConfiguration) -> some View { + ValueLabel(configuration) + } + } + + struct OnStarImageFioriStyle: OnStarImageStyle { + let ratingControlFormViewConfiguration: RatingControlFormViewConfiguration + + func makeBody(_ configuration: OnStarImageConfiguration) -> some View { + OnStarImage(configuration) + } + } + + struct OffStarImageFioriStyle: OffStarImageStyle { + let ratingControlFormViewConfiguration: RatingControlFormViewConfiguration + + func makeBody(_ configuration: OffStarImageConfiguration) -> some View { + OffStarImage(configuration) + } + } + + struct HalfStarImageFioriStyle: HalfStarImageStyle { + let ratingControlFormViewConfiguration: RatingControlFormViewConfiguration + + func makeBody(_ configuration: HalfStarImageConfiguration) -> some View { + HalfStarImage(configuration) + } + } + + struct ReviewCountLabelFioriStyle: ReviewCountLabelStyle { + let ratingControlFormViewConfiguration: RatingControlFormViewConfiguration + + func makeBody(_ configuration: ReviewCountLabelConfiguration) -> some View { + ReviewCountLabel(configuration) + } + } + struct SubtitleFioriStyle: SubtitleStyle { let ratingControlFormViewConfiguration: RatingControlFormViewConfiguration @@ -126,8 +141,8 @@ extension RatingControlFormViewFioriStyle { } func getSubtitleColor() -> Color { - switch self.ratingControlFormViewConfiguration.controlState { - case .disabled: + switch self.ratingControlFormViewConfiguration.ratingControlStyle { + case .editableDisabled: return .preferredColor(.separator) default: return .preferredColor(.tertiaryLabel) @@ -142,12 +157,4 @@ extension RatingControlFormViewFioriStyle { RatingControl(configuration) } } - - struct FormViewFioriStyle: FormViewStyle { - let ratingControlFormViewConfiguration: RatingControlFormViewConfiguration - - func makeBody(_ configuration: FormViewConfiguration) -> some View { - FormView(configuration) - } - } } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/RatingControlStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/RatingControlStyle.fiori.swift index 272bded4f..7bd546859 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/RatingControlStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/RatingControlStyle.fiori.swift @@ -4,51 +4,107 @@ import SwiftUI // Base Layout style public struct RatingControlBaseStyle: RatingControlStyle { + @Environment(\.sizeCategory) var sizeCategory + public func makeBody(_ configuration: RatingControlConfiguration) -> some View { - HStack(spacing: configuration.getItemSpacing()) { - ForEach(configuration.ratingItems(configuration.rating)) { ratingItem in - if ratingItem.isOn { - configuration.getOnImageView() - } else { - configuration.getOffImageView() + self.getMainBody(configuration) + .onTapGesture { location in + if configuration.ratingControlStyle == .editable { + self.setRatingValue(configuration, location: location, isTap: true) } } - } - .onTapGesture { location in - if configuration.ratingControlStyle == .editable { - self.setRatingValue(configuration, location: location) + .gesture( + DragGesture(minimumDistance: 0.5) + .onChanged { value in + if configuration.ratingControlStyle == .editable { + self.setRatingValue(configuration, location: value.location) + } + } + ) + .accessibilityElement() + .accessibilityLabel(self.getAccessibilityLabel(configuration)) + .setAccessibilityAdjustable(configuration.ratingControlStyle == .editable) { direction in + if configuration.ratingControlStyle == .editable { + switch direction { + case .increment: + let incrementValue = configuration.rating + 1 + let newRating = min(configuration.ratingBounds.upperBound, incrementValue) + self.setRatingValue(configuration, newRating: newRating) + case .decrement: + let decrementValue = configuration.rating - 1 + let newRating = max(configuration.ratingBounds.lowerBound, decrementValue) + self.setRatingValue(configuration, newRating: newRating) + @unknown default: + break + } + } + } + } + + @ViewBuilder func getMainBody(_ configuration: RatingControlConfiguration) -> some View { + if self.sizeCategory >= ContentSizeCategory.extraExtraExtraLarge { + VStack { + self.getFirstLabel(configuration) + HStack(spacing: configuration.getItemSpacing()) { + self.getRatingView(configuration) + } + self.getSecondLabel(configuration) + } + } else { + HStack(spacing: configuration.getItemSpacing()) { + self.getFirstLabel(configuration) + self.getRatingView(configuration) + self.getSecondLabel(configuration) } } - .gesture( - DragGesture(minimumDistance: 0.5) - .onChanged { value in - if configuration.ratingControlStyle == .editable { - self.setRatingValue(configuration, location: value.location) - } + } + + @ViewBuilder func getFirstLabel(_ configuration: RatingControlConfiguration) -> some View { + if configuration.showsValueLabel, self.getReadOnly(configuration) { + // shows value label + ValueLabel(valueLabel: AttributedString(self.getValueText(configuration))) + } + } + + @ViewBuilder func getRatingView(_ configuration: RatingControlConfiguration) -> some View { + if self.getReadOnly(configuration), let averageRating = configuration.averageRating { + ForEach(configuration.ratingItems(averageRating)) { ratingItem in + if ratingItem.isOn { + configuration.onStarImage + } else if ratingItem.isHalf { + configuration.halfStarImage + } else { + configuration.offStarImage } - ) - .accessibilityElement() - .accessibilityLabel(self.getAccessibilityLabel(configuration)) - .setAccessibilityAdjustable(configuration.ratingControlStyle == .editable) { direction in - if configuration.ratingControlStyle == .editable { - switch direction { - case .increment: - let incrementValue = configuration.rating + 1 - let newRating = min(configuration.ratingBounds.upperBound, incrementValue) - self.setRatingValue(configuration, newRating: newRating) - case .decrement: - let decrementValue = configuration.rating - 1 - let newRating = max(configuration.ratingBounds.lowerBound, decrementValue) - self.setRatingValue(configuration, newRating: newRating) - @unknown default: - break + } + } else { + ForEach(configuration.ratingItems(configuration.rating)) { ratingItem in + if ratingItem.isOn { + configuration.onStarImage + } else { + configuration.offStarImage } } } } - func setRatingValue(_ configuration: RatingControlConfiguration, location: CGPoint) { - let newValue = configuration.getRatingValue(location) + @ViewBuilder func getSecondLabel(_ configuration: RatingControlConfiguration) -> some View { + if configuration.showsValueLabel, !self.getReadOnly(configuration) { + // shows value label + ValueLabel(valueLabel: AttributedString(self.getValueText(configuration))) + } + if let reviewCount = configuration.reviewCount, configuration.showsReviewCountLabel, getReadOnly(configuration) { + // shows review count + ReviewCountLabel(reviewCountLabel: AttributedString(self.getReviewCountText(configuration, reviewCount: reviewCount))) + } + } + + func setRatingValue(_ configuration: RatingControlConfiguration, location: CGPoint, isTap: Bool = false) { + var newValue = configuration.getRatingValue(location) + if isTap, newValue == 1, configuration.rating == 1 { + // Tap on the first star is set rating to 0 if original rating is 1 + newValue = 0 + } self.setRatingValue(configuration, newRating: newValue) } @@ -62,6 +118,33 @@ public struct RatingControlBaseStyle: RatingControlStyle { func getAccessibilityLabel(_ configuration: RatingControlConfiguration) -> String { RatingControl.getAccessibilityLabelString(configuration.rating, ratingBounds: configuration.ratingBounds) } + + func getReadOnly(_ configuration: RatingControlConfiguration) -> Bool { + switch configuration.ratingControlStyle { + case .editable, .editableDisabled: + return false + default: + return true + } + } + + func getValueText(_ configuration: RatingControlConfiguration) -> String { + guard self.getReadOnly(configuration) else { + let format = configuration.ratingValueFormat ?? NSLocalizedString("%d of %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, configuration.rating, configuration.ratingBounds.upperBound) + } + let avgRating = configuration.averageRating ?? CGFloat(configuration.rating) + return String(format: configuration.averageRatingFormat, avgRating, configuration.ratingBounds.upperBound) + } + + func getReviewCountText(_ configuration: RatingControlConfiguration, reviewCount: Int) -> String { + if let reviewCountCeiling = configuration.reviewCountCeiling, reviewCount > reviewCountCeiling { + let format = configuration.reviewCountCeilingFormat ?? NSLocalizedString("%d+ reviews", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, reviewCountCeiling) + } + let format = configuration.reviewCountFormat ?? NSLocalizedString("%d reviews", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, reviewCount) + } } // Default fiori styles @@ -71,6 +154,68 @@ extension RatingControlFioriStyle { RatingControl(configuration) } } + + struct ValueLabelFioriStyle: ValueLabelStyle { + let ratingControlConfiguration: RatingControlConfiguration + + func makeBody(_ configuration: ValueLabelConfiguration) -> some View { + ValueLabel(configuration) + .font(self.ratingControlConfiguration.getDefaultLabelFont()) + .foregroundStyle(self.ratingControlConfiguration.getDefaultLabelColor()) + } + } + + struct OnStarImageFioriStyle: OnStarImageStyle { + let ratingControlConfiguration: RatingControlConfiguration + + func makeBody(_ configuration: OnStarImageConfiguration) -> some View { + OnStarImage(configuration) + .scaledToFit() + .frame(width: self.ratingControlConfiguration.getItemSize().width, height: self.ratingControlConfiguration.getItemSize().height) + .font(.body) + .fontWeight(.light) + .imageScale(self.ratingControlConfiguration.getScale()) + .foregroundColor(self.ratingControlConfiguration.getOnColor()) + } + } + + struct OffStarImageFioriStyle: OffStarImageStyle { + let ratingControlConfiguration: RatingControlConfiguration + + func makeBody(_ configuration: OffStarImageConfiguration) -> some View { + OffStarImage(configuration) + .scaledToFit() + .frame(width: self.ratingControlConfiguration.getItemSize().width, height: self.ratingControlConfiguration.getItemSize().height) + .font(.body) + .fontWeight(.light) + .imageScale(self.ratingControlConfiguration.getScale()) + .foregroundColor(self.ratingControlConfiguration.getOffColor()) + } + } + + struct HalfStarImageFioriStyle: HalfStarImageStyle { + let ratingControlConfiguration: RatingControlConfiguration + + func makeBody(_ configuration: HalfStarImageConfiguration) -> some View { + HalfStarImage(configuration) + .scaledToFit() + .frame(width: self.ratingControlConfiguration.getItemSize().width, height: self.ratingControlConfiguration.getItemSize().height) + .font(.fiori(forTextStyle: .body)) + .fontWeight(.light) + .imageScale(self.ratingControlConfiguration.getScale()) + .foregroundColor(self.ratingControlConfiguration.getOnColor()) + } + } + + struct ReviewCountLabelFioriStyle: ReviewCountLabelStyle { + let ratingControlConfiguration: RatingControlConfiguration + + func makeBody(_ configuration: ReviewCountLabelConfiguration) -> some View { + ReviewCountLabel(configuration) + .font(self.ratingControlConfiguration.getDefaultLabelFont()) + .foregroundStyle(self.ratingControlConfiguration.getDefaultLabelColor()) + } + } } extension View { diff --git a/Sources/FioriSwiftUICore/_FioriStyles/ReviewCountLabelStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/ReviewCountLabelStyle.fiori.swift new file mode 100644 index 000000000..8aa90d66c --- /dev/null +++ b/Sources/FioriSwiftUICore/_FioriStyles/ReviewCountLabelStyle.fiori.swift @@ -0,0 +1,20 @@ +import FioriThemeManager +import Foundation +import SwiftUI + +// Base Layout style +public struct ReviewCountLabelBaseStyle: ReviewCountLabelStyle { + @ViewBuilder + public func makeBody(_ configuration: ReviewCountLabelConfiguration) -> some View { + // Add default layout here + configuration.reviewCountLabel + } +} + +// Default fiori styles +public struct ReviewCountLabelFioriStyle: ReviewCountLabelStyle { + @ViewBuilder + public func makeBody(_ configuration: ReviewCountLabelConfiguration) -> some View { + ReviewCountLabel(configuration) + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/HalfStarImage/HalfStarImage.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/HalfStarImage/HalfStarImage.generated.swift new file mode 100644 index 000000000..bf42c7f0b --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/HalfStarImage/HalfStarImage.generated.swift @@ -0,0 +1,66 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +import FioriThemeManager + +public struct HalfStarImage { + /// / The image to be used for "half" rating star. + let halfStarImage: any View + + @Environment(\.halfStarImageStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder halfStarImage: () -> any View) { + self.halfStarImage = halfStarImage() + } +} + +public extension HalfStarImage { + init(halfStarImage: Image = FioriIcon.actions.halfStar.renderingMode(.template).resizable()) { + self.init(halfStarImage: { halfStarImage }) + } +} + +public extension HalfStarImage { + init(_ configuration: HalfStarImageConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: HalfStarImageConfiguration, shouldApplyDefaultStyle: Bool) { + self.halfStarImage = configuration.halfStarImage + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension HalfStarImage: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(halfStarImage: .init(self.halfStarImage))).typeErased + .transformEnvironment(\.halfStarImageStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension HalfStarImage { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + HalfStarImage(.init(halfStarImage: .init(self.halfStarImage))) + .shouldApplyDefaultStyle(false) + .halfStarImageStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/HalfStarImage/HalfStarImageStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/HalfStarImage/HalfStarImageStyle.generated.swift new file mode 100644 index 000000000..2232c4cf3 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/HalfStarImage/HalfStarImageStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol HalfStarImageStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: HalfStarImageConfiguration) -> Body +} + +struct AnyHalfStarImageStyle: HalfStarImageStyle { + let content: (HalfStarImageConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (HalfStarImageConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: HalfStarImageConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct HalfStarImageConfiguration { + public let halfStarImage: HalfStarImage + + public typealias HalfStarImage = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OffStarImage/OffStarImage.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OffStarImage/OffStarImage.generated.swift new file mode 100644 index 000000000..136ca7547 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OffStarImage/OffStarImage.generated.swift @@ -0,0 +1,66 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +import FioriThemeManager + +public struct OffStarImage { + /// / The image to be used for "Off" rating star. + let offStarImage: any View + + @Environment(\.offStarImageStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder offStarImage: () -> any View) { + self.offStarImage = offStarImage() + } +} + +public extension OffStarImage { + init(offStarImage: Image = FioriIcon.actions.unfavorite.renderingMode(.template).resizable()) { + self.init(offStarImage: { offStarImage }) + } +} + +public extension OffStarImage { + init(_ configuration: OffStarImageConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: OffStarImageConfiguration, shouldApplyDefaultStyle: Bool) { + self.offStarImage = configuration.offStarImage + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension OffStarImage: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(offStarImage: .init(self.offStarImage))).typeErased + .transformEnvironment(\.offStarImageStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension OffStarImage { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + OffStarImage(.init(offStarImage: .init(self.offStarImage))) + .shouldApplyDefaultStyle(false) + .offStarImageStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OffStarImage/OffStarImageStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OffStarImage/OffStarImageStyle.generated.swift new file mode 100644 index 000000000..c397eddcf --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OffStarImage/OffStarImageStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol OffStarImageStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: OffStarImageConfiguration) -> Body +} + +struct AnyOffStarImageStyle: OffStarImageStyle { + let content: (OffStarImageConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (OffStarImageConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: OffStarImageConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct OffStarImageConfiguration { + public let offStarImage: OffStarImage + + public typealias OffStarImage = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OnStarImage/OnStarImage.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OnStarImage/OnStarImage.generated.swift new file mode 100644 index 000000000..0aa3cc5b6 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OnStarImage/OnStarImage.generated.swift @@ -0,0 +1,66 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +import FioriThemeManager + +public struct OnStarImage { + /// The image to be used for "On" rating star. + let onStarImage: any View + + @Environment(\.onStarImageStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder onStarImage: () -> any View) { + self.onStarImage = onStarImage() + } +} + +public extension OnStarImage { + init(onStarImage: Image = FioriIcon.actions.favorite.renderingMode(.template).resizable()) { + self.init(onStarImage: { onStarImage }) + } +} + +public extension OnStarImage { + init(_ configuration: OnStarImageConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: OnStarImageConfiguration, shouldApplyDefaultStyle: Bool) { + self.onStarImage = configuration.onStarImage + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension OnStarImage: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(onStarImage: .init(self.onStarImage))).typeErased + .transformEnvironment(\.onStarImageStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension OnStarImage { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + OnStarImage(.init(onStarImage: .init(self.onStarImage))) + .shouldApplyDefaultStyle(false) + .onStarImageStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OnStarImage/OnStarImageStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OnStarImage/OnStarImageStyle.generated.swift new file mode 100644 index 000000000..6041559a0 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OnStarImage/OnStarImageStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol OnStarImageStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: OnStarImageConfiguration) -> Body +} + +struct AnyOnStarImageStyle: OnStarImageStyle { + let content: (OnStarImageConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (OnStarImageConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: OnStarImageConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct OnStarImageConfiguration { + public let onStarImage: OnStarImage + + public typealias OnStarImage = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControl/RatingControl.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControl/RatingControl.generated.swift index b89b25b01..17354755a 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControl/RatingControl.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControl/RatingControl.generated.swift @@ -3,54 +3,120 @@ import Foundation import SwiftUI +import FioriThemeManager + /// `RatingControl` uses images to represent a rating. /// /// The number of "On" images denotes the rating. /// The default "On" image is a filled star while the default "Off" inmage /// is an unfilled star. public struct RatingControl { + let valueLabel: any View + /// The image to be used for "On" rating star. + let onStarImage: any View + /// / The image to be used for "Off" rating star. + let offStarImage: any View + /// / The image to be used for "half" rating star. + let halfStarImage: any View + let reviewCountLabel: any View /// The rating value. @Binding var rating: Int /// The style of this `RatingControl`. let ratingControlStyle: RatingControl.Style /// The range of the rating values. The default is `0...5`. let ratingBounds: ClosedRange - /// The custom image to be used for "On". - let onImage: Image? - /// The custom image to be used for "Off". - let offImage: Image? /// The custom fixed size of each item image view. let itemSize: CGSize? - /// The custom color for the ON image. - let onColor: Color? - /// The custom color for the OFF image. - let offColor: Color? /// The custom spacing between images. let interItemSpacing: CGFloat? + /// The rating format for displaying rating value. + /// When this is `nil`, the default format is "%d of %d" where "of" is the localized "of". The first parameter is the rating value while the second parameter is the total number of stars. + let ratingValueFormat: String? + /// This property indicates if the value label is to be displayed or not. The default value is `false` for backward compatibility. + let showsValueLabel: Bool + /// The average rating for read-only style. + let averageRating: CGFloat? + /// The format for display the average rating. The default is "%.1f" + let averageRatingFormat: String + /// The number of reviews. + let reviewCount: Int? + /// The format for the review count string. The default is "%d reviews" where "reviews" is the localized "reviews" string. + let reviewCountFormat: String? + /// The ceiling number to be displayed for review count. If the `reviewCount` is larger than this number, this number will be displayed with a "+" sign after the number. + let reviewCountCeiling: Int? + /// The format for the review count string when the count is over the ceiling. The default is "%d+ reviews" where "reviews" is the localized "reviews" string. + let reviewCountCeilingFormat: String? + /// This property indicates if the review count label is to be displayed or not. The default value is `false` for backward compatibility. + let showsReviewCountLabel: Bool @Environment(\.ratingControlStyle) var style fileprivate var _shouldApplyDefaultStyle = true - public init(rating: Binding, + public init(@ViewBuilder valueLabel: () -> any View = { EmptyView() }, + @ViewBuilder onStarImage: () -> any View, + @ViewBuilder offStarImage: () -> any View, + @ViewBuilder halfStarImage: () -> any View, + @ViewBuilder reviewCountLabel: () -> any View = { EmptyView() }, + rating: Binding, ratingControlStyle: RatingControl.Style = .editable, ratingBounds: ClosedRange = 0 ... 5, - onImage: Image? = nil, - offImage: Image? = nil, itemSize: CGSize? = nil, - onColor: Color? = nil, - offColor: Color? = nil, - interItemSpacing: CGFloat? = nil) + interItemSpacing: CGFloat? = nil, + ratingValueFormat: String? = nil, + showsValueLabel: Bool = false, + averageRating: CGFloat? = nil, + averageRatingFormat: String = "%.1f", + reviewCount: Int? = nil, + reviewCountFormat: String? = nil, + reviewCountCeiling: Int? = nil, + reviewCountCeilingFormat: String? = nil, + showsReviewCountLabel: Bool = false) { + self.valueLabel = ValueLabel(valueLabel: valueLabel) + self.onStarImage = OnStarImage(onStarImage: onStarImage) + self.offStarImage = OffStarImage(offStarImage: offStarImage) + self.halfStarImage = HalfStarImage(halfStarImage: halfStarImage) + self.reviewCountLabel = ReviewCountLabel(reviewCountLabel: reviewCountLabel) self._rating = rating self.ratingControlStyle = ratingControlStyle self.ratingBounds = ratingBounds - self.onImage = onImage - self.offImage = offImage self.itemSize = itemSize - self.onColor = onColor - self.offColor = offColor self.interItemSpacing = interItemSpacing + self.ratingValueFormat = ratingValueFormat + self.showsValueLabel = showsValueLabel + self.averageRating = averageRating + self.averageRatingFormat = averageRatingFormat + self.reviewCount = reviewCount + self.reviewCountFormat = reviewCountFormat + self.reviewCountCeiling = reviewCountCeiling + self.reviewCountCeilingFormat = reviewCountCeilingFormat + self.showsReviewCountLabel = showsReviewCountLabel + } +} + +public extension RatingControl { + init(valueLabel: AttributedString? = nil, + onStarImage: Image = FioriIcon.actions.favorite.renderingMode(.template).resizable(), + offStarImage: Image = FioriIcon.actions.unfavorite.renderingMode(.template).resizable(), + halfStarImage: Image = FioriIcon.actions.halfStar.renderingMode(.template).resizable(), + reviewCountLabel: AttributedString? = nil, + rating: Binding, + ratingControlStyle: RatingControl.Style = .editable, + ratingBounds: ClosedRange = 0 ... 5, + itemSize: CGSize? = nil, + interItemSpacing: CGFloat? = nil, + ratingValueFormat: String? = nil, + showsValueLabel: Bool = false, + averageRating: CGFloat? = nil, + averageRatingFormat: String = "%.1f", + reviewCount: Int? = nil, + reviewCountFormat: String? = nil, + reviewCountCeiling: Int? = nil, + reviewCountCeilingFormat: String? = nil, + showsReviewCountLabel: Bool = false) + { + self.init(valueLabel: { OptionalText(valueLabel) }, onStarImage: { onStarImage }, offStarImage: { offStarImage }, halfStarImage: { halfStarImage }, reviewCountLabel: { OptionalText(reviewCountLabel) }, rating: rating, ratingControlStyle: ratingControlStyle, ratingBounds: ratingBounds, itemSize: itemSize, interItemSpacing: interItemSpacing, ratingValueFormat: ratingValueFormat, showsValueLabel: showsValueLabel, averageRating: averageRating, averageRatingFormat: averageRatingFormat, reviewCount: reviewCount, reviewCountFormat: reviewCountFormat, reviewCountCeiling: reviewCountCeiling, reviewCountCeilingFormat: reviewCountCeilingFormat, showsReviewCountLabel: showsReviewCountLabel) } } @@ -60,15 +126,25 @@ public extension RatingControl { } internal init(_ configuration: RatingControlConfiguration, shouldApplyDefaultStyle: Bool) { + self.valueLabel = configuration.valueLabel + self.onStarImage = configuration.onStarImage + self.offStarImage = configuration.offStarImage + self.halfStarImage = configuration.halfStarImage + self.reviewCountLabel = configuration.reviewCountLabel self._rating = configuration.$rating self.ratingControlStyle = configuration.ratingControlStyle self.ratingBounds = configuration.ratingBounds - self.onImage = configuration.onImage - self.offImage = configuration.offImage self.itemSize = configuration.itemSize - self.onColor = configuration.onColor - self.offColor = configuration.offColor self.interItemSpacing = configuration.interItemSpacing + self.ratingValueFormat = configuration.ratingValueFormat + self.showsValueLabel = configuration.showsValueLabel + self.averageRating = configuration.averageRating + self.averageRatingFormat = configuration.averageRatingFormat + self.reviewCount = configuration.reviewCount + self.reviewCountFormat = configuration.reviewCountFormat + self.reviewCountCeiling = configuration.reviewCountCeiling + self.reviewCountCeilingFormat = configuration.reviewCountCeilingFormat + self.showsReviewCountLabel = configuration.showsReviewCountLabel self._shouldApplyDefaultStyle = shouldApplyDefaultStyle } } @@ -78,7 +154,7 @@ extension RatingControl: View { if self._shouldApplyDefaultStyle { self.defaultStyle() } else { - self.style.resolve(configuration: .init(rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, onImage: self.onImage, offImage: self.offImage, itemSize: self.itemSize, onColor: self.onColor, offColor: self.offColor, interItemSpacing: self.interItemSpacing)).typeErased + self.style.resolve(configuration: .init(valueLabel: .init(self.valueLabel), onStarImage: .init(self.onStarImage), offStarImage: .init(self.offStarImage), halfStarImage: .init(self.halfStarImage), reviewCountLabel: .init(self.reviewCountLabel), rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, itemSize: self.itemSize, interItemSpacing: self.interItemSpacing, ratingValueFormat: self.ratingValueFormat, showsValueLabel: self.showsValueLabel, averageRating: self.averageRating, averageRatingFormat: self.averageRatingFormat, reviewCount: self.reviewCount, reviewCountFormat: self.reviewCountFormat, reviewCountCeiling: self.reviewCountCeiling, reviewCountCeilingFormat: self.reviewCountCeilingFormat, showsReviewCountLabel: self.showsReviewCountLabel)).typeErased .transformEnvironment(\.ratingControlStyleStack) { stack in if !stack.isEmpty { stack.removeLast() @@ -96,7 +172,7 @@ private extension RatingControl { } func defaultStyle() -> some View { - RatingControl(.init(rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, onImage: self.onImage, offImage: self.offImage, itemSize: self.itemSize, onColor: self.onColor, offColor: self.offColor, interItemSpacing: self.interItemSpacing)) + RatingControl(.init(valueLabel: .init(self.valueLabel), onStarImage: .init(self.onStarImage), offStarImage: .init(self.offStarImage), halfStarImage: .init(self.halfStarImage), reviewCountLabel: .init(self.reviewCountLabel), rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, itemSize: self.itemSize, interItemSpacing: self.interItemSpacing, ratingValueFormat: self.ratingValueFormat, showsValueLabel: self.showsValueLabel, averageRating: self.averageRating, averageRatingFormat: self.averageRatingFormat, reviewCount: self.reviewCount, reviewCountFormat: self.reviewCountFormat, reviewCountCeiling: self.reviewCountCeiling, reviewCountCeilingFormat: self.reviewCountCeilingFormat, showsReviewCountLabel: self.showsReviewCountLabel)) .shouldApplyDefaultStyle(false) .ratingControlStyle(RatingControlFioriStyle.ContentFioriStyle()) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControl/RatingControlStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControl/RatingControlStyle.generated.swift index 7dc60b9b5..fa9996a73 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControl/RatingControlStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControl/RatingControlStyle.generated.swift @@ -22,19 +22,40 @@ struct AnyRatingControlStyle: RatingControlStyle { } public struct RatingControlConfiguration { + public let valueLabel: ValueLabel + public let onStarImage: OnStarImage + public let offStarImage: OffStarImage + public let halfStarImage: HalfStarImage + public let reviewCountLabel: ReviewCountLabel @Binding public var rating: Int public let ratingControlStyle: RatingControl.Style public let ratingBounds: ClosedRange - public let onImage: Image? - public let offImage: Image? public let itemSize: CGSize? - public let onColor: Color? - public let offColor: Color? public let interItemSpacing: CGFloat? + public let ratingValueFormat: String? + public let showsValueLabel: Bool + public let averageRating: CGFloat? + public let averageRatingFormat: String + public let reviewCount: Int? + public let reviewCountFormat: String? + public let reviewCountCeiling: Int? + public let reviewCountCeilingFormat: String? + public let showsReviewCountLabel: Bool + + public typealias ValueLabel = ConfigurationViewWrapper + public typealias OnStarImage = ConfigurationViewWrapper + public typealias OffStarImage = ConfigurationViewWrapper + public typealias HalfStarImage = ConfigurationViewWrapper + public typealias ReviewCountLabel = ConfigurationViewWrapper } public struct RatingControlFioriStyle: RatingControlStyle { public func makeBody(_ configuration: RatingControlConfiguration) -> some View { RatingControl(configuration) + .valueLabelStyle(ValueLabelFioriStyle(ratingControlConfiguration: configuration)) + .onStarImageStyle(OnStarImageFioriStyle(ratingControlConfiguration: configuration)) + .offStarImageStyle(OffStarImageFioriStyle(ratingControlConfiguration: configuration)) + .halfStarImageStyle(HalfStarImageFioriStyle(ratingControlConfiguration: configuration)) + .reviewCountLabelStyle(ReviewCountLabelFioriStyle(ratingControlConfiguration: configuration)) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormView.generated.swift index 089409e86..adb15321d 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormView.generated.swift @@ -3,32 +3,49 @@ import Foundation import SwiftUI +import FioriThemeManager + /// The form view which contains a title, rating control, and a subtitle public struct RatingControlFormView { let title: any View + let valueLabel: any View + /// The image to be used for "On" rating star. + let onStarImage: any View + /// / The image to be used for "Off" rating star. + let offStarImage: any View + /// / The image to be used for "half" rating star. + let halfStarImage: any View + let reviewCountLabel: any View /// The rating value. @Binding var rating: Int /// The style of this `RatingControl`. let ratingControlStyle: RatingControl.Style /// The range of the rating values. The default is `0...5`. let ratingBounds: ClosedRange - /// The custom image to be used for "On". - let onImage: Image? - /// The custom image to be used for "Off". - let offImage: Image? /// The custom fixed size of each item image view. let itemSize: CGSize? - /// The custom color for the ON image. - let onColor: Color? - /// The custom color for the OFF image. - let offColor: Color? /// The custom spacing between images. let interItemSpacing: CGFloat? + /// The rating format for displaying rating value. + /// When this is `nil`, the default format is "%d of %d" where "of" is the localized "of". The first parameter is the rating value while the second parameter is the total number of stars. + let ratingValueFormat: String? + /// This property indicates if the value label is to be displayed or not. The default value is `false` for backward compatibility. + let showsValueLabel: Bool + /// The average rating for read-only style. + let averageRating: CGFloat? + /// The format for display the average rating. The default is "%.1f" + let averageRatingFormat: String + /// The number of reviews. + let reviewCount: Int? + /// The format for the review count string. The default is "%d reviews" where "reviews" is the localized "reviews" string. + let reviewCountFormat: String? + /// The ceiling number to be displayed for review count. If the `reviewCount` is larger than this number, this number will be displayed with a "+" sign after the number. + let reviewCountCeiling: Int? + /// The format for the review count string when the count is over the ceiling. The default is "%d+ reviews" where "reviews" is the localized "reviews" string. + let reviewCountCeilingFormat: String? + /// This property indicates if the review count label is to be displayed or not. The default value is `false` for backward compatibility. + let showsReviewCountLabel: Bool let subtitle: any View - /// The `ControlState` of the form view. The default is `normal` - let controlState: ControlState - /// The error message of the form view. - let errorMessage: AttributedString? /// Indicates if the axis for displaying the title and rating control. let axis: Axis @@ -37,54 +54,78 @@ public struct RatingControlFormView { fileprivate var _shouldApplyDefaultStyle = true public init(@ViewBuilder title: () -> any View, + @ViewBuilder valueLabel: () -> any View = { EmptyView() }, + @ViewBuilder onStarImage: () -> any View, + @ViewBuilder offStarImage: () -> any View, + @ViewBuilder halfStarImage: () -> any View, + @ViewBuilder reviewCountLabel: () -> any View = { EmptyView() }, rating: Binding, ratingControlStyle: RatingControl.Style = .editable, ratingBounds: ClosedRange = 0 ... 5, - onImage: Image? = nil, - offImage: Image? = nil, itemSize: CGSize? = nil, - onColor: Color? = nil, - offColor: Color? = nil, interItemSpacing: CGFloat? = nil, + ratingValueFormat: String? = nil, + showsValueLabel: Bool = false, + averageRating: CGFloat? = nil, + averageRatingFormat: String = "%.1f", + reviewCount: Int? = nil, + reviewCountFormat: String? = nil, + reviewCountCeiling: Int? = nil, + reviewCountCeilingFormat: String? = nil, + showsReviewCountLabel: Bool = false, @ViewBuilder subtitle: () -> any View = { EmptyView() }, - controlState: ControlState = .normal, - errorMessage: AttributedString? = nil, axis: Axis = .horizontal) { self.title = Title(title: title) + self.valueLabel = ValueLabel(valueLabel: valueLabel) + self.onStarImage = OnStarImage(onStarImage: onStarImage) + self.offStarImage = OffStarImage(offStarImage: offStarImage) + self.halfStarImage = HalfStarImage(halfStarImage: halfStarImage) + self.reviewCountLabel = ReviewCountLabel(reviewCountLabel: reviewCountLabel) self._rating = rating self.ratingControlStyle = ratingControlStyle self.ratingBounds = ratingBounds - self.onImage = onImage - self.offImage = offImage self.itemSize = itemSize - self.onColor = onColor - self.offColor = offColor self.interItemSpacing = interItemSpacing + self.ratingValueFormat = ratingValueFormat + self.showsValueLabel = showsValueLabel + self.averageRating = averageRating + self.averageRatingFormat = averageRatingFormat + self.reviewCount = reviewCount + self.reviewCountFormat = reviewCountFormat + self.reviewCountCeiling = reviewCountCeiling + self.reviewCountCeilingFormat = reviewCountCeilingFormat + self.showsReviewCountLabel = showsReviewCountLabel self.subtitle = Subtitle(subtitle: subtitle) - self.controlState = controlState - self.errorMessage = errorMessage self.axis = axis } } public extension RatingControlFormView { init(title: AttributedString, + valueLabel: AttributedString? = nil, + onStarImage: Image = FioriIcon.actions.favorite.renderingMode(.template).resizable(), + offStarImage: Image = FioriIcon.actions.unfavorite.renderingMode(.template).resizable(), + halfStarImage: Image = FioriIcon.actions.halfStar.renderingMode(.template).resizable(), + reviewCountLabel: AttributedString? = nil, rating: Binding, ratingControlStyle: RatingControl.Style = .editable, ratingBounds: ClosedRange = 0 ... 5, - onImage: Image? = nil, - offImage: Image? = nil, itemSize: CGSize? = nil, - onColor: Color? = nil, - offColor: Color? = nil, interItemSpacing: CGFloat? = nil, + ratingValueFormat: String? = nil, + showsValueLabel: Bool = false, + averageRating: CGFloat? = nil, + averageRatingFormat: String = "%.1f", + reviewCount: Int? = nil, + reviewCountFormat: String? = nil, + reviewCountCeiling: Int? = nil, + reviewCountCeilingFormat: String? = nil, + showsReviewCountLabel: Bool = false, subtitle: AttributedString? = nil, - controlState: ControlState = .normal, - errorMessage: AttributedString? = nil, axis: Axis = .horizontal) { - self.init(title: { Text(title) }, rating: rating, ratingControlStyle: ratingControlStyle, ratingBounds: ratingBounds, onImage: onImage, offImage: offImage, itemSize: itemSize, onColor: onColor, offColor: offColor, interItemSpacing: interItemSpacing, subtitle: { OptionalText(subtitle) }, controlState: controlState, errorMessage: errorMessage, axis: axis) + self.init(title: { Text(title) }, valueLabel: { OptionalText(valueLabel) }, onStarImage: { onStarImage }, offStarImage: { offStarImage }, halfStarImage: { halfStarImage }, reviewCountLabel: { OptionalText(reviewCountLabel) }, rating: rating, ratingControlStyle: ratingControlStyle, ratingBounds: ratingBounds, itemSize: itemSize, interItemSpacing: interItemSpacing, ratingValueFormat: ratingValueFormat, showsValueLabel: showsValueLabel, averageRating: averageRating, averageRatingFormat: averageRatingFormat, reviewCount: reviewCount, reviewCountFormat: reviewCountFormat, reviewCountCeiling: reviewCountCeiling, reviewCountCeilingFormat: reviewCountCeilingFormat, showsReviewCountLabel: showsReviewCountLabel, subtitle: { OptionalText(subtitle) }, axis: axis) } } @@ -95,18 +136,26 @@ public extension RatingControlFormView { internal init(_ configuration: RatingControlFormViewConfiguration, shouldApplyDefaultStyle: Bool) { self.title = configuration.title + self.valueLabel = configuration.valueLabel + self.onStarImage = configuration.onStarImage + self.offStarImage = configuration.offStarImage + self.halfStarImage = configuration.halfStarImage + self.reviewCountLabel = configuration.reviewCountLabel self._rating = configuration.$rating self.ratingControlStyle = configuration.ratingControlStyle self.ratingBounds = configuration.ratingBounds - self.onImage = configuration.onImage - self.offImage = configuration.offImage self.itemSize = configuration.itemSize - self.onColor = configuration.onColor - self.offColor = configuration.offColor self.interItemSpacing = configuration.interItemSpacing + self.ratingValueFormat = configuration.ratingValueFormat + self.showsValueLabel = configuration.showsValueLabel + self.averageRating = configuration.averageRating + self.averageRatingFormat = configuration.averageRatingFormat + self.reviewCount = configuration.reviewCount + self.reviewCountFormat = configuration.reviewCountFormat + self.reviewCountCeiling = configuration.reviewCountCeiling + self.reviewCountCeilingFormat = configuration.reviewCountCeilingFormat + self.showsReviewCountLabel = configuration.showsReviewCountLabel self.subtitle = configuration.subtitle - self.controlState = configuration.controlState - self.errorMessage = configuration.errorMessage self.axis = configuration.axis self._shouldApplyDefaultStyle = shouldApplyDefaultStyle } @@ -117,7 +166,7 @@ extension RatingControlFormView: View { if self._shouldApplyDefaultStyle { self.defaultStyle() } else { - self.style.resolve(configuration: .init(title: .init(self.title), rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, onImage: self.onImage, offImage: self.offImage, itemSize: self.itemSize, onColor: self.onColor, offColor: self.offColor, interItemSpacing: self.interItemSpacing, subtitle: .init(self.subtitle), controlState: self.controlState, errorMessage: self.errorMessage, axis: self.axis)).typeErased + self.style.resolve(configuration: .init(title: .init(self.title), valueLabel: .init(self.valueLabel), onStarImage: .init(self.onStarImage), offStarImage: .init(self.offStarImage), halfStarImage: .init(self.halfStarImage), reviewCountLabel: .init(self.reviewCountLabel), rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, itemSize: self.itemSize, interItemSpacing: self.interItemSpacing, ratingValueFormat: self.ratingValueFormat, showsValueLabel: self.showsValueLabel, averageRating: self.averageRating, averageRatingFormat: self.averageRatingFormat, reviewCount: self.reviewCount, reviewCountFormat: self.reviewCountFormat, reviewCountCeiling: self.reviewCountCeiling, reviewCountCeilingFormat: self.reviewCountCeilingFormat, showsReviewCountLabel: self.showsReviewCountLabel, subtitle: .init(self.subtitle), axis: self.axis)).typeErased .transformEnvironment(\.ratingControlFormViewStyleStack) { stack in if !stack.isEmpty { stack.removeLast() @@ -135,7 +184,7 @@ private extension RatingControlFormView { } func defaultStyle() -> some View { - RatingControlFormView(.init(title: .init(self.title), rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, onImage: self.onImage, offImage: self.offImage, itemSize: self.itemSize, onColor: self.onColor, offColor: self.offColor, interItemSpacing: self.interItemSpacing, subtitle: .init(self.subtitle), controlState: self.controlState, errorMessage: self.errorMessage, axis: self.axis)) + RatingControlFormView(.init(title: .init(self.title), valueLabel: .init(self.valueLabel), onStarImage: .init(self.onStarImage), offStarImage: .init(self.offStarImage), halfStarImage: .init(self.halfStarImage), reviewCountLabel: .init(self.reviewCountLabel), rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, itemSize: self.itemSize, interItemSpacing: self.interItemSpacing, ratingValueFormat: self.ratingValueFormat, showsValueLabel: self.showsValueLabel, averageRating: self.averageRating, averageRatingFormat: self.averageRatingFormat, reviewCount: self.reviewCount, reviewCountFormat: self.reviewCountFormat, reviewCountCeiling: self.reviewCountCeiling, reviewCountCeilingFormat: self.reviewCountCeilingFormat, showsReviewCountLabel: self.showsReviewCountLabel, subtitle: .init(self.subtitle), axis: self.axis)) .shouldApplyDefaultStyle(false) .ratingControlFormViewStyle(RatingControlFormViewFioriStyle.ContentFioriStyle()) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormViewStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormViewStyle.generated.swift index ff1ec13b4..ce10ad649 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormViewStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/RatingControlFormView/RatingControlFormViewStyle.generated.swift @@ -23,21 +23,34 @@ struct AnyRatingControlFormViewStyle: RatingControlFormViewStyle { public struct RatingControlFormViewConfiguration { public let title: Title + public let valueLabel: ValueLabel + public let onStarImage: OnStarImage + public let offStarImage: OffStarImage + public let halfStarImage: HalfStarImage + public let reviewCountLabel: ReviewCountLabel @Binding public var rating: Int public let ratingControlStyle: RatingControl.Style public let ratingBounds: ClosedRange - public let onImage: Image? - public let offImage: Image? public let itemSize: CGSize? - public let onColor: Color? - public let offColor: Color? public let interItemSpacing: CGFloat? + public let ratingValueFormat: String? + public let showsValueLabel: Bool + public let averageRating: CGFloat? + public let averageRatingFormat: String + public let reviewCount: Int? + public let reviewCountFormat: String? + public let reviewCountCeiling: Int? + public let reviewCountCeilingFormat: String? + public let showsReviewCountLabel: Bool public let subtitle: Subtitle - public let controlState: ControlState - public let errorMessage: AttributedString? public let axis: Axis public typealias Title = ConfigurationViewWrapper + public typealias ValueLabel = ConfigurationViewWrapper + public typealias OnStarImage = ConfigurationViewWrapper + public typealias OffStarImage = ConfigurationViewWrapper + public typealias HalfStarImage = ConfigurationViewWrapper + public typealias ReviewCountLabel = ConfigurationViewWrapper public typealias Subtitle = ConfigurationViewWrapper } @@ -45,8 +58,12 @@ public struct RatingControlFormViewFioriStyle: RatingControlFormViewStyle { public func makeBody(_ configuration: RatingControlFormViewConfiguration) -> some View { RatingControlFormView(configuration) .titleStyle(TitleFioriStyle(ratingControlFormViewConfiguration: configuration)) + .valueLabelStyle(ValueLabelFioriStyle(ratingControlFormViewConfiguration: configuration)) + .onStarImageStyle(OnStarImageFioriStyle(ratingControlFormViewConfiguration: configuration)) + .offStarImageStyle(OffStarImageFioriStyle(ratingControlFormViewConfiguration: configuration)) + .halfStarImageStyle(HalfStarImageFioriStyle(ratingControlFormViewConfiguration: configuration)) + .reviewCountLabelStyle(ReviewCountLabelFioriStyle(ratingControlFormViewConfiguration: configuration)) .subtitleStyle(SubtitleFioriStyle(ratingControlFormViewConfiguration: configuration)) .ratingControlStyle(RatingControlFioriStyle(ratingControlFormViewConfiguration: configuration)) - .formViewStyle(FormViewFioriStyle(ratingControlFormViewConfiguration: configuration)) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ReviewCountLabel/ReviewCountLabel.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ReviewCountLabel/ReviewCountLabel.generated.swift new file mode 100644 index 000000000..c5a070c33 --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ReviewCountLabel/ReviewCountLabel.generated.swift @@ -0,0 +1,63 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public struct ReviewCountLabel { + let reviewCountLabel: any View + + @Environment(\.reviewCountLabelStyle) var style + + fileprivate var _shouldApplyDefaultStyle = true + + public init(@ViewBuilder reviewCountLabel: () -> any View = { EmptyView() }) { + self.reviewCountLabel = reviewCountLabel() + } +} + +public extension ReviewCountLabel { + init(reviewCountLabel: AttributedString? = nil) { + self.init(reviewCountLabel: { OptionalText(reviewCountLabel) }) + } +} + +public extension ReviewCountLabel { + init(_ configuration: ReviewCountLabelConfiguration) { + self.init(configuration, shouldApplyDefaultStyle: false) + } + + internal init(_ configuration: ReviewCountLabelConfiguration, shouldApplyDefaultStyle: Bool) { + self.reviewCountLabel = configuration.reviewCountLabel + self._shouldApplyDefaultStyle = shouldApplyDefaultStyle + } +} + +extension ReviewCountLabel: View { + public var body: some View { + if self._shouldApplyDefaultStyle { + self.defaultStyle() + } else { + self.style.resolve(configuration: .init(reviewCountLabel: .init(self.reviewCountLabel))).typeErased + .transformEnvironment(\.reviewCountLabelStyleStack) { stack in + if !stack.isEmpty { + stack.removeLast() + } + } + } + } +} + +private extension ReviewCountLabel { + func shouldApplyDefaultStyle(_ bool: Bool) -> some View { + var s = self + s._shouldApplyDefaultStyle = bool + return s + } + + func defaultStyle() -> some View { + ReviewCountLabel(.init(reviewCountLabel: .init(self.reviewCountLabel))) + .shouldApplyDefaultStyle(false) + .reviewCountLabelStyle(.fiori) + .typeErased + } +} diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ReviewCountLabel/ReviewCountLabelStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ReviewCountLabel/ReviewCountLabelStyle.generated.swift new file mode 100644 index 000000000..c1d762c8c --- /dev/null +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ReviewCountLabel/ReviewCountLabelStyle.generated.swift @@ -0,0 +1,28 @@ +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT +import Foundation +import SwiftUI + +public protocol ReviewCountLabelStyle: DynamicProperty { + associatedtype Body: View + + func makeBody(_ configuration: ReviewCountLabelConfiguration) -> Body +} + +struct AnyReviewCountLabelStyle: ReviewCountLabelStyle { + let content: (ReviewCountLabelConfiguration) -> any View + + init(@ViewBuilder _ content: @escaping (ReviewCountLabelConfiguration) -> any View) { + self.content = content + } + + public func makeBody(_ configuration: ReviewCountLabelConfiguration) -> some View { + self.content(configuration).typeErased + } +} + +public struct ReviewCountLabelConfiguration { + public let reviewCountLabel: ReviewCountLabel + + public typealias ReviewCountLabel = ConfigurationViewWrapper +} diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift index 0390dfb2b..2cb487bd7 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ComponentStyleProtocol+Extension.generated.swift @@ -1767,6 +1767,20 @@ public extension GreetingTextStyle where Self == GreetingTextFioriStyle { } } +// MARK: HalfStarImageStyle + +public extension HalfStarImageStyle where Self == HalfStarImageBaseStyle { + static var base: HalfStarImageBaseStyle { + HalfStarImageBaseStyle() + } +} + +public extension HalfStarImageStyle where Self == HalfStarImageFioriStyle { + static var fiori: HalfStarImageFioriStyle { + HalfStarImageFioriStyle() + } +} + // MARK: HeaderActionStyle public extension HeaderActionStyle where Self == HeaderActionBaseStyle { @@ -2971,6 +2985,34 @@ public extension ObjectItemStyle where Self == ObjectItemActionStyle { } } +// MARK: OffStarImageStyle + +public extension OffStarImageStyle where Self == OffStarImageBaseStyle { + static var base: OffStarImageBaseStyle { + OffStarImageBaseStyle() + } +} + +public extension OffStarImageStyle where Self == OffStarImageFioriStyle { + static var fiori: OffStarImageFioriStyle { + OffStarImageFioriStyle() + } +} + +// MARK: OnStarImageStyle + +public extension OnStarImageStyle where Self == OnStarImageBaseStyle { + static var base: OnStarImageBaseStyle { + OnStarImageBaseStyle() + } +} + +public extension OnStarImageStyle where Self == OnStarImageFioriStyle { + static var fiori: OnStarImageFioriStyle { + OnStarImageFioriStyle() + } +} + // MARK: OptionalTitleStyle public extension OptionalTitleStyle where Self == OptionalTitleBaseStyle { @@ -3251,6 +3293,111 @@ public extension RatingControlStyle where Self == RatingControlFioriStyle { } } +public struct RatingControlValueLabelStyle: RatingControlStyle { + let style: any ValueLabelStyle + + public func makeBody(_ configuration: RatingControlConfiguration) -> some View { + RatingControl(configuration) + .valueLabelStyle(self.style) + .typeErased + } +} + +public extension RatingControlStyle where Self == RatingControlValueLabelStyle { + static func valueLabelStyle(_ style: some ValueLabelStyle) -> RatingControlValueLabelStyle { + RatingControlValueLabelStyle(style: style) + } + + static func valueLabelStyle(@ViewBuilder content: @escaping (ValueLabelConfiguration) -> some View) -> RatingControlValueLabelStyle { + let style = AnyValueLabelStyle(content) + return RatingControlValueLabelStyle(style: style) + } +} + +public struct RatingControlOnStarImageStyle: RatingControlStyle { + let style: any OnStarImageStyle + + public func makeBody(_ configuration: RatingControlConfiguration) -> some View { + RatingControl(configuration) + .onStarImageStyle(self.style) + .typeErased + } +} + +public extension RatingControlStyle where Self == RatingControlOnStarImageStyle { + static func onStarImageStyle(_ style: some OnStarImageStyle) -> RatingControlOnStarImageStyle { + RatingControlOnStarImageStyle(style: style) + } + + static func onStarImageStyle(@ViewBuilder content: @escaping (OnStarImageConfiguration) -> some View) -> RatingControlOnStarImageStyle { + let style = AnyOnStarImageStyle(content) + return RatingControlOnStarImageStyle(style: style) + } +} + +public struct RatingControlOffStarImageStyle: RatingControlStyle { + let style: any OffStarImageStyle + + public func makeBody(_ configuration: RatingControlConfiguration) -> some View { + RatingControl(configuration) + .offStarImageStyle(self.style) + .typeErased + } +} + +public extension RatingControlStyle where Self == RatingControlOffStarImageStyle { + static func offStarImageStyle(_ style: some OffStarImageStyle) -> RatingControlOffStarImageStyle { + RatingControlOffStarImageStyle(style: style) + } + + static func offStarImageStyle(@ViewBuilder content: @escaping (OffStarImageConfiguration) -> some View) -> RatingControlOffStarImageStyle { + let style = AnyOffStarImageStyle(content) + return RatingControlOffStarImageStyle(style: style) + } +} + +public struct RatingControlHalfStarImageStyle: RatingControlStyle { + let style: any HalfStarImageStyle + + public func makeBody(_ configuration: RatingControlConfiguration) -> some View { + RatingControl(configuration) + .halfStarImageStyle(self.style) + .typeErased + } +} + +public extension RatingControlStyle where Self == RatingControlHalfStarImageStyle { + static func halfStarImageStyle(_ style: some HalfStarImageStyle) -> RatingControlHalfStarImageStyle { + RatingControlHalfStarImageStyle(style: style) + } + + static func halfStarImageStyle(@ViewBuilder content: @escaping (HalfStarImageConfiguration) -> some View) -> RatingControlHalfStarImageStyle { + let style = AnyHalfStarImageStyle(content) + return RatingControlHalfStarImageStyle(style: style) + } +} + +public struct RatingControlReviewCountLabelStyle: RatingControlStyle { + let style: any ReviewCountLabelStyle + + public func makeBody(_ configuration: RatingControlConfiguration) -> some View { + RatingControl(configuration) + .reviewCountLabelStyle(self.style) + .typeErased + } +} + +public extension RatingControlStyle where Self == RatingControlReviewCountLabelStyle { + static func reviewCountLabelStyle(_ style: some ReviewCountLabelStyle) -> RatingControlReviewCountLabelStyle { + RatingControlReviewCountLabelStyle(style: style) + } + + static func reviewCountLabelStyle(@ViewBuilder content: @escaping (ReviewCountLabelConfiguration) -> some View) -> RatingControlReviewCountLabelStyle { + let style = AnyReviewCountLabelStyle(content) + return RatingControlReviewCountLabelStyle(style: style) + } +} + // MARK: RatingControlFormViewStyle public extension RatingControlFormViewStyle where Self == RatingControlFormViewBaseStyle { @@ -3286,6 +3433,111 @@ public extension RatingControlFormViewStyle where Self == RatingControlFormViewT } } +public struct RatingControlFormViewValueLabelStyle: RatingControlFormViewStyle { + let style: any ValueLabelStyle + + public func makeBody(_ configuration: RatingControlFormViewConfiguration) -> some View { + RatingControlFormView(configuration) + .valueLabelStyle(self.style) + .typeErased + } +} + +public extension RatingControlFormViewStyle where Self == RatingControlFormViewValueLabelStyle { + static func valueLabelStyle(_ style: some ValueLabelStyle) -> RatingControlFormViewValueLabelStyle { + RatingControlFormViewValueLabelStyle(style: style) + } + + static func valueLabelStyle(@ViewBuilder content: @escaping (ValueLabelConfiguration) -> some View) -> RatingControlFormViewValueLabelStyle { + let style = AnyValueLabelStyle(content) + return RatingControlFormViewValueLabelStyle(style: style) + } +} + +public struct RatingControlFormViewOnStarImageStyle: RatingControlFormViewStyle { + let style: any OnStarImageStyle + + public func makeBody(_ configuration: RatingControlFormViewConfiguration) -> some View { + RatingControlFormView(configuration) + .onStarImageStyle(self.style) + .typeErased + } +} + +public extension RatingControlFormViewStyle where Self == RatingControlFormViewOnStarImageStyle { + static func onStarImageStyle(_ style: some OnStarImageStyle) -> RatingControlFormViewOnStarImageStyle { + RatingControlFormViewOnStarImageStyle(style: style) + } + + static func onStarImageStyle(@ViewBuilder content: @escaping (OnStarImageConfiguration) -> some View) -> RatingControlFormViewOnStarImageStyle { + let style = AnyOnStarImageStyle(content) + return RatingControlFormViewOnStarImageStyle(style: style) + } +} + +public struct RatingControlFormViewOffStarImageStyle: RatingControlFormViewStyle { + let style: any OffStarImageStyle + + public func makeBody(_ configuration: RatingControlFormViewConfiguration) -> some View { + RatingControlFormView(configuration) + .offStarImageStyle(self.style) + .typeErased + } +} + +public extension RatingControlFormViewStyle where Self == RatingControlFormViewOffStarImageStyle { + static func offStarImageStyle(_ style: some OffStarImageStyle) -> RatingControlFormViewOffStarImageStyle { + RatingControlFormViewOffStarImageStyle(style: style) + } + + static func offStarImageStyle(@ViewBuilder content: @escaping (OffStarImageConfiguration) -> some View) -> RatingControlFormViewOffStarImageStyle { + let style = AnyOffStarImageStyle(content) + return RatingControlFormViewOffStarImageStyle(style: style) + } +} + +public struct RatingControlFormViewHalfStarImageStyle: RatingControlFormViewStyle { + let style: any HalfStarImageStyle + + public func makeBody(_ configuration: RatingControlFormViewConfiguration) -> some View { + RatingControlFormView(configuration) + .halfStarImageStyle(self.style) + .typeErased + } +} + +public extension RatingControlFormViewStyle where Self == RatingControlFormViewHalfStarImageStyle { + static func halfStarImageStyle(_ style: some HalfStarImageStyle) -> RatingControlFormViewHalfStarImageStyle { + RatingControlFormViewHalfStarImageStyle(style: style) + } + + static func halfStarImageStyle(@ViewBuilder content: @escaping (HalfStarImageConfiguration) -> some View) -> RatingControlFormViewHalfStarImageStyle { + let style = AnyHalfStarImageStyle(content) + return RatingControlFormViewHalfStarImageStyle(style: style) + } +} + +public struct RatingControlFormViewReviewCountLabelStyle: RatingControlFormViewStyle { + let style: any ReviewCountLabelStyle + + public func makeBody(_ configuration: RatingControlFormViewConfiguration) -> some View { + RatingControlFormView(configuration) + .reviewCountLabelStyle(self.style) + .typeErased + } +} + +public extension RatingControlFormViewStyle where Self == RatingControlFormViewReviewCountLabelStyle { + static func reviewCountLabelStyle(_ style: some ReviewCountLabelStyle) -> RatingControlFormViewReviewCountLabelStyle { + RatingControlFormViewReviewCountLabelStyle(style: style) + } + + static func reviewCountLabelStyle(@ViewBuilder content: @escaping (ReviewCountLabelConfiguration) -> some View) -> RatingControlFormViewReviewCountLabelStyle { + let style = AnyReviewCountLabelStyle(content) + return RatingControlFormViewReviewCountLabelStyle(style: style) + } +} + public struct RatingControlFormViewSubtitleStyle: RatingControlFormViewStyle { let style: any SubtitleStyle @@ -3328,24 +3580,17 @@ public extension RatingControlFormViewStyle where Self == RatingControlFormViewR } } -public struct RatingControlFormViewFormViewStyle: RatingControlFormViewStyle { - let style: any FormViewStyle +// MARK: ReviewCountLabelStyle - public func makeBody(_ configuration: RatingControlFormViewConfiguration) -> some View { - RatingControlFormView(configuration) - .formViewStyle(self.style) - .typeErased +public extension ReviewCountLabelStyle where Self == ReviewCountLabelBaseStyle { + static var base: ReviewCountLabelBaseStyle { + ReviewCountLabelBaseStyle() } } -public extension RatingControlFormViewStyle where Self == RatingControlFormViewFormViewStyle { - static func formViewStyle(_ style: some FormViewStyle) -> RatingControlFormViewFormViewStyle { - RatingControlFormViewFormViewStyle(style: style) - } - - static func formViewStyle(@ViewBuilder content: @escaping (FormViewConfiguration) -> some View) -> RatingControlFormViewFormViewStyle { - let style = AnyFormViewStyle(content) - return RatingControlFormViewFormViewStyle(style: style) +public extension ReviewCountLabelStyle where Self == ReviewCountLabelFioriStyle { + static var fiori: ReviewCountLabelFioriStyle { + ReviewCountLabelFioriStyle() } } diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift index 25c8eca93..7bb8863fc 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/EnvironmentVariables.generated.swift @@ -570,6 +570,27 @@ extension EnvironmentValues { } } +// MARK: HalfStarImageStyle + +struct HalfStarImageStyleStackKey: EnvironmentKey { + static let defaultValue: [any HalfStarImageStyle] = [] +} + +extension EnvironmentValues { + var halfStarImageStyle: any HalfStarImageStyle { + self.halfStarImageStyleStack.last ?? .base + } + + var halfStarImageStyleStack: [any HalfStarImageStyle] { + get { + self[HalfStarImageStyleStackKey.self] + } + set { + self[HalfStarImageStyleStackKey.self] = newValue + } + } +} + // MARK: HeaderActionStyle struct HeaderActionStyleStackKey: EnvironmentKey { @@ -1053,6 +1074,48 @@ extension EnvironmentValues { } } +// MARK: OffStarImageStyle + +struct OffStarImageStyleStackKey: EnvironmentKey { + static let defaultValue: [any OffStarImageStyle] = [] +} + +extension EnvironmentValues { + var offStarImageStyle: any OffStarImageStyle { + self.offStarImageStyleStack.last ?? .base + } + + var offStarImageStyleStack: [any OffStarImageStyle] { + get { + self[OffStarImageStyleStackKey.self] + } + set { + self[OffStarImageStyleStackKey.self] = newValue + } + } +} + +// MARK: OnStarImageStyle + +struct OnStarImageStyleStackKey: EnvironmentKey { + static let defaultValue: [any OnStarImageStyle] = [] +} + +extension EnvironmentValues { + var onStarImageStyle: any OnStarImageStyle { + self.onStarImageStyleStack.last ?? .base + } + + var onStarImageStyleStack: [any OnStarImageStyle] { + get { + self[OnStarImageStyleStackKey.self] + } + set { + self[OnStarImageStyleStackKey.self] = newValue + } + } +} + // MARK: OptionalTitleStyle struct OptionalTitleStyleStackKey: EnvironmentKey { @@ -1242,6 +1305,27 @@ extension EnvironmentValues { } } +// MARK: ReviewCountLabelStyle + +struct ReviewCountLabelStyleStackKey: EnvironmentKey { + static let defaultValue: [any ReviewCountLabelStyle] = [] +} + +extension EnvironmentValues { + var reviewCountLabelStyle: any ReviewCountLabelStyle { + self.reviewCountLabelStyleStack.last ?? .base + } + + var reviewCountLabelStyleStack: [any ReviewCountLabelStyle] { + get { + self[ReviewCountLabelStyleStackKey.self] + } + set { + self[ReviewCountLabelStyleStackKey.self] = newValue + } + } +} + // MARK: Row1Style struct Row1StyleStackKey: EnvironmentKey { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift index fe1c3bd6e..5edc2b190 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ModifiedStyle.generated.swift @@ -764,6 +764,34 @@ public extension GreetingTextStyle { } } +// MARK: HalfStarImageStyle + +extension ModifiedStyle: HalfStarImageStyle where Style: HalfStarImageStyle { + public func makeBody(_ configuration: HalfStarImageConfiguration) -> some View { + HalfStarImage(configuration) + .halfStarImageStyle(self.style) + .modifier(self.modifier) + } +} + +public struct HalfStarImageStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.halfStarImageStyle(self.style) + } +} + +public extension HalfStarImageStyle { + func modifier(_ modifier: some ViewModifier) -> some HalfStarImageStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some HalfStarImageStyle) -> some HalfStarImageStyle { + style.modifier(HalfStarImageStyleModifier(style: self)) + } +} + // MARK: HeaderActionStyle extension ModifiedStyle: HeaderActionStyle where Style: HeaderActionStyle { @@ -1408,6 +1436,62 @@ public extension ObjectItemStyle { } } +// MARK: OffStarImageStyle + +extension ModifiedStyle: OffStarImageStyle where Style: OffStarImageStyle { + public func makeBody(_ configuration: OffStarImageConfiguration) -> some View { + OffStarImage(configuration) + .offStarImageStyle(self.style) + .modifier(self.modifier) + } +} + +public struct OffStarImageStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.offStarImageStyle(self.style) + } +} + +public extension OffStarImageStyle { + func modifier(_ modifier: some ViewModifier) -> some OffStarImageStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some OffStarImageStyle) -> some OffStarImageStyle { + style.modifier(OffStarImageStyleModifier(style: self)) + } +} + +// MARK: OnStarImageStyle + +extension ModifiedStyle: OnStarImageStyle where Style: OnStarImageStyle { + public func makeBody(_ configuration: OnStarImageConfiguration) -> some View { + OnStarImage(configuration) + .onStarImageStyle(self.style) + .modifier(self.modifier) + } +} + +public struct OnStarImageStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.onStarImageStyle(self.style) + } +} + +public extension OnStarImageStyle { + func modifier(_ modifier: some ViewModifier) -> some OnStarImageStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some OnStarImageStyle) -> some OnStarImageStyle { + style.modifier(OnStarImageStyleModifier(style: self)) + } +} + // MARK: OptionalTitleStyle extension ModifiedStyle: OptionalTitleStyle where Style: OptionalTitleStyle { @@ -1660,6 +1744,34 @@ public extension RatingControlFormViewStyle { } } +// MARK: ReviewCountLabelStyle + +extension ModifiedStyle: ReviewCountLabelStyle where Style: ReviewCountLabelStyle { + public func makeBody(_ configuration: ReviewCountLabelConfiguration) -> some View { + ReviewCountLabel(configuration) + .reviewCountLabelStyle(self.style) + .modifier(self.modifier) + } +} + +public struct ReviewCountLabelStyleModifier: ViewModifier { + let style: Style + + public func body(content: Content) -> some View { + content.reviewCountLabelStyle(self.style) + } +} + +public extension ReviewCountLabelStyle { + func modifier(_ modifier: some ViewModifier) -> some ReviewCountLabelStyle { + ModifiedStyle(style: self, modifier: modifier) + } + + func concat(_ style: some ReviewCountLabelStyle) -> some ReviewCountLabelStyle { + style.modifier(ReviewCountLabelStyleModifier(style: self)) + } +} + // MARK: Row1Style extension ModifiedStyle: Row1Style where Style: Row1Style { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift index 79cc672e9..1ef7477ee 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ResolvedStyle.generated.swift @@ -435,6 +435,22 @@ extension GreetingTextStyle { } } +// MARK: HalfStarImageStyle + +struct ResolvedHalfStarImageStyle: View { + let style: Style + let configuration: HalfStarImageConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension HalfStarImageStyle { + func resolve(configuration: HalfStarImageConfiguration) -> some View { + ResolvedHalfStarImageStyle(style: self, configuration: configuration) + } +} + // MARK: HeaderActionStyle struct ResolvedHeaderActionStyle: View { @@ -803,6 +819,38 @@ extension ObjectItemStyle { } } +// MARK: OffStarImageStyle + +struct ResolvedOffStarImageStyle: View { + let style: Style + let configuration: OffStarImageConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension OffStarImageStyle { + func resolve(configuration: OffStarImageConfiguration) -> some View { + ResolvedOffStarImageStyle(style: self, configuration: configuration) + } +} + +// MARK: OnStarImageStyle + +struct ResolvedOnStarImageStyle: View { + let style: Style + let configuration: OnStarImageConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension OnStarImageStyle { + func resolve(configuration: OnStarImageConfiguration) -> some View { + ResolvedOnStarImageStyle(style: self, configuration: configuration) + } +} + // MARK: OptionalTitleStyle struct ResolvedOptionalTitleStyle: View { @@ -947,6 +995,22 @@ extension RatingControlFormViewStyle { } } +// MARK: ReviewCountLabelStyle + +struct ResolvedReviewCountLabelStyle: View { + let style: Style + let configuration: ReviewCountLabelConfiguration + var body: some View { + self.style.makeBody(self.configuration) + } +} + +extension ReviewCountLabelStyle { + func resolve(configuration: ReviewCountLabelConfiguration) -> some View { + ResolvedReviewCountLabelStyle(style: self, configuration: configuration) + } +} + // MARK: Row1Style struct ResolvedRow1Style: View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift index 84a5420fe..f9f30c855 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift @@ -89,11 +89,7 @@ extension PlaceholderTextFieldConfiguration { extension RatingControlFormViewConfiguration { var _ratingControl: RatingControl { - RatingControl(.init(rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, onImage: self.onImage, offImage: self.offImage, itemSize: self.itemSize, onColor: self.onColor, offColor: self.offColor, interItemSpacing: self.interItemSpacing), shouldApplyDefaultStyle: true) - } - - var _formView: FormView { - FormView(.init(controlState: self.controlState, errorMessage: self.errorMessage), shouldApplyDefaultStyle: true) + RatingControl(.init(valueLabel: .init(self.valueLabel), onStarImage: .init(self.onStarImage), offStarImage: .init(self.offStarImage), halfStarImage: .init(self.halfStarImage), reviewCountLabel: .init(self.reviewCountLabel), rating: self.$rating, ratingControlStyle: self.ratingControlStyle, ratingBounds: self.ratingBounds, itemSize: self.itemSize, interItemSpacing: self.interItemSpacing, ratingValueFormat: self.ratingValueFormat, showsValueLabel: self.showsValueLabel, averageRating: self.averageRating, averageRatingFormat: self.averageRatingFormat, reviewCount: self.reviewCount, reviewCountFormat: self.reviewCountFormat, reviewCountCeiling: self.reviewCountCeiling, reviewCountCeilingFormat: self.reviewCountCeilingFormat, showsReviewCountLabel: self.showsReviewCountLabel), shouldApplyDefaultStyle: true) } } diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift index d748c32af..113ecfd45 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/View+Extension_.generated.swift @@ -462,6 +462,23 @@ public extension View { } } +// MARK: HalfStarImageStyle + +public extension View { + func halfStarImageStyle(_ style: some HalfStarImageStyle) -> some View { + self.transformEnvironment(\.halfStarImageStyleStack) { stack in + stack.append(style) + } + } + + func halfStarImageStyle(@ViewBuilder content: @escaping (HalfStarImageConfiguration) -> some View) -> some View { + self.transformEnvironment(\.halfStarImageStyleStack) { stack in + let style = AnyHalfStarImageStyle(content) + stack.append(style) + } + } +} + // MARK: HeaderActionStyle public extension View { @@ -853,6 +870,40 @@ public extension View { } } +// MARK: OffStarImageStyle + +public extension View { + func offStarImageStyle(_ style: some OffStarImageStyle) -> some View { + self.transformEnvironment(\.offStarImageStyleStack) { stack in + stack.append(style) + } + } + + func offStarImageStyle(@ViewBuilder content: @escaping (OffStarImageConfiguration) -> some View) -> some View { + self.transformEnvironment(\.offStarImageStyleStack) { stack in + let style = AnyOffStarImageStyle(content) + stack.append(style) + } + } +} + +// MARK: OnStarImageStyle + +public extension View { + func onStarImageStyle(_ style: some OnStarImageStyle) -> some View { + self.transformEnvironment(\.onStarImageStyleStack) { stack in + stack.append(style) + } + } + + func onStarImageStyle(@ViewBuilder content: @escaping (OnStarImageConfiguration) -> some View) -> some View { + self.transformEnvironment(\.onStarImageStyleStack) { stack in + let style = AnyOnStarImageStyle(content) + stack.append(style) + } + } +} + // MARK: OptionalTitleStyle public extension View { @@ -1006,6 +1057,23 @@ public extension View { } } +// MARK: ReviewCountLabelStyle + +public extension View { + func reviewCountLabelStyle(_ style: some ReviewCountLabelStyle) -> some View { + self.transformEnvironment(\.reviewCountLabelStyleStack) { stack in + stack.append(style) + } + } + + func reviewCountLabelStyle(@ViewBuilder content: @escaping (ReviewCountLabelConfiguration) -> some View) -> some View { + self.transformEnvironment(\.reviewCountLabelStyleStack) { stack in + let style = AnyReviewCountLabelStyle(content) + stack.append(style) + } + } +} + // MARK: Row1Style public extension View { diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift index 39735d69f..cfe3d7b0a 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/ViewEmptyChecking+Extension.generated.swift @@ -215,6 +215,12 @@ extension GreetingText: _ViewEmptyChecking { } } +extension HalfStarImage: _ViewEmptyChecking { + public var isEmpty: Bool { + halfStarImage.isEmpty + } +} + extension HeaderAction: _ViewEmptyChecking { public var isEmpty: Bool { headerAction.isEmpty @@ -380,6 +386,18 @@ extension ObjectItem: _ViewEmptyChecking { } } +extension OffStarImage: _ViewEmptyChecking { + public var isEmpty: Bool { + offStarImage.isEmpty + } +} + +extension OnStarImage: _ViewEmptyChecking { + public var isEmpty: Bool { + onStarImage.isEmpty + } +} + extension OptionalTitle: _ViewEmptyChecking { public var isEmpty: Bool { optionalTitle.isEmpty @@ -428,17 +446,32 @@ extension ProfileHeader: _ViewEmptyChecking { extension RatingControl: _ViewEmptyChecking { public var isEmpty: Bool { - false + valueLabel.isEmpty && + onStarImage.isEmpty && + offStarImage.isEmpty && + halfStarImage.isEmpty && + reviewCountLabel.isEmpty } } extension RatingControlFormView: _ViewEmptyChecking { public var isEmpty: Bool { title.isEmpty && + valueLabel.isEmpty && + onStarImage.isEmpty && + offStarImage.isEmpty && + halfStarImage.isEmpty && + reviewCountLabel.isEmpty && subtitle.isEmpty } } +extension ReviewCountLabel: _ViewEmptyChecking { + public var isEmpty: Bool { + reviewCountLabel.isEmpty + } +} + extension Row1: _ViewEmptyChecking { public var isEmpty: Bool { row1.isEmpty diff --git a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings index 2fe276f9c..9a882ce20 100644 --- a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings @@ -192,3 +192,12 @@ /* XBUT: timeline preview component timestamp label */ "Today, %d" = "Today, %d"; + +/* XFLD: RatingControl value label format */ +"%d of %d" = "%d of %d"; + +/* XFLD: RatingControl review count format */ +"%d reviews" = "%d reviews"; + +/* XFLD: RatingControl review count over ceiling format */ +"%d+ reviews" = "%d+ reviews"; diff --git a/Tests/FioriSwiftUITests/FioriSwiftUICore/RatingControlDataTests.swift b/Tests/FioriSwiftUITests/FioriSwiftUICore/RatingControlDataTests.swift index 3b03f27c4..6b8eb73b7 100644 --- a/Tests/FioriSwiftUITests/FioriSwiftUICore/RatingControlDataTests.swift +++ b/Tests/FioriSwiftUITests/FioriSwiftUICore/RatingControlDataTests.swift @@ -1,4 +1,5 @@ @testable import FioriSwiftUICore +import FioriThemeManager import SwiftUI import XCTest @@ -11,45 +12,44 @@ final class RatingControlDataTests: XCTestCase { // Put teardown code here. This method is called after the invocation of each test method in the class. } - @State var rating = 2 func testEditable() throws { - let ratingConfig = RatingControlConfiguration(rating: $rating, ratingControlStyle: .editable, ratingBounds: 0 ... 5, onImage: nil, offImage: nil, itemSize: nil, onColor: nil, offColor: nil, interItemSpacing: nil) + let ratingConfig = RatingControlConfiguration(valueLabel: RatingControlConfiguration.ValueLabel(Text("testLabel")), onStarImage: RatingControlConfiguration.OnStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), offStarImage: RatingControlConfiguration.OffStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), halfStarImage: RatingControlConfiguration.HalfStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), reviewCountLabel: RatingControlConfiguration.ReviewCountLabel(Text("reviews")), rating: .constant(2), ratingControlStyle: .editable, ratingBounds: 0 ... 5, itemSize: nil, interItemSpacing: nil, ratingValueFormat: nil, showsValueLabel: false, averageRating: nil, averageRatingFormat: "%.1f", reviewCount: nil, reviewCountFormat: nil, reviewCountCeiling: nil, reviewCountCeilingFormat: nil, showsReviewCountLabel: false) XCTAssertEqual(ratingConfig.getOnColor().cgColor, Color.preferredColor(.tintColor).cgColor) XCTAssertEqual(ratingConfig.getOffColor().cgColor, Color.preferredColor(.tintColor).cgColor) XCTAssertEqual(ratingConfig.getScale(), Image.Scale.large) XCTAssertEqual(ratingConfig.getItemSize(), CGSize(width: 28, height: 28)) - XCTAssertEqual(ratingConfig.getItemSpacing(), CGFloat(4)) + XCTAssertEqual(ratingConfig.getItemSpacing(), CGFloat(8)) } func testEditableDisabled() throws { - let ratingConfig = RatingControlConfiguration(rating: $rating, ratingControlStyle: .editableDisabled, ratingBounds: 0 ... 5, onImage: nil, offImage: nil, itemSize: nil, onColor: nil, offColor: nil, interItemSpacing: nil) + let ratingConfig = RatingControlConfiguration(valueLabel: RatingControlConfiguration.ValueLabel(Text("testLabel")), onStarImage: RatingControlConfiguration.OnStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), offStarImage: RatingControlConfiguration.OffStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), halfStarImage: RatingControlConfiguration.HalfStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), reviewCountLabel: RatingControlConfiguration.ReviewCountLabel(Text("reviews")), rating: .constant(2), ratingControlStyle: .editableDisabled, ratingBounds: 0 ... 5, itemSize: nil, interItemSpacing: nil, ratingValueFormat: nil, showsValueLabel: false, averageRating: nil, averageRatingFormat: "%.1f", reviewCount: nil, reviewCountFormat: nil, reviewCountCeiling: nil, reviewCountCeilingFormat: nil, showsReviewCountLabel: false) XCTAssertEqual(ratingConfig.getOnColor().cgColor, Color.preferredColor(.tintColor).cgColor) XCTAssertEqual(ratingConfig.getOffColor().cgColor, Color.preferredColor(.tintColor).cgColor) XCTAssertEqual(ratingConfig.getScale(), Image.Scale.large) XCTAssertEqual(ratingConfig.getItemSize(), CGSize(width: 28, height: 28)) - XCTAssertEqual(ratingConfig.getItemSpacing(), CGFloat(4)) + XCTAssertEqual(ratingConfig.getItemSpacing(), CGFloat(8)) } func testStandard() throws { - let ratingConfig = RatingControlConfiguration(rating: $rating, ratingControlStyle: .standard, ratingBounds: 0 ... 5, onImage: nil, offImage: nil, itemSize: nil, onColor: nil, offColor: nil, interItemSpacing: nil) + let ratingConfig = RatingControlConfiguration(valueLabel: RatingControlConfiguration.ValueLabel(Text("testLabel")), onStarImage: RatingControlConfiguration.OnStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), offStarImage: RatingControlConfiguration.OffStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), halfStarImage: RatingControlConfiguration.HalfStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), reviewCountLabel: RatingControlConfiguration.ReviewCountLabel(Text("reviews")), rating: .constant(2), ratingControlStyle: .standard, ratingBounds: 0 ... 5, itemSize: nil, interItemSpacing: nil, ratingValueFormat: nil, showsValueLabel: false, averageRating: nil, averageRatingFormat: "%.1f", reviewCount: nil, reviewCountFormat: nil, reviewCountCeiling: nil, reviewCountCeilingFormat: nil, showsReviewCountLabel: false) XCTAssertEqual(ratingConfig.getOnColor().cgColor, Color.preferredColor(.tertiaryLabel).cgColor) XCTAssertEqual(ratingConfig.getOffColor().cgColor, Color.preferredColor(.tertiaryLabel).cgColor) XCTAssertEqual(ratingConfig.getScale(), Image.Scale.small) XCTAssertEqual(ratingConfig.getItemSize(), CGSize(width: 16, height: 16)) - XCTAssertEqual(ratingConfig.getItemSpacing(), CGFloat(2)) + XCTAssertEqual(ratingConfig.getItemSpacing(), CGFloat(6)) } func testAccented() throws { - let ratingConfig = RatingControlConfiguration(rating: $rating, ratingControlStyle: .accented, ratingBounds: 0 ... 5, onImage: nil, offImage: nil, itemSize: nil, onColor: nil, offColor: nil, interItemSpacing: nil) + let ratingConfig = RatingControlConfiguration(valueLabel: RatingControlConfiguration.ValueLabel(Text("testLabel")), onStarImage: RatingControlConfiguration.OnStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), offStarImage: RatingControlConfiguration.OffStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), halfStarImage: RatingControlConfiguration.HalfStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), reviewCountLabel: RatingControlConfiguration.ReviewCountLabel(Text("reviews")), rating: .constant(2), ratingControlStyle: .accented, ratingBounds: 0 ... 5, itemSize: nil, interItemSpacing: nil, ratingValueFormat: nil, showsValueLabel: false, averageRating: nil, averageRatingFormat: "%.1f", reviewCount: nil, reviewCountFormat: nil, reviewCountCeiling: nil, reviewCountCeilingFormat: nil, showsReviewCountLabel: false) XCTAssertEqual(ratingConfig.getOnColor().cgColor, Color.preferredColor(.mango4).cgColor) XCTAssertEqual(ratingConfig.getOffColor().cgColor, Color.preferredColor(.mango4).cgColor) XCTAssertEqual(ratingConfig.getScale(), Image.Scale.small) XCTAssertEqual(ratingConfig.getItemSize(), CGSize(width: 16, height: 16)) - XCTAssertEqual(ratingConfig.getItemSpacing(), CGFloat(2)) + XCTAssertEqual(ratingConfig.getItemSpacing(), CGFloat(6)) } func testRatingItems() throws { - let ratingConfig = RatingControlConfiguration(rating: $rating, ratingControlStyle: .editable, ratingBounds: 0 ... 5, onImage: nil, offImage: nil, itemSize: nil, onColor: nil, offColor: nil, interItemSpacing: nil) + let ratingConfig = RatingControlConfiguration(valueLabel: RatingControlConfiguration.ValueLabel(Text("testLabel")), onStarImage: RatingControlConfiguration.OnStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), offStarImage: RatingControlConfiguration.OffStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), halfStarImage: RatingControlConfiguration.HalfStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), reviewCountLabel: RatingControlConfiguration.ReviewCountLabel(Text("reviews")), rating: .constant(2), ratingControlStyle: .editable, ratingBounds: 0 ... 5, itemSize: nil, interItemSpacing: nil, ratingValueFormat: nil, showsValueLabel: false, averageRating: nil, averageRatingFormat: "%.1f", reviewCount: nil, reviewCountFormat: nil, reviewCountCeiling: nil, reviewCountCeilingFormat: nil, showsReviewCountLabel: false) let items = ratingConfig.ratingItems(2) XCTAssertEqual(items.count, 5) XCTAssertEqual(items[0].isOn, true) @@ -58,7 +58,7 @@ final class RatingControlDataTests: XCTestCase { XCTAssertEqual(items[3].isOn, false) XCTAssertEqual(items[4].isOn, false) - let ratingConfig2 = RatingControlConfiguration(rating: $rating, ratingControlStyle: .accented, ratingBounds: -5 ... 5, onImage: nil, offImage: nil, itemSize: nil, onColor: nil, offColor: nil, interItemSpacing: nil) + let ratingConfig2 = RatingControlConfiguration(valueLabel: RatingControlConfiguration.ValueLabel(Text("testLabel")), onStarImage: RatingControlConfiguration.OnStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), offStarImage: RatingControlConfiguration.OffStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), halfStarImage: RatingControlConfiguration.HalfStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), reviewCountLabel: RatingControlConfiguration.ReviewCountLabel(Text("reviews")), rating: .constant(2), ratingControlStyle: .accented, ratingBounds: -5 ... 5, itemSize: nil, interItemSpacing: nil, ratingValueFormat: nil, showsValueLabel: false, averageRating: nil, averageRatingFormat: "%.1f", reviewCount: nil, reviewCountFormat: nil, reviewCountCeiling: nil, reviewCountCeilingFormat: nil, showsReviewCountLabel: false) let items2 = ratingConfig2.ratingItems(2) XCTAssertEqual(items2.count, 10) XCTAssertEqual(items2[0].isOn, true) @@ -71,5 +71,14 @@ final class RatingControlDataTests: XCTestCase { XCTAssertEqual(items2[7].isOn, false) XCTAssertEqual(items2[8].isOn, false) XCTAssertEqual(items2[9].isOn, false) + + let ratingConfig3 = RatingControlConfiguration(valueLabel: RatingControlConfiguration.ValueLabel(Text("testLabel")), onStarImage: RatingControlConfiguration.OnStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), offStarImage: RatingControlConfiguration.OffStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), halfStarImage: RatingControlConfiguration.HalfStarImage(FioriIcon.actions.favorite.renderingMode(.template).resizable()), reviewCountLabel: RatingControlConfiguration.ReviewCountLabel(Text("reviews")), rating: .constant(2), ratingControlStyle: .standard, ratingBounds: 0 ... 5, itemSize: nil, interItemSpacing: nil, ratingValueFormat: nil, showsValueLabel: false, averageRating: 3.5, averageRatingFormat: "%.1f", reviewCount: nil, reviewCountFormat: nil, reviewCountCeiling: nil, reviewCountCeilingFormat: nil, showsReviewCountLabel: false) + let items3 = ratingConfig3.ratingItems(3.5) + XCTAssertEqual(items3.count, 5) + XCTAssertEqual(items3[0].isOn, true) + XCTAssertEqual(items3[1].isOn, true) + XCTAssertEqual(items3[2].isOn, true) + XCTAssertEqual(items3[3].isHalf, true) + XCTAssertEqual(items3[4].isOn, false) } } From 9cee816cacc06ff80faf8019f0d7d1fc82f3ec2e Mon Sep 17 00:00:00 2001 From: bigCode Date: Fri, 13 Sep 2024 09:42:17 +0800 Subject: [PATCH 24/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:HCPSDKFIORIU?= =?UTF-8?q?IKIT-2716]Step=20Progress=20Indicator=20(#787)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2716]Step Progress Indicator Add stepProgressIndicatorNodeType for step node. * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2716]Step Progress Indicator use TextOrIcon replace Image * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2716]Step Progress Indicator code format * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2716]Step Progress Indicator Remove the mixture property of StepProgressIndicatorNodeType. * feat: 🎸 Step Progress Indicator remove stepProgressIndicatorNodeType implementation * refactor: 💡 Modify code using TextOrIconView --------- Co-authored-by: Bill Zhou --- .../StepProgressIndicatorExample.swift | 80 +++++++++++-------- .../StepProgressIndicator/StepItem.swift | 4 +- .../StepProgressIndicator/_DefaultSteps.swift | 6 +- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift index bfed3ef30..b803ff219 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/StepProgressIndicator/StepProgressIndicatorExample.swift @@ -51,27 +51,34 @@ struct StepProgressIndicatorExample_Previews: PreviewProvider { } struct SPIExampleWithIcon: View { - static let icon = Image(systemName: "app.dashed") @State var title: String = "" - @State var steps = [StepItemData(title: "Sign In", icon: FioriIcon.arrows.initiative, state: .completed), - StepItemData(title: "User Info", icon: FioriIcon.people.personPlaceholder, state: .completed), - StepItemData(title: "Account Info", icon: FioriIcon.actions.edit, state: .completed), - StepItemData(title: "Settings", icon: FioriIcon.actions.actionSettings, state: .normal, substeps: [StepItemData(title: "Settings 1")]), - StepItemData(title: "Other0", icon: icon, state: .disabled), - StepItemData(title: "Other1", icon: icon, state: .completed), - StepItemData(title: "other2", icon: icon, state: .error)] - @State var selection: String = "" + @State var iconSteps = [StepItemData(title: "Sign In", node: .icon(FioriIcon.arrows.initiative), state: .completed), + StepItemData(title: "User Info", node: .icon(FioriIcon.people.personPlaceholder), state: .completed), + StepItemData(title: "Account Info", node: .icon(FioriIcon.actions.edit), state: .completed), + StepItemData(title: "Settings", node: .icon(FioriIcon.actions.actionSettings), state: .normal, substeps: [StepItemData(title: "Settings 1")]), + StepItemData(title: "Other0", node: .icon(Image(systemName: "app.dashed")), state: .disabled), + StepItemData(title: "Other1", node: .icon(Image(systemName: "app.dashed")), state: .completed), + StepItemData(title: "other2", node: .icon(Image(systemName: "app.dashed")), state: .error)] + @State var textSteps = [StepItemData(title: "Sign In", state: .completed), + StepItemData(title: "User Info", node: .text("AB"), state: .completed), + StepItemData(title: "Account Info", state: .completed), + StepItemData(title: "Settings", node: .text("AD"), state: .normal, substeps: [StepItemData(title: "Settings 1")]), + StepItemData(title: "Other0", state: .disabled), + StepItemData(title: "Other1", state: .completed), + StepItemData(title: "other2", state: .error)] + @State var iconSelection: String = "" + @State var textSelection: String = "" var body: some View { VStack(alignment: .leading) { - Text("Icon").bold() - StepProgressIndicator(selection: self.$selection, - stepItems: self.steps) + Text("Step: Only Icon Node").bold() + StepProgressIndicator(selection: self.$iconSelection, + stepItems: self.iconSteps) { Text(self.$title.wrappedValue).lineLimit(1) } action: { Button {} label: { HStack(spacing: 2) { - Text("All Steps(\(self.steps.count))") + Text("All Steps(\(self.iconSteps.count))") .foregroundStyle(Color.preferredColor(.tintColor)) FioriIcon.actions.slimArrowRight .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) @@ -79,6 +86,22 @@ struct SPIExampleWithIcon: View { } } } + .padding() + .onChange(of: self.iconSelection, perform: { _ in + self.updateCurrentStepName() + }) + .onAppear { + self.updateCurrentStepName() + } + + Text("Step: Only Text Node").bold() + StepProgressIndicator(selection: self.$textSelection, + stepItems: self.textSteps) + { + Text("Invariant title").lineLimit(1) + } action: {} + .padding() + Spacer().padding(20) Button { self.completeStep() @@ -87,19 +110,12 @@ struct SPIExampleWithIcon: View { } .padding(20) } - .padding() - .onChange(of: self.selection, perform: { _ in - self.updateCurrentStepName() - }) - .onAppear { - self.updateCurrentStepName() - } } func getStep() -> StepItem? { func findStep(in data: [StepItem]) -> StepItem? { for step in data { - if step.id == self.selection { + if step.id == self.iconSelection { return step } @@ -114,7 +130,7 @@ struct SPIExampleWithIcon: View { return nil } - return findStep(in: self.steps) + return findStep(in: self.iconSteps) } func updateCurrentStepName() { @@ -125,15 +141,15 @@ struct SPIExampleWithIcon: View { } func completeStep() { - for index in self.steps.indices { - if self.steps[index].id == self.selection { - self.steps[index].state = .completed + for index in self.iconSteps.indices { + if self.iconSteps[index].id == self.iconSelection { + self.iconSteps[index].state = .completed } else { - let substeps = self.steps[index].substeps + let substeps = self.iconSteps[index].substeps guard !substeps.isEmpty else { continue } for subindex in substeps.indices { - if substeps[subindex].id == self.selection { - self.steps[index].substeps[subindex].state = .completed + if substeps[subindex].id == self.iconSelection { + self.iconSteps[index].substeps[subindex].state = .completed } } } @@ -528,8 +544,8 @@ struct StepItemData: StepItem { var id: String = UUID().uuidString /// Step title. var title: String? - /// Node icon - var icon: Image? + /// Step node + var node: TextOrIcon? /// Step state. var state: StepProgressIndicatorState = .normal /// Sub-steps for this one. @@ -537,13 +553,13 @@ struct StepItemData: StepItem { init(id: String = UUID().uuidString, title: String? = nil, - icon: Image? = nil, + node: TextOrIcon? = nil, state: StepProgressIndicatorState = [], substeps: [StepItemData] = []) { self.id = id self.title = title - self.icon = icon + self.node = node self.state = state self.substeps = substeps } diff --git a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepItem.swift b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepItem.swift index 10dd8ef99..43c879312 100644 --- a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepItem.swift +++ b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/StepItem.swift @@ -11,6 +11,6 @@ public protocol StepItem { var state: StepProgressIndicatorState { get set } /// Substeps for this one. var substeps: [StepItem] { get set } - /// Node icon. - var icon: Image? { get } + /// Step node. + var node: TextOrIcon? { get } } diff --git a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/_DefaultSteps.swift b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/_DefaultSteps.swift index f8a571c5f..0c6ad439d 100644 --- a/Sources/FioriSwiftUICore/Views/StepProgressIndicator/_DefaultSteps.swift +++ b/Sources/FioriSwiftUICore/Views/StepProgressIndicator/_DefaultSteps.swift @@ -1,3 +1,4 @@ +import FioriThemeManager import SwiftUI /// Not used by developers. @@ -98,12 +99,11 @@ struct DefaultSingleStep: View { } node: { ZStack { self.node(by: self.stepItem.state, isSelected: isSelected) - if self.stepItem.icon.isEmpty { + if self.stepItem.node == nil { Text("\(self.index + 1)") .font(Font.fiori(forTextStyle: .footnote)) - } else { - self.stepItem.icon } + TextOrIconView(self.stepItem.node) } .frame(width: self.sideLength, height: self.sideLength) .overlay { From dd8342839762da4ae09bb15f97bfa0ed7d8ffc31 Mon Sep 17 00:00:00 2001 From: Bill Zhou Date: Fri, 13 Sep 2024 11:23:15 -0700 Subject: [PATCH 25/29] chore: merge rel-4.1 into main (#800) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2702]Illustrated Message (#782) [JIRA:HCPSDKFIORIUIKIT-2702]Illustrated Message Enhancement. Cherrypick from main. Co-authored-by: I824136 * chore: 🤖 fix layout issue in Icons and IconBuilder * fix: 🐛 [HCPSDKFIORIUIKIT-2720] Style updates in ObjectItem * chore(release): 4.1.4 * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update (#795) * chore: 🤖 fix layout issue in Icons and IconBuilder * fix: 🐛 [HCPSDKFIORIUIKIT-2720] Style updates in ObjectItem * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update * fix: 🐛 [FIORIDESIGN-13047] Joule Menu Selection Icon Update * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update (#795) (#796) * chore: 🤖 fix layout issue in Icons and IconBuilder * fix: 🐛 [HCPSDKFIORIUIKIT-2720] Style updates in ObjectItem * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update * fix: 🐛 [FIORIDESIGN-13047] Joule Menu Selection Icon Update * fix: 🐛 [FIORIDESIGN-13047] Joule Menu Selection Icon Update (#798) * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update (#795) * chore: 🤖 fix layout issue in Icons and IconBuilder * fix: 🐛 [HCPSDKFIORIUIKIT-2720] Style updates in ObjectItem * fix: 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update * fix: 🐛 [FIORIDESIGN-13047] Joule Menu Selection Icon Update * fix: 🐛 [JIRA:IOSSDKBUG-324]Usage of SwiftUI KeyValueFormView (#790) * fix: 🐛 [JIRA:IOSSDKBUG-324]Usage of SwiftUI KeyValueFormView ✅ Closes: IOSSDKBUG-324 * fix: 🐛 [JIRA:IOSSDKBUG-324]Usage of SwiftUI KeyValueFormView --------- Co-authored-by: I824136 * docs: ✏️ changelog for 4.1.5 --------- Co-authored-by: xiaoqinggrace <52239714+xiaoqinggrace@users.noreply.github.com> Co-authored-by: I824136 Co-authored-by: Marco Eidinger --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7371ee26..17461568d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [4.1.5](https://github.com/SAP/cloud-sdk-ios-fiori/compare/4.1.4...4.1.5) (2024-09-12) + + +### Bug Fixes + +* 🐛 [FIORIDESIGN-13047] Joule Menu Selection Icon Update ([#798](https://github.com/SAP/cloud-sdk-ios-fiori/issues/798)) ([40ff9fe](https://github.com/SAP/cloud-sdk-ios-fiori/commit/40ff9fe310b6efffa807a429e74d8571b6a188f7)) +* 🐛 [JIRA:IOSSDKBUG-324]Usage of SwiftUI KeyValueFormView ([#790](https://github.com/SAP/cloud-sdk-ios-fiori/issues/790)) ([d03f33a](https://github.com/SAP/cloud-sdk-ios-fiori/commit/d03f33a377eb1a7ed0cc8d8b71337bd85e0d1e78)) +* 🐛 [IOSSDKBUG-346] JouleWelcomeScreen title style update ([#795](https://github.com/SAP/cloud-sdk-ios-fiori/issues/795)) + +### [4.1.4](https://github.com/SAP/cloud-sdk-ios-fiori/compare/4.1.3...4.1.4) (2024-09-03) + + +### Features + +* 🎸 [JIRA:HCPSDKFIORIUIKIT-2702]Illustrated Message ([#782](https://github.com/SAP/cloud-sdk-ios-fiori/issues/782)) ([239d7ae](https://github.com/SAP/cloud-sdk-ios-fiori/commit/239d7aee0ab2c23644c134d2f287d292bb27203b)) + ### [4.1.3](https://github.com/SAP/cloud-sdk-ios-fiori/compare/4.1.2...4.1.3) (2024-08-19) ### Features From 1fc41a6a2cdb11211ede7dfc0f79469dd2723aee Mon Sep 17 00:00:00 2001 From: janhuachu <48767307+janhuachu@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:18:33 -0700 Subject: [PATCH 26/29] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20RatingControl:=20a?= =?UTF-8?q?ccessibility=20updates=20(#801)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Closes: JIRA:HCPSDKFIORIUIKIT-2707 --- .../DataTypes/RatingControl+DataType.swift | 40 ++++++++++++++++++- .../RatingControlFormViewStyle.fiori.swift | 1 + .../RatingControlStyle.fiori.swift | 9 ++--- .../en.lproj/FioriSwiftUICore.strings | 15 +++++++ 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift index 5990d4a58..8fab74bab 100644 --- a/Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift +++ b/Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift @@ -51,7 +51,7 @@ public extension RatingControl { } internal static func getAccessibilityLabelString(_ rating: Int, ratingBounds: ClosedRange) -> String { - let labelFormat = NSLocalizedString("%d out of %d stars", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + let labelFormat = NSLocalizedString("Rating Control %d of %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") return String(format: labelFormat, rating, ratingBounds.count - 1) } } @@ -184,4 +184,42 @@ extension RatingControlConfiguration { return self.ratingBounds.lowerBound + n } } + + func getAccessbilityLabelString() -> String { + switch ratingControlStyle { + case .editable, .editableDisabled: + let format = NSLocalizedString("Rating Control %d of %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, rating, ratingBounds.count - 1) + default: + // read-only + if let averageRating { + if let reviewCount { + if let reviewCountCeiling, reviewCount > reviewCountCeiling { + // review count > review count ceiling + let format = NSLocalizedString("Rating Control, average rating %.1f of %d, %d plus reviews", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, averageRating, ratingBounds.count - 1, reviewCountCeiling) + } else { + // review count < review count ceiling + let format = NSLocalizedString("Rating Control, average rating %.1f of %d, %d reviews", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, averageRating, ratingBounds.count - 1, reviewCount) + } + } else { + // no review count + let format = NSLocalizedString("Rating Control, average rating %.1f of %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, averageRating, ratingBounds.count - 1) + } + } else { + // no average rating + if let reviewCount { + // has review count + let format = NSLocalizedString("Rating Control, rating %d of %d, %d reviews", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, rating, ratingBounds.count - 1, reviewCount) + } else { + // no review count + let format = NSLocalizedString("Rating Control %d of %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") + return String(format: format, rating, ratingBounds.count - 1) + } + } + } + } } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/RatingControlFormViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/RatingControlFormViewStyle.fiori.swift index a9a9b28a8..38943ca43 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/RatingControlFormViewStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/RatingControlFormViewStyle.fiori.swift @@ -54,6 +54,7 @@ public struct RatingControlFormViewBaseStyle: RatingControlFormViewStyle { } } } + .disabled(configuration.ratingControlStyle == .editableDisabled) } func setRatingValue(_ configuration: RatingControlFormViewConfiguration, newRating: Int) { diff --git a/Sources/FioriSwiftUICore/_FioriStyles/RatingControlStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/RatingControlStyle.fiori.swift index 7bd546859..ef30964d6 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/RatingControlStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/RatingControlStyle.fiori.swift @@ -22,7 +22,7 @@ public struct RatingControlBaseStyle: RatingControlStyle { } ) .accessibilityElement() - .accessibilityLabel(self.getAccessibilityLabel(configuration)) + .accessibilityLabel(configuration.getAccessbilityLabelString()) .setAccessibilityAdjustable(configuration.ratingControlStyle == .editable) { direction in if configuration.ratingControlStyle == .editable { switch direction { @@ -39,6 +39,7 @@ public struct RatingControlBaseStyle: RatingControlStyle { } } } + .disabled(configuration.ratingControlStyle == .editableDisabled) } @ViewBuilder func getMainBody(_ configuration: RatingControlConfiguration) -> some View { @@ -111,14 +112,10 @@ public struct RatingControlBaseStyle: RatingControlStyle { func setRatingValue(_ configuration: RatingControlConfiguration, newRating: Int) { if configuration.rating != newRating { configuration.rating = newRating - UIAccessibility.post(notification: .announcement, argument: self.getAccessibilityLabel(configuration)) + UIAccessibility.post(notification: .announcement, argument: RatingControl.getAccessibilityLabelString(newRating, ratingBounds: configuration.ratingBounds)) } } - func getAccessibilityLabel(_ configuration: RatingControlConfiguration) -> String { - RatingControl.getAccessibilityLabelString(configuration.rating, ratingBounds: configuration.ratingBounds) - } - func getReadOnly(_ configuration: RatingControlConfiguration) -> Bool { switch configuration.ratingControlStyle { case .editable, .editableDisabled: diff --git a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings index 9a882ce20..5c12484b2 100644 --- a/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings +++ b/Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings @@ -201,3 +201,18 @@ /* XFLD: RatingControl review count over ceiling format */ "%d+ reviews" = "%d+ reviews"; + +/* XACT: RatingControl accessibility label */ +"Rating Control %d of %d" = "Rating Control %d of %d"; + +/* XACT: read-only RatingControl accessibility label: rating with review count */ +"Rating Control, rating %d of %d, %d reviews" = "Rating Control, rating %d of %d, %d reviews"; + +/* XACT: read-only RatingControl accessibility label: average rating only */ +"Rating Control, average rating %.1f of %d" = "Rating Control, average rating %.1f of %d"; + +/* XACT: read-only RatingControl accessibility label: average rating with review count */ +"Rating Control, average rating %.1f of %d, %d reviews" = "Rating Control, average rating %.1f of %d, %d reviews"; + +/* XACT: read-only RatingControl accessibility label: average rating with review count over ceiling */ +"Rating Control, average rating %.1f of %d, %d plus reviews" = "Rating Control, average rating %.1f of %d, %d plus reviews"; From aa10c2d3c11fa14ce95ace76dbb911b88f737374 Mon Sep 17 00:00:00 2001 From: dyongxu <61523257+dyongxu@users.noreply.github.com> Date: Thu, 19 Sep 2024 07:36:19 +0800 Subject: [PATCH 27/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e2bb49c5..9d40dcd4b 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ We plan to progressively bring more Fiori UI components into this module in the | ProfileHeader | :white_check_mark: | | TimelineItem | :white_check_mark: | | SegmentedControlPicker | :white_check_mark: | -| TimelinePreviewItem | :x: | +| TimelinePreviewItem | :white_check_mark: | | ChartFloorplan | :x: | | CollectionItem | :x: | | BarcodeScanner | :x: | From 29f33b825bda13783fca0162175345e431f9df88 Mon Sep 17 00:00:00 2001 From: janhuachu <48767307+janhuachu@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:01:10 -0700 Subject: [PATCH 28/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[JIRA:FIORIDESIGN-1?= =?UTF-8?q?2289]=20fix=20textinput=20review=20comments=20(#804)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FormViews/NoteFormViewExample.swift | 4 ++-- .../FormViews/TitleFormViewExample.swift | 12 +++++++++++- .../KeyValueFormViewStyle.fiori.swift | 7 ++++++- .../_FioriStyles/NoteFormViewStyle.fiori.swift | 15 ++++++++------- .../PlaceholderTextEditorStyle.fiori.swift | 1 - .../PlaceholderTextFieldStyle.fiori.swift | 2 +- .../TextFieldFormViewStyle.fiori.swift | 9 ++++++++- .../TextInputInfoViewStyle.fiori.swift | 4 ++-- .../_FioriStyles/TitleFormViewStyle.fiori.swift | 6 +++--- 9 files changed, 41 insertions(+), 19 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FormViews/NoteFormViewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FormViews/NoteFormViewExample.swift index 582e399d2..84582fb16 100755 --- a/Apps/Examples/Examples/FioriSwiftUICore/FormViews/NoteFormViewExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/FormViews/NoteFormViewExample.swift @@ -61,10 +61,10 @@ struct NoteFormViewExample: View { NoteFormView(text: self.$valueText3, placeholder: "Please enter something", errorMessage: self.getErrorMessage(), minTextEditorHeight: 50, maxTextEditorHeight: 100, maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit) Text("Disabled") - NoteFormView(text: self.$disabledText, placeholder: "Disabled", controlState: .disabled, minTextEditorHeight: 50, maxTextEditorHeight: 100) + NoteFormView(text: self.$disabledText, placeholder: "Disabled", controlState: .disabled, maxTextEditorHeight: 100) Text("Read-Only") - NoteFormView(text: self.$readOnlyText, placeholder: "Read-Only", controlState: .readOnly, minTextEditorHeight: 50, maxTextEditorHeight: 200, hidesReadOnlyHint: self.hidesReadonlyHint) + NoteFormView(text: self.$readOnlyText, placeholder: "Read-Only", controlState: .readOnly, maxTextEditorHeight: 200, hidesReadOnlyHint: self.hidesReadonlyHint) } #if !os(visionOS) .scrollDismissesKeyboard(.immediately) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FormViews/TitleFormViewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FormViews/TitleFormViewExample.swift index 9924077d4..f77cd0fa0 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/FormViews/TitleFormViewExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/FormViews/TitleFormViewExample.swift @@ -46,20 +46,30 @@ struct TitleFormViewExample: View { Text("Default TitleForm") TitleFormView(text: self.$valueText1, placeholder: "TitleFormView", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit) - + .padding(.leading, -4) + .padding(.trailing, -4) + Text("Existing Text") .italic() TitleFormView(text: self.$valueText2, placeholder: "TitleFormView", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit) + .padding(.leading, -4) + .padding(.trailing, -4) Text("Empty Text") .italic() TitleFormView(text: self.$valueText3, placeholder: "Please enter something", errorMessage: self.getErrorMessage(), maxTextLength: self.getMaxTextLength(), hintText: self.getHintText(), isCharCountEnabled: self.showsCharCount, allowsBeyondLimit: self.allowsBeyondLimit) + .padding(.leading, -4) + .padding(.trailing, -4) Text("Disabled") TitleFormView(text: self.$disabledText, placeholder: "Disabled", controlState: .disabled) + .padding(.leading, -4) + .padding(.trailing, -4) Text("Read-Only") TitleFormView(text: self.$readOnlyText, placeholder: "Read-Only", controlState: .readOnly, hidesReadOnlyHint: self.hidesReadonlyHint) + .padding(.leading, -4) + .padding(.trailing, -4) } #if !os(visionOS) .scrollDismissesKeyboard(.immediately) diff --git a/Sources/FioriSwiftUICore/_FioriStyles/KeyValueFormViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/KeyValueFormViewStyle.fiori.swift index 5f3ea24d5..3ed787f1d 100755 --- a/Sources/FioriSwiftUICore/_FioriStyles/KeyValueFormViewStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/KeyValueFormViewStyle.fiori.swift @@ -29,6 +29,11 @@ extension KeyValueFormViewFioriStyle { .foregroundStyle(self.getTitleColor(configuration)) .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) } + .mandatoryFieldIndicatorStyle { indicatorConf in + MandatoryFieldIndicator(indicatorConf) + .foregroundStyle(self.getTitleColor(configuration)) + .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) + } .focused(self.$isFocused) } @@ -90,7 +95,7 @@ extension KeyValueFormViewFioriStyle { func makeBody(_ configuration: MandatoryFieldIndicatorConfiguration) -> some View { MandatoryFieldIndicator(configuration) - .foregroundStyle(Color.preferredColor(self.keyValueFormViewConfiguration.controlState == .disabled ? .separator : .primaryLabel)) + .foregroundStyle(Color.preferredColor(self.keyValueFormViewConfiguration.controlState == .disabled ? .quaternaryLabel : .primaryLabel)) .font(.fiori(forTextStyle: .subheadline, weight: .semibold)) .padding(.bottom, -4) .padding(.top, 11) diff --git a/Sources/FioriSwiftUICore/_FioriStyles/NoteFormViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/NoteFormViewStyle.fiori.swift index 389664d22..f16b5d4ca 100755 --- a/Sources/FioriSwiftUICore/_FioriStyles/NoteFormViewStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/NoteFormViewStyle.fiori.swift @@ -48,6 +48,7 @@ extension NoteFormViewFioriStyle { TextInputInfoView(config) .textInputInfoViewStyle(style) .typeErased + .padding(.top, -8) } .placeholderTextEditorStyle { config in PlaceholderTextEditor(config) @@ -62,14 +63,14 @@ extension NoteFormViewFioriStyle { .onChange(of: configuration.text) { s in self.checkCharCount(configuration, textString: s) } - .padding(.bottom, self.isInfoViewNeeded(configuration) ? 0 : 9) + .padding(.bottom, self.isInfoViewNeeded(configuration) ? 0 : 7) } .textViewStyle { config in TextView(config) .foregroundStyle(self.getTextColor(configuration)) .font(.fiori(forTextStyle: .body)) } - .padding(.top, 9) + .padding(.top, 7) } func getTextColor(_ configuration: NoteFormViewConfiguration) -> Color { @@ -101,15 +102,15 @@ extension NoteFormViewFioriStyle { } func getMinHeight(_ configuration: NoteFormViewConfiguration) -> CGFloat { - // Somehow, the minHeight is 14pt higher than the specified. Use this adjustment. - let minHeightAdjustMent = 14.0 + // TextEditor will add some other views that the minHeight is 16pt higher than the specified. Use this to adjust. + let minHeightAdjustment = 16.0 guard let minHeight = configuration.minTextEditorHeight else { - return 88 - minHeightAdjustMent + return 88 - minHeightAdjustment } guard minHeight > 44 else { - return 88 - minHeightAdjustMent + return 88 - minHeightAdjustment } - return minHeight - minHeightAdjustMent + return minHeight - minHeightAdjustment } func getMaxHeight(_ configuration: NoteFormViewConfiguration) -> CGFloat { diff --git a/Sources/FioriSwiftUICore/_FioriStyles/PlaceholderTextEditorStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/PlaceholderTextEditorStyle.fiori.swift index 98793bdd4..66e6bf46b 100755 --- a/Sources/FioriSwiftUICore/_FioriStyles/PlaceholderTextEditorStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/PlaceholderTextEditorStyle.fiori.swift @@ -10,7 +10,6 @@ public struct PlaceholderTextEditorBaseStyle: PlaceholderTextEditorStyle { public func makeBody(_ configuration: PlaceholderTextEditorConfiguration) -> some View { ZStack(alignment: .topLeading) { configuration._textView.body - .focused(self.$isFocused) .focused(self.$isFocused) .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification)) { _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { diff --git a/Sources/FioriSwiftUICore/_FioriStyles/PlaceholderTextFieldStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/PlaceholderTextFieldStyle.fiori.swift index 3bb6e2d4a..f41f5062d 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/PlaceholderTextFieldStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/PlaceholderTextFieldStyle.fiori.swift @@ -30,7 +30,7 @@ public struct PlaceholderTextFieldBaseStyle: PlaceholderTextFieldStyle { Image(systemName: "xmark.circle") .font(.fiori(forTextStyle: .body)) .foregroundColor(.preferredColor(.tertiaryLabel)) - .padding(.trailing, 8) + .padding(.trailing, 1) } } } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/TextFieldFormViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/TextFieldFormViewStyle.fiori.swift index 065d9cc84..ae9076252 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/TextFieldFormViewStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/TextFieldFormViewStyle.fiori.swift @@ -5,7 +5,7 @@ import SwiftUI /// The base layout style for `TextFieldFormView`. public struct TextFieldFormViewBaseStyle: TextFieldFormViewStyle { public func makeBody(_ configuration: TextFieldFormViewConfiguration) -> some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 4) { HStack(spacing: 0) { configuration.title if configuration.isRequired { @@ -15,6 +15,12 @@ public struct TextFieldFormViewBaseStyle: TextFieldFormViewStyle { } configuration._titleFormView } + .padding(.top, -1) + .padding(.bottom, self.isInfoViewNeeded(configuration) ? 0 : 1) + } + + func isInfoViewNeeded(_ configuration: TextFieldFormViewConfiguration) -> Bool { + TextInputFormViewConfiguration(configuration, isFocused: false).isInfoViewNeeded() } } @@ -63,6 +69,7 @@ extension TextFieldFormViewFioriStyle { TextInputInfoView(config) .textInputInfoViewStyle(style) .typeErased + .padding(.top, -3) } } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/TextInputInfoViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/TextInputInfoViewStyle.fiori.swift index bd92dc576..3bc854e5b 100755 --- a/Sources/FioriSwiftUICore/_FioriStyles/TextInputInfoViewStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/TextInputInfoViewStyle.fiori.swift @@ -387,7 +387,7 @@ struct TextInputFormViewConfiguration { func getTextColor() -> Color { switch self.getControlState() { case .disabled: - return .preferredColor(.separator) + return .preferredColor(.quaternaryLabel) default: return .preferredColor(.primaryLabel) } @@ -417,7 +417,7 @@ struct TextInputFormViewConfiguration { } func getBackgroundColor() -> Color { - self.getEditable() ? .clear : .preferredColor(.tertiaryFill) + self.getEditable() ? .clear : .preferredColor(.quaternaryFill) } func getBorderWidth() -> CGFloat { diff --git a/Sources/FioriSwiftUICore/_FioriStyles/TitleFormViewStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/TitleFormViewStyle.fiori.swift index c9fcff5e9..a8ab7ad08 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/TitleFormViewStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/TitleFormViewStyle.fiori.swift @@ -13,7 +13,8 @@ public struct TitleFormViewBaseStyle: TitleFormViewStyle { .disabled(self.getDisabled(configuration)) } .textInputInfoView(isPresented: Binding(get: { self.isInfoViewNeeded(configuration) }, set: { _ in }), description: self.getInfoString(configuration), counter: self.getCounterString(configuration)) - .backgroundStyle(.red) + .padding(.top, -1) + .padding(.bottom, self.isInfoViewNeeded(configuration) ? -12 : -1) } func getDisabled(_ configuration: TitleFormViewConfiguration) -> Bool { @@ -51,6 +52,7 @@ extension TitleFormViewFioriStyle { TextInputInfoView(config) .textInputInfoViewStyle(style) .typeErased + .padding(.top, -4) } .placeholderTextFieldStyle { config in PlaceholderTextField(config) @@ -59,8 +61,6 @@ extension TitleFormViewFioriStyle { } .background(self.getBackgroundColor(configuration)) } - .padding(.top, 0) - .padding(.bottom, self.isInfoViewNeeded(configuration) ? -9 : 0) } } From 98a0abb4c82dabd6f1ef9e5ccaed83c38e9935a6 Mon Sep 17 00:00:00 2001 From: zzchao-1999 <149659707+zzchao-1999@users.noreply.github.com> Date: Mon, 23 Sep 2024 14:14:08 +0800 Subject: [PATCH 29/29] =?UTF-8?q?fix:=20=F0=9F=90=9B=20IOSSDKBUG-354=20Ste?= =?UTF-8?q?pperView=20SwiftUI=20EXC=5FBAD=5FACCESS=20(#805)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FormCells/StepperViewExample.swift | 6 ++---- .../CompositeComponentProtocols.swift | 3 ++- .../_FioriStyles/StepperFieldStyle.fiori.swift | 16 ++++++++-------- .../AvatarStack/AvatarStack.generated.swift | 4 ++-- .../AvatarsTitle/AvatarsTitle.generated.swift | 2 +- .../DateTimePicker.generated.swift | 4 ++-- .../OptionalTitle/OptionalTitle.generated.swift | 2 +- .../StepperField/StepperField.generated.swift | 6 +++--- .../StepperFieldStyle.generated.swift | 2 +- .../StepperView/StepperView.generated.swift | 6 +++--- .../StepperView/StepperViewStyle.generated.swift | 2 +- .../TimelinePreview.generated.swift | 4 ++-- .../TimelinePreviewItem.generated.swift | 8 ++++---- .../ValueLabel/ValueLabel.generated.swift | 2 +- 14 files changed, 33 insertions(+), 34 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FormCells/StepperViewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FormCells/StepperViewExample.swift index f9a08ea88..826d9ab33 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/FormCells/StepperViewExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/FormCells/StepperViewExample.swift @@ -32,7 +32,6 @@ struct StepperViewExample: View { StepperView( title: { Text("Value") }, text: self.$normalStepValue, - step: 1, stepRange: 0 ... 100, description: { Text("Hint Text") } ) @@ -41,8 +40,7 @@ struct StepperViewExample: View { StepperView( title: { Text("Value") }, text: self.$negativeValue, - step: 1, - stepRange: 0 ... 100, + stepRange: 10 ... 100, description: { Text(self.isInputValueValid ? "Hint Text" : "Validation failed.") } ).onChange(of: self.negativeValue, perform: { value in if Int(value) ?? 1 > 80 { @@ -55,7 +53,7 @@ struct StepperViewExample: View { .disabled(self.isDisabled) StepperView( - title: { Text("loooooooooooooooooooooongTitle") }, + title: { Text("loooooooooooooooooooooooooooooooooooongTitle") }, text: self.$longTitleStepValue, step: 3, stepRange: 0 ... 100, diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index e3c1cef86..e10a22d66 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -132,7 +132,8 @@ protocol _JouleWelcomeScreen: _MediaImageComponent, _GreetingTextComponent, _Tit // sourcery: importFrameworks = ["FioriThemeManager"] protocol _StepperFieldComponent: _DecrementActionComponent, _TextInputFieldComponent, _IncrementActionComponent { /// The step value - var step: Int? { get } + // sourcery: defaultValue = 1 + var step: Int { get } /// a range of values var stepRange: ClosedRange { get } diff --git a/Sources/FioriSwiftUICore/_FioriStyles/StepperFieldStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/StepperFieldStyle.fiori.swift index dc11ee9ca..b2ae121f1 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/StepperFieldStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/StepperFieldStyle.fiori.swift @@ -20,8 +20,8 @@ public struct StepperFieldBaseStyle: StepperFieldStyle { HStack(spacing: 0) { configuration.decrementAction .onSimultaneousTapGesture { - if let stepValue = configuration.step, var currentTextValue = Int(configuration.text) { - currentTextValue -= stepValue + if var currentTextValue = Int(configuration.text) { + currentTextValue -= configuration.step currentTextValue = currentTextValue < configuration.stepRange.lowerBound ? configuration.stepRange.lowerBound : currentTextValue configuration.text = String(currentTextValue) } @@ -38,8 +38,8 @@ public struct StepperFieldBaseStyle: StepperFieldStyle { } configuration.incrementAction .onSimultaneousTapGesture { - if let stepValue = configuration.step, var currentTextValue = Int(configuration.text) { - currentTextValue += stepValue + if var currentTextValue = Int(configuration.text) { + currentTextValue += configuration.step currentTextValue = currentTextValue > configuration.stepRange.upperBound ? configuration.stepRange.upperBound : currentTextValue configuration.text = String(currentTextValue) } @@ -64,9 +64,9 @@ extension StepperFieldFioriStyle { @Environment(\.colorScheme) var colorScheme func makeBody(_ configuration: DecrementActionConfiguration) -> some View { - @State var isDecrementBtnEnabled: Bool = self.isEnabled ? Int(self.stepperFieldConfiguration.text) ?? self.stepperFieldConfiguration.stepRange.lowerBound > self.stepperFieldConfiguration.stepRange.lowerBound ? true : false : false + let isDecrementBtnEnabled: Bool = self.isEnabled ? Int(self.stepperFieldConfiguration.text) ?? self.stepperFieldConfiguration.stepRange.lowerBound > self.stepperFieldConfiguration.stepRange.lowerBound ? true : false : false let decrementDescFormat = NSLocalizedString("Decrease the value by %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") - let decrementDesc = String(format: decrementDescFormat, stepperFieldConfiguration.step ?? 1) + let decrementDesc = String(format: decrementDescFormat, stepperFieldConfiguration.step) return DecrementAction(configuration) .foregroundColor(.preferredColor(.tintColor)) .frame(minWidth: 44, minHeight: 44) @@ -93,9 +93,9 @@ extension StepperFieldFioriStyle { @Environment(\.colorScheme) var colorScheme func makeBody(_ configuration: IncrementActionConfiguration) -> some View { - @State var isIncrementBtnEnabled: Bool = self.isEnabled ? Int(self.stepperFieldConfiguration.text) ?? self.stepperFieldConfiguration.stepRange.upperBound < self.stepperFieldConfiguration.stepRange.upperBound ? true : false : false + let isIncrementBtnEnabled: Bool = self.isEnabled ? Int(self.stepperFieldConfiguration.text) ?? self.stepperFieldConfiguration.stepRange.upperBound < self.stepperFieldConfiguration.stepRange.upperBound ? true : false : false let incrementDescFormat = NSLocalizedString("Increase the value by %d", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") - let incrementDesc = String(format: incrementDescFormat, stepperFieldConfiguration.step ?? 1) + let incrementDesc = String(format: incrementDescFormat, stepperFieldConfiguration.step) return IncrementAction(configuration) .foregroundColor(.preferredColor(.tintColor)) .frame(minWidth: 44, minHeight: 44) diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStack.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStack.generated.swift index 6f967cc69..29d8be456 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStack.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarStack/AvatarStack.generated.swift @@ -14,8 +14,8 @@ public struct AvatarStack { public init(@AvatarsBuilder avatars: () -> any View = { EmptyView() }, @ViewBuilder avatarsTitle: () -> any View = { EmptyView() }) { - self.avatars = Avatars { avatars() } - self.avatarsTitle = AvatarsTitle { avatarsTitle() } + self.avatars = Avatars(avatars: avatars) + self.avatarsTitle = AvatarsTitle(avatarsTitle: avatarsTitle) } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitle.generated.swift index ac1d53feb..3de12bae3 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/AvatarsTitle/AvatarsTitle.generated.swift @@ -55,7 +55,7 @@ private extension AvatarsTitle { } func defaultStyle() -> some View { - AvatarsTitle(avatarsTitle: { self.avatarsTitle }) + AvatarsTitle(.init(avatarsTitle: .init(self.avatarsTitle))) .shouldApplyDefaultStyle(false) .avatarsTitleStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift index 84a970108..8897e2004 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/DateTimePicker/DateTimePicker.generated.swift @@ -18,8 +18,8 @@ public struct DateTimePicker { selectedDate: Binding, pickerComponents: DatePicker.Components = [.date, .hourAndMinute]) { - self.title = Title { title() } - self.valueLabel = ValueLabel { valueLabel() } + self.title = Title(title: title) + self.valueLabel = ValueLabel(valueLabel: valueLabel) self._selectedDate = selectedDate self.pickerComponents = pickerComponents } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitle.generated.swift index 61fe127d6..4a88ec93d 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/OptionalTitle/OptionalTitle.generated.swift @@ -55,7 +55,7 @@ private extension OptionalTitle { } func defaultStyle() -> some View { - OptionalTitle(optionalTitle: { self.optionalTitle }) + OptionalTitle(.init(optionalTitle: .init(self.optionalTitle))) .shouldApplyDefaultStyle(false) .optionalTitleStyle(.fiori) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift index 5664540e7..5dd1e6204 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift @@ -10,7 +10,7 @@ public struct StepperField { @Binding var text: String let incrementAction: any View /// The step value - let step: Int? + let step: Int /// a range of values let stepRange: ClosedRange @@ -21,7 +21,7 @@ public struct StepperField { public init(@ViewBuilder decrementAction: () -> any View = { FioriButton { _ in FioriIcon.actions.less } }, text: Binding, @ViewBuilder incrementAction: () -> any View = { FioriButton { _ in FioriIcon.actions.add } }, - step: Int? = nil, + step: Int = 1, stepRange: ClosedRange) { self.decrementAction = DecrementAction(decrementAction: decrementAction) @@ -36,7 +36,7 @@ public extension StepperField { init(decrementAction: FioriButton? = FioriButton { _ in FioriIcon.actions.less }, text: Binding, incrementAction: FioriButton? = FioriButton { _ in FioriIcon.actions.add }, - step: Int? = nil, + step: Int = 1, stepRange: ClosedRange) { self.init(decrementAction: { decrementAction }, text: text, incrementAction: { incrementAction }, step: step, stepRange: stepRange) diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperFieldStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperFieldStyle.generated.swift index c4ef44a39..e2c01c874 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperFieldStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperFieldStyle.generated.swift @@ -25,7 +25,7 @@ public struct StepperFieldConfiguration { public let decrementAction: DecrementAction @Binding public var text: String public let incrementAction: IncrementAction - public let step: Int? + public let step: Int public let stepRange: ClosedRange public typealias DecrementAction = ConfigurationViewWrapper diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift index b1cbda8ed..5411751e3 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift @@ -11,7 +11,7 @@ public struct StepperView { @Binding var text: String let incrementAction: any View /// The step value - let step: Int? + let step: Int /// a range of values let stepRange: ClosedRange let icon: any View @@ -25,7 +25,7 @@ public struct StepperView { @ViewBuilder decrementAction: () -> any View = { FioriButton { _ in FioriIcon.actions.less } }, text: Binding, @ViewBuilder incrementAction: () -> any View = { FioriButton { _ in FioriIcon.actions.add } }, - step: Int? = nil, + step: Int = 1, stepRange: ClosedRange, @ViewBuilder icon: () -> any View = { EmptyView() }, @ViewBuilder description: () -> any View = { EmptyView() }) @@ -46,7 +46,7 @@ public extension StepperView { decrementAction: FioriButton? = FioriButton { _ in FioriIcon.actions.less }, text: Binding, incrementAction: FioriButton? = FioriButton { _ in FioriIcon.actions.add }, - step: Int? = nil, + step: Int = 1, stepRange: ClosedRange, icon: Image? = nil, description: AttributedString? = nil) diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperViewStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperViewStyle.generated.swift index 63458e05f..14412d789 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperViewStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperViewStyle.generated.swift @@ -26,7 +26,7 @@ public struct StepperViewConfiguration { public let decrementAction: DecrementAction @Binding public var text: String public let incrementAction: IncrementAction - public let step: Int? + public let step: Int public let stepRange: ClosedRange public let icon: Icon public let description: Description diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreview.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreview.generated.swift index d5ec8bcd9..f20fefcfd 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreview.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreview/TimelinePreview.generated.swift @@ -55,8 +55,8 @@ public struct TimelinePreview { @ViewBuilder action: () -> any View = { EmptyView() }, items: Binding<[any TimelinePreviewItemModel]>) { - self.optionalTitle = OptionalTitle { optionalTitle() } - self.action = Action { action() } + self.optionalTitle = OptionalTitle(optionalTitle: optionalTitle) + self.action = Action(action: action) self._items = items } } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItem.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItem.generated.swift index 47138f117..f9750dd0f 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItem.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/TimelinePreviewItem/TimelinePreviewItem.generated.swift @@ -25,10 +25,10 @@ public struct TimelinePreviewItem { isFuture: Bool = false, nodeType: TimelineNodeType) { - self.title = Title { title() } - self.icon = Icon { icon() } - self.timelineNode = TimelineNode { timelineNode() } - self.timestamp = Timestamp { timestamp() } + self.title = Title(title: title) + self.icon = Icon(icon: icon) + self.timelineNode = TimelineNode(timelineNode: timelineNode) + self.timestamp = Timestamp(timestamp: timestamp) self.isFuture = isFuture self.nodeType = nodeType } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabel.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabel.generated.swift index 0d7425818..eef614f2d 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabel.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/ValueLabel/ValueLabel.generated.swift @@ -55,7 +55,7 @@ private extension ValueLabel { } func defaultStyle() -> some View { - ValueLabel(valueLabel: { self.valueLabel }) + ValueLabel(.init(valueLabel: .init(self.valueLabel))) .shouldApplyDefaultStyle(false) .valueLabelStyle(.fiori) .typeErased