From a51ee1894f15d326bc0be54671b35373059018d1 Mon Sep 17 00:00:00 2001 From: hengyi-zhang Date: Mon, 28 Oct 2024 22:57:44 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[JIRA:IOSSDKBUG-418]?= =?UTF-8?q?=20Fix=20FilterFeedbackBar=20layout=20(#837)=20(#840)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 [JIRA:IOSSDKBUG-418] Fix FilterFeedbackBar layout * fix: 🐛 [JIRA:IOSSDKBUG-418] Fix FilterFeedbackBar layout --------- Co-authored-by: dyongxu <61523257+dyongxu@users.noreply.github.com> --- .../Components/CancellableResettableForm.swift | 4 ++-- .../Views/SearchListPickerItem+View.swift | 3 --- .../Views/SortFilter/FilterFeedbackBarItem+View.swift | 8 ++++---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift index c34e34a8d..c17ce5436 100644 --- a/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift +++ b/Sources/FioriSwiftUICore/Components/CancellableResettableForm.swift @@ -37,7 +37,7 @@ struct CancellableResettableDialogForm, onUpdate: @escaping () -> Void) { self._item = item @@ -252,14 +252,14 @@ struct PickerMenuItem: View { } selectAll: { isAll in self.item.selectAll(isAll) } updateSearchListPickerHeight: { height in - self.detentHeight = height + 52 + 56 + 70 + self.detentHeight = height } + .frame(maxHeight: UIDevice.current.userInterfaceIdiom != .phone ? (self.detentHeight) : nil) .padding(0) Spacer() } - .frame(maxWidth: .infinity) .frame(minWidth: UIDevice.current.userInterfaceIdiom != .phone ? 393 : nil) - .frame(height: UIDevice.current.userInterfaceIdiom != .phone ? self.detentHeight : nil) + .frame(height: UIDevice.current.userInterfaceIdiom != .phone ? self.detentHeight + 52 + 56 + 70 : nil) .presentationDetents([.large]) } } From ee52618218f25454aa8d9b9aa5e62977859a8cae Mon Sep 17 00:00:00 2001 From: restaurantt Date: Mon, 28 Oct 2024 23:32:27 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[IOSSDKBUG-416]Filter?= =?UTF-8?q?FeedbackBar=20on=20iPad=20layout=20(#839)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 [IOSSDKBUG-416]FilterFeedbackBar on iPad layout * fix: 🐛 [IOSSDKBUG-416]FilterFeedbackBar on iPad layout --------- Co-authored-by: dyongxu <61523257+dyongxu@users.noreply.github.com> --- .../SortFilter/SortFilterExample.swift | 6 +++++- .../Views/OptionListPickerItem+View.swift | 17 ++++++++++++++-- .../Views/SearchListPickerItem+View.swift | 20 ++++++++++++++++--- .../FilterFeedbackBarItem+View.swift | 10 +++++----- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift index d0c8290be..0f7252b8a 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -6,7 +6,11 @@ struct SortFilterExample: View { [ .switch(item: .init(name: "Favorite", value: true, icon: "heart.fill"), showsOnFilterFeedbackBar: true), .switch(item: .init(name: "Tagged", value: nil, icon: "tag"), showsOnFilterFeedbackBar: false), - .picker(item: .init(name: "JIRA Status", value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review Pending Pending Pending Pending Pending", "Accepted Medium", "Pending Medium", "Completed Medium"], allowsMultipleSelection: true, allowsEmptySelection: true, showsValueForSingleSelected: false, icon: "clock", itemLayout: .fixed, displayMode: .automatic), showsOnFilterFeedbackBar: true) + .picker(item: .init(name: "List Few JIRA Status", value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review Pending Pending Pending Pending Pending", "Accepted Medium", "Pending Medium", "Completed Medium"], allowsMultipleSelection: true, allowsEmptySelection: true, showsValueForSingleSelected: false, icon: "clock", itemLayout: .fixed, displayMode: .list), showsOnFilterFeedbackBar: true), + .picker(item: .init(name: "List Many JIRA Status", value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review Pending Pending Pending Pending Pending", "Accepted Medium", "Pending Medium", "Completed Medium", "Checked", "Unchecked", "Partially Checked", "Checked and Unchecked", "Checked and Partially Checked", "Unchecked and Partially Checked", "Partially Checked and Unchecked", "Checked and Unchecked and Partially Checked", "Unchecked and Partially Checked and Partially Checked", "Partially Checked and Unchecked and Partially Checked", "Checked Finally", "Unchecked Finally", "Partially Checked Finally", "Checked and Unchecked Finally", "Checked and Partially Checked Finally", "Unchecked and Partially Checked Finally", "Partially Checked and Unchecked Finally", "Checked Finally and Partially Checked Finally", "Unchecked Finally and Partially Checked Finally", "Partially Checked Finally and Partially Checked Finally", "Review", "Reviewed", "To be Reviewed", "Pending for Review", "Booked", "To be Booked", "Will Book", "Booking Canceled"], allowsMultipleSelection: true, allowsEmptySelection: true, showsValueForSingleSelected: false, icon: "clock", itemLayout: .fixed, displayMode: .list), showsOnFilterFeedbackBar: true), + .picker(item: .init(name: "FormCell Flexible Filter Selection", value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review Pending Pending Pending Pending Pending", "Accepted Medium", "Pending", "Completed Medium"], allowsMultipleSelection: true, allowsEmptySelection: true, showsValueForSingleSelected: false, icon: "clock", itemLayout: .flexible, displayMode: .filterFormCell), showsOnFilterFeedbackBar: true), + .picker(item: .init(name: "FormCell Fixed Few Filter Selection", value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review Pending Pending Pending Pending Pending"], allowsMultipleSelection: true, allowsEmptySelection: true, showsValueForSingleSelected: false, icon: "clock", itemLayout: .fixed, displayMode: .filterFormCell), showsOnFilterFeedbackBar: true), + .picker(item: .init(name: "FormCell Fixed Many Filter Selection", value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review Pending Pending Pending Pending Pending", "Accepted Medium", "Pending Medium", "Completed Medium", "Checked", "Unchecked", "Partially Checked", "Checked and Unchecked", "Checked and Partially Checked", "Unchecked and Partially Checked", "Partially Checked and Unchecked", "Checked and Unchecked and Partially Checked", "Unchecked and Partially Checked and Partially Checked", "Partially Checked and Unchecked and Partially Checked", "Checked Finally", "Unchecked Finally", "Partially Checked Finally", "Checked and Unchecked Finally", "Checked and Partially Checked Finally", "Unchecked and Partially Checked Finally", "Partially Checked and Unchecked Finally", "Checked Finally and Partially Checked Finally", "Unchecked Finally and Partially Checked Finally", "Partially Checked Finally and Partially Checked Finally", "Review", "Reviewed", "To be Reviewed", "Pending for Review", "Booked", "To be Booked", "Will Book", "Booking Canceled"], allowsMultipleSelection: true, allowsEmptySelection: true, showsValueForSingleSelected: false, icon: "clock", itemLayout: .fixed, displayMode: .filterFormCell), showsOnFilterFeedbackBar: true) ], [ .picker(item: .init(name: "Priority", value: [0], valueOptions: ["High", "Medium", "Low"], allowsMultipleSelection: true, allowsEmptySelection: true, showsValueForSingleSelected: false, icon: "filemenu.and.cursorarrow"), showsOnFilterFeedbackBar: true), diff --git a/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift index 3b7a2a749..def4b1f01 100644 --- a/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift @@ -64,7 +64,8 @@ extension OptionListPickerItem: View { let popverHeight = Screen.bounds.size.height - StatusBar.height let totalSpacing: CGFloat = (UIDevice.current.userInterfaceIdiom == .pad ? 8 : 16) * 2 let totalPadding: CGFloat = (UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) * 2 - let maxScrollViewHeight = popverHeight - totalSpacing - totalPadding - 120 + let safeAreaInset = self.getSafeAreaInsets() + let maxScrollViewHeight = popverHeight - totalSpacing - totalPadding - safeAreaInset.top - safeAreaInset.bottom - (UIDevice.current.userInterfaceIdiom == .pad ? 210 : 30) self._height = min(geometry.size.height, maxScrollViewHeight) } } @@ -94,7 +95,8 @@ extension OptionListPickerItem: View { let popverHeight = Screen.bounds.size.height - StatusBar.height let totalSpacing: CGFloat = (UIDevice.current.userInterfaceIdiom == .pad ? 8 : 16) * 2 let totalPadding: CGFloat = (UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) * 2 - let maxScrollViewHeight = popverHeight - totalSpacing - totalPadding - 120 + let safeAreaInset = self.getSafeAreaInsets() + let maxScrollViewHeight = popverHeight - totalSpacing - totalPadding - safeAreaInset.top - safeAreaInset.bottom - (UIDevice.current.userInterfaceIdiom == .pad ? 210 : 30) self._height = min(geometry.size.height, maxScrollViewHeight) } } @@ -102,6 +104,17 @@ extension OptionListPickerItem: View { } .frame(height: _height) } + + private func getSafeAreaInsets() -> UIEdgeInsets { + guard let keyWindow = UIApplication.shared.connectedScenes + .first(where: { $0.activationState == .foregroundActive }) + .flatMap({ $0 as? UIWindowScene })?.windows + .first(where: \.isKeyWindow) + else { + return .zero + } + return keyWindow.safeAreaInsets + } } /* diff --git a/Sources/FioriSwiftUICore/Views/SearchListPickerItem+View.swift b/Sources/FioriSwiftUICore/Views/SearchListPickerItem+View.swift index 173e8aa6d..b3641f804 100644 --- a/Sources/FioriSwiftUICore/Views/SearchListPickerItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SearchListPickerItem+View.swift @@ -59,13 +59,16 @@ extension SearchListPickerItem: View { } } .modifier(FioriIntrospectModifier { scrollView in + if !_searchText.isEmpty { + return + } DispatchQueue.main.async { let popverHeight = Screen.bounds.size.height - StatusBar.height let totalSpacing: CGFloat = (UIDevice.current.userInterfaceIdiom == .pad ? 8 : 16) * 2 let totalPadding: CGFloat = (UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) * 2 - let maxScrollViewHeight = popverHeight - totalSpacing - totalPadding - 52 - 56 - 120 - - self._height = UIDevice.current.userInterfaceIdiom != .phone ? min(scrollView.contentSize.height, 396) : min(min(scrollView.contentSize.height, maxScrollViewHeight), 396) + let safeAreaInset = self.getSafeAreaInsets() + let maxScrollViewHeight = popverHeight - totalSpacing - totalPadding - 52 - 56 - safeAreaInset.top - safeAreaInset.bottom - (UIDevice.current.userInterfaceIdiom == .pad ? 230 : 30) + self._height = min(scrollView.contentSize.height, maxScrollViewHeight) var isSelectAllViewShow = false if allowsMultipleSelection { if _value.count != _valueOptions.count || allowsEmptySelection { @@ -111,6 +114,17 @@ extension SearchListPickerItem: View { private func findIndex(of item: String) -> Int? { _valueOptions.firstIndex(where: { $0 == item }) } + + private func getSafeAreaInsets() -> UIEdgeInsets { + guard let keyWindow = UIApplication.shared.connectedScenes + .first(where: { $0.activationState == .foregroundActive }) + .flatMap({ $0 as? UIWindowScene })?.windows + .first(where: \.isKeyWindow) + else { + return .zero + } + return keyWindow.safeAreaInsets + } } #Preview { diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift index c26598598..e0e5ab1b2 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift @@ -77,7 +77,7 @@ struct SliderMenuItem: View { .onTapGesture { self.isSheetVisible.toggle() } - .popover(isPresented: self.$isSheetVisible, attachmentAnchor: .point(.bottom)) { + .popover(isPresented: self.$isSheetVisible) { CancellableResettableDialogForm { SortFilterItemTitle(title: self.item.name) } cancelAction: { @@ -153,7 +153,7 @@ struct PickerMenuItem: View { .onTapGesture { self.isSheetVisible.toggle() } - .popover(isPresented: self.$isSheetVisible, attachmentAnchor: .point(.bottom)) { + .popover(isPresented: self.$isSheetVisible) { CancellableResettableDialogForm { SortFilterItemTitle(title: self.item.name) } cancelAction: { @@ -224,7 +224,7 @@ struct PickerMenuItem: View { .onTapGesture { self.isSheetVisible.toggle() } - .popover(isPresented: self.$isSheetVisible, attachmentAnchor: .point(.bottom)) { + .popover(isPresented: self.$isSheetVisible) { CancellableResettableDialogNavigationForm { SortFilterItemTitle(title: self.item.name) } cancelAction: { @@ -311,7 +311,7 @@ struct DateTimeMenuItem: View { .onTapGesture { self.isSheetVisible.toggle() } - .popover(isPresented: self.$isSheetVisible, attachmentAnchor: .point(.bottom)) { + .popover(isPresented: self.$isSheetVisible) { CancellableResettableDialogForm { SortFilterItemTitle(title: self.item.name) } cancelAction: { @@ -448,7 +448,7 @@ struct FullCFGMenuItem: View { .onTapGesture { self.isSheetVisible.toggle() } - .popover(isPresented: self.$isSheetVisible, attachmentAnchor: .point(.bottom)) { + .popover(isPresented: self.$isSheetVisible) { SortFilterView( title: { if let title = fullCFGButton.name { From d1b1c04ce6da24edecb1fce6245cefcea843e749 Mon Sep 17 00:00:00 2001 From: xiaoqinggrace <52239714+xiaoqinggrace@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:54:13 -0700 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=F0=9F=90=9B=20IOSSDKBUG-425=20Fiori?= =?UTF-8?q?=20FilterFeedbackBar=20items=20rendering=20(#845)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Closes: IOSSDKBUG-425 Co-authored-by: I824136 --- .../Views/SortFilter/FilterFeedbackBarItem+Style.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift index 3f6df1abc..4d17bba6c 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+Style.swift @@ -72,7 +72,7 @@ public struct DefaultFilterFeedbackBarStyle: FilterFeedbackBarStyle { RoundedRectangle(cornerRadius: self.cornerRadius) .fill(configuration.isSelected ? self.fillColorSelected : self.fillColorUnselected) RoundedRectangle(cornerRadius: self.cornerRadius) - .stroke(configuration.isSelected ? self.strokeColorSelected : self.strokeColorUnselected, lineWidth: self.borderWidth) + .strokeBorder(configuration.isSelected ? self.strokeColorSelected : self.strokeColorUnselected, lineWidth: self.borderWidth) } ) ) From b204c532b233ece999506e00d295220a735fbc23 Mon Sep 17 00:00:00 2001 From: restaurantt Date: Tue, 29 Oct 2024 11:25:14 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=F0=9F=90=9B=20[IOSSDKBUG-416]Fix=20?= =?UTF-8?q?FilterFeedbackBar=20layout=20(#847)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/SortFilter/FilterFeedbackBarItem+View.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift index e0e5ab1b2..2fcf07cdd 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift @@ -252,7 +252,7 @@ struct PickerMenuItem: View { } selectAll: { isAll in self.item.selectAll(isAll) } updateSearchListPickerHeight: { height in - self.detentHeight = height + self.detentHeight = max(height, 88) } .frame(maxHeight: UIDevice.current.userInterfaceIdiom != .phone ? (self.detentHeight) : nil) .padding(0) From 359d846bf67f56d47fa87a8f619abf6979b4805f Mon Sep 17 00:00:00 2001 From: zzchao-1999 <149659707+zzchao-1999@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:33:01 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[HCPSDKFIORIUIKIT-27?= =?UTF-8?q?91]SwiftUI:=20floating=20value=20support=20(#834)=20(#849)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 [HCPSDKFIORIUIKIT-2791]SwiftUI: floating value support * feat: 🎸 [HCPSDKFIORIUIKIT-2791]Added a number formatter --- .../FormCells/StepperViewExample.swift | 25 +++++- .../CompositeComponentProtocols.swift | 8 +- .../StepperFieldStyle.fiori.swift | 79 ++++++++++++++----- .../TextInputFieldStyle.fiori.swift | 2 +- .../StepperField/StepperField.generated.swift | 24 +++--- .../StepperFieldStyle.generated.swift | 5 +- .../StepperView/StepperView.generated.swift | 28 ++++--- .../StepperViewStyle.generated.swift | 5 +- ...yleConfiguration+Extension.generated.swift | 2 +- 9 files changed, 131 insertions(+), 47 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/FormCells/StepperViewExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/FormCells/StepperViewExample.swift index 826d9ab33..f53ead3d0 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/FormCells/StepperViewExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/FormCells/StepperViewExample.swift @@ -3,6 +3,8 @@ import SwiftUI struct StepperViewExample: View { @State var normalStepValue = "3" + @State var doubleStepValue = "6.0" + @State var multipleDigits = "6.895" @State var longTitleStepValue = "3" @State var customStyleStepValue = "3" @State var noFocusValue = "79" @@ -37,13 +39,34 @@ struct StepperViewExample: View { ) .disabled(self.isDisabled) + StepperView( + title: { Text("Value") }, + text: self.$doubleStepValue, + step: 0.5, + stepRange: 0.5 ... 80.5, + isDecimalSupported: true, + description: { Text("Double Value") } + ) + .disabled(self.isDisabled) + + StepperView( + title: { Text("Value") }, + text: self.$multipleDigits, + step: 0.005, + stepRange: 0.005 ... 80.895, + isDecimalSupported: true, + description: { Text("Multi-digit Double Value") } + ) + .disabled(self.isDisabled) + StepperView( title: { Text("Value") }, text: self.$negativeValue, stepRange: 10 ... 100, description: { Text(self.isInputValueValid ? "Hint Text" : "Validation failed.") } ).onChange(of: self.negativeValue, perform: { value in - if Int(value) ?? 1 > 80 { + let cValue = Double(value) ?? 10 + if cValue > 80 || cValue < 20 { self.isInputValueValid = false } else { self.isInputValueValid = true diff --git a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift index 912d3d9aa..74c13a93a 100755 --- a/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift +++ b/Sources/FioriSwiftUICore/_ComponentProtocols/CompositeComponentProtocols.swift @@ -133,10 +133,14 @@ protocol _JouleWelcomeScreen: _MediaImageComponent, _GreetingTextComponent, _Tit protocol _StepperFieldComponent: _DecrementActionComponent, _TextInputFieldComponent, _IncrementActionComponent { /// The step value // sourcery: defaultValue = 1 - var step: Int { get } + var step: Double { get } /// a range of values - var stepRange: ClosedRange { get } + var stepRange: ClosedRange { get } + + /// Indicates whether the stepper field supports decimal values. Default is false. + // sourcery: defaultValue = false + var isDecimalSupported: Bool { get } } // sourcery: CompositeComponent diff --git a/Sources/FioriSwiftUICore/_FioriStyles/StepperFieldStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/StepperFieldStyle.fiori.swift index b2ae121f1..eb2febb62 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/StepperFieldStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/StepperFieldStyle.fiori.swift @@ -16,36 +16,79 @@ import SwiftUI // Base Layout style public struct StepperFieldBaseStyle: StepperFieldStyle { + @State private var previousValue: String = "" public func makeBody(_ configuration: StepperFieldConfiguration) -> some View { HStack(spacing: 0) { configuration.decrementAction .onSimultaneousTapGesture { - if var currentTextValue = Int(configuration.text) { - currentTextValue -= configuration.step - currentTextValue = currentTextValue < configuration.stepRange.lowerBound ? configuration.stepRange.lowerBound : currentTextValue - configuration.text = String(currentTextValue) - } + self.adjustValue(by: -configuration.step, configuration: configuration) } configuration._textInputField .textInputFieldStyle(.number) .onChange(of: configuration.text) { newValue in - let value = Int(newValue) - if value ?? 0 > configuration.stepRange.upperBound { - configuration.text = String(configuration.stepRange.upperBound) - } else if value ?? 0 < configuration.stepRange.lowerBound { - configuration.text = String(configuration.stepRange.lowerBound) - } + self.updateText(for: newValue, configuration: configuration) } configuration.incrementAction .onSimultaneousTapGesture { - if var currentTextValue = Int(configuration.text) { - currentTextValue += configuration.step - currentTextValue = currentTextValue > configuration.stepRange.upperBound ? configuration.stepRange.upperBound : currentTextValue - configuration.text = String(currentTextValue) - } + self.adjustValue(by: configuration.step, configuration: configuration) } } } + + private func adjustValue(by step: Double, configuration: StepperFieldConfiguration) { + let currentValue = Double(configuration.text) + let newValue = currentValue.map { $0 + step } ?? 0.0 + let clampedValue = self.clampValue(newValue, configuration: configuration) + if configuration.isDecimalSupported { + configuration.text = String(describing: clampedValue) + } else { + configuration.text = String(describing: Int(clampedValue)) + } + self.previousValue = configuration.text + } + + private func updateText(for text: String, configuration: StepperFieldConfiguration) { + if configuration.isDecimalSupported { + if let doubleValue = Double(text) { + let clampedValue = self.clampValue(doubleValue, configuration: configuration) + let formattedValue = self.numberFormatter(forStep: configuration.step).string(from: NSNumber(value: clampedValue)) ?? "" + configuration.text = formattedValue + } + } else { + if text.contains(".") || text.isEmpty { + configuration.text = self.previousValue + } else if let doubleValue = Double(text) { + let clampedValue = self.clampValue(doubleValue, configuration: configuration) + configuration.text = String(Int(clampedValue)) + } + } + self.previousValue = configuration.text + } + + private func clampValue(_ value: Double, configuration: StepperFieldConfiguration) -> Double { + min(max(value, configuration.stepRange.lowerBound), configuration.stepRange.upperBound) + } + + private func getDecimalPlaces(step: Double) -> Int { + let stepString = String(step) + if let decimalPointIndex = stepString.firstIndex(of: ".") { + let decimalPointPosition = stepString.distance(from: stepString.startIndex, to: decimalPointIndex) + let endPosition = stepString.distance(from: stepString.startIndex, to: stepString.endIndex) + let decimalPlacesCount = endPosition - decimalPointPosition - 1 + return max(0, decimalPlacesCount) + } else { + return 0 + } + } + + private func numberFormatter(forStep step: Double) -> NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + let decimalPlaces = self.getDecimalPlaces(step: step) + formatter.minimumFractionDigits = decimalPlaces + formatter.maximumFractionDigits = decimalPlaces + return formatter + } } // Default fiori styles @@ -64,7 +107,7 @@ extension StepperFieldFioriStyle { @Environment(\.colorScheme) var colorScheme func makeBody(_ configuration: DecrementActionConfiguration) -> some View { - let isDecrementBtnEnabled: Bool = self.isEnabled ? Int(self.stepperFieldConfiguration.text) ?? self.stepperFieldConfiguration.stepRange.lowerBound > self.stepperFieldConfiguration.stepRange.lowerBound ? true : false : false + let isDecrementBtnEnabled: Bool = self.isEnabled ? Double(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) return DecrementAction(configuration) @@ -93,7 +136,7 @@ extension StepperFieldFioriStyle { @Environment(\.colorScheme) var colorScheme func makeBody(_ configuration: IncrementActionConfiguration) -> some View { - let isIncrementBtnEnabled: Bool = self.isEnabled ? Int(self.stepperFieldConfiguration.text) ?? self.stepperFieldConfiguration.stepRange.upperBound < self.stepperFieldConfiguration.stepRange.upperBound ? true : false : false + let isIncrementBtnEnabled: Bool = self.isEnabled ? Double(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) return IncrementAction(configuration) diff --git a/Sources/FioriSwiftUICore/_FioriStyles/TextInputFieldStyle.fiori.swift b/Sources/FioriSwiftUICore/_FioriStyles/TextInputFieldStyle.fiori.swift index 1dbc75710..07f8e83cb 100644 --- a/Sources/FioriSwiftUICore/_FioriStyles/TextInputFieldStyle.fiori.swift +++ b/Sources/FioriSwiftUICore/_FioriStyles/TextInputFieldStyle.fiori.swift @@ -26,7 +26,7 @@ public struct TextInputFieldNumberStyle: TextInputFieldStyle { .frame(minHeight: 44) .keyboardType(.numberPad) .onChange(of: configuration.text) { newValue in - let filtered = newValue.filter(\.isNumber) + let filtered = newValue.filter { $0.isNumber || $0 == "." } if filtered != newValue { configuration.text = filtered } diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift index 6dccd189b..7fd4e464b 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperField.generated.swift @@ -10,9 +10,11 @@ public struct StepperField { @Binding var text: String let incrementAction: any View /// The step value - let step: Int + let step: Double /// a range of values - let stepRange: ClosedRange + let stepRange: ClosedRange + /// Indicates whether the stepper field supports decimal values. Default is false. + let isDecimalSupported: Bool @Environment(\.stepperFieldStyle) var style @@ -21,14 +23,16 @@ 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 = 1, - stepRange: ClosedRange) + step: Double = 1, + stepRange: ClosedRange, + isDecimalSupported: Bool = false) { self.decrementAction = DecrementAction { decrementAction() } self._text = text self.incrementAction = IncrementAction { incrementAction() } self.step = step self.stepRange = stepRange + self.isDecimalSupported = isDecimalSupported } } @@ -36,10 +40,11 @@ public extension StepperField { init(decrementAction: FioriButton? = FioriButton { _ in FioriIcon.actions.less }, text: Binding, incrementAction: FioriButton? = FioriButton { _ in FioriIcon.actions.add }, - step: Int = 1, - stepRange: ClosedRange) + step: Double = 1, + stepRange: ClosedRange, + isDecimalSupported: Bool = false) { - self.init(decrementAction: { decrementAction }, text: text, incrementAction: { incrementAction }, step: step, stepRange: stepRange) + self.init(decrementAction: { decrementAction }, text: text, incrementAction: { incrementAction }, step: step, stepRange: stepRange, isDecimalSupported: isDecimalSupported) } } @@ -54,6 +59,7 @@ public extension StepperField { self.incrementAction = configuration.incrementAction self.step = configuration.step self.stepRange = configuration.stepRange + self.isDecimalSupported = configuration.isDecimalSupported self._shouldApplyDefaultStyle = shouldApplyDefaultStyle } } @@ -63,7 +69,7 @@ extension StepperField: View { if self._shouldApplyDefaultStyle { self.defaultStyle() } else { - self.style.resolve(configuration: .init(decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange)).typeErased + self.style.resolve(configuration: .init(decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange, isDecimalSupported: self.isDecimalSupported)).typeErased .transformEnvironment(\.stepperFieldStyleStack) { stack in if !stack.isEmpty { stack.removeLast() @@ -81,7 +87,7 @@ private extension StepperField { } func defaultStyle() -> some View { - StepperField(.init(decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange)) + StepperField(.init(decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange, isDecimalSupported: self.isDecimalSupported)) .shouldApplyDefaultStyle(false) .stepperFieldStyle(StepperFieldFioriStyle.ContentFioriStyle()) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperFieldStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperFieldStyle.generated.swift index e2c01c874..b0a8dfc77 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperFieldStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperField/StepperFieldStyle.generated.swift @@ -25,8 +25,9 @@ public struct StepperFieldConfiguration { public let decrementAction: DecrementAction @Binding public var text: String public let incrementAction: IncrementAction - public let step: Int - public let stepRange: ClosedRange + public let step: Double + public let stepRange: ClosedRange + public let isDecimalSupported: Bool public typealias DecrementAction = ConfigurationViewWrapper public typealias IncrementAction = ConfigurationViewWrapper diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift index 64469349e..252cc05d8 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperView.generated.swift @@ -11,9 +11,11 @@ public struct StepperView { @Binding var text: String let incrementAction: any View /// The step value - let step: Int + let step: Double /// a range of values - let stepRange: ClosedRange + let stepRange: ClosedRange + /// Indicates whether the stepper field supports decimal values. Default is false. + let isDecimalSupported: Bool let icon: any View let description: any View @@ -25,8 +27,9 @@ 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 = 1, - stepRange: ClosedRange, + step: Double = 1, + stepRange: ClosedRange, + isDecimalSupported: Bool = false, @ViewBuilder icon: () -> any View = { EmptyView() }, @ViewBuilder description: () -> any View = { EmptyView() }) { @@ -36,8 +39,9 @@ public struct StepperView { self.incrementAction = IncrementAction { incrementAction() } self.step = step self.stepRange = stepRange - self.icon = Icon { icon() } - self.description = Description { description() } + self.isDecimalSupported = isDecimalSupported + self.icon = Icon(icon: icon) + self.description = Description(description: description) } } @@ -46,12 +50,13 @@ public extension StepperView { decrementAction: FioriButton? = FioriButton { _ in FioriIcon.actions.less }, text: Binding, incrementAction: FioriButton? = FioriButton { _ in FioriIcon.actions.add }, - step: Int = 1, - stepRange: ClosedRange, + step: Double = 1, + stepRange: ClosedRange, + isDecimalSupported: Bool = false, icon: Image? = nil, description: AttributedString? = nil) { - self.init(title: { Text(title) }, decrementAction: { decrementAction }, text: text, incrementAction: { incrementAction }, step: step, stepRange: stepRange, icon: { icon }, description: { OptionalText(description) }) + self.init(title: { Text(title) }, decrementAction: { decrementAction }, text: text, incrementAction: { incrementAction }, step: step, stepRange: stepRange, isDecimalSupported: isDecimalSupported, icon: { icon }, description: { OptionalText(description) }) } } @@ -67,6 +72,7 @@ public extension StepperView { self.incrementAction = configuration.incrementAction self.step = configuration.step self.stepRange = configuration.stepRange + self.isDecimalSupported = configuration.isDecimalSupported self.icon = configuration.icon self.description = configuration.description self._shouldApplyDefaultStyle = shouldApplyDefaultStyle @@ -78,7 +84,7 @@ extension StepperView: View { if self._shouldApplyDefaultStyle { self.defaultStyle() } else { - self.style.resolve(configuration: .init(title: .init(self.title), decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange, icon: .init(self.icon), description: .init(self.description))).typeErased + self.style.resolve(configuration: .init(title: .init(self.title), decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange, isDecimalSupported: self.isDecimalSupported, icon: .init(self.icon), description: .init(self.description))).typeErased .transformEnvironment(\.stepperViewStyleStack) { stack in if !stack.isEmpty { stack.removeLast() @@ -96,7 +102,7 @@ private extension StepperView { } func defaultStyle() -> some View { - StepperView(.init(title: .init(self.title), decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange, icon: .init(self.icon), description: .init(self.description))) + StepperView(.init(title: .init(self.title), decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange, isDecimalSupported: self.isDecimalSupported, icon: .init(self.icon), description: .init(self.description))) .shouldApplyDefaultStyle(false) .stepperViewStyle(StepperViewFioriStyle.ContentFioriStyle()) .typeErased diff --git a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperViewStyle.generated.swift b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperViewStyle.generated.swift index 14412d789..6ce477eb1 100644 --- a/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperViewStyle.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/StyleableComponents/StepperView/StepperViewStyle.generated.swift @@ -26,8 +26,9 @@ public struct StepperViewConfiguration { public let decrementAction: DecrementAction @Binding public var text: String public let incrementAction: IncrementAction - public let step: Int - public let stepRange: ClosedRange + public let step: Double + public let stepRange: ClosedRange + public let isDecimalSupported: Bool public let icon: Icon public let description: Description diff --git a/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift b/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift index 692361fe9..2d1af5be2 100755 --- a/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/SupportingFiles/StyleConfiguration+Extension.generated.swift @@ -125,7 +125,7 @@ extension StepperFieldConfiguration { extension StepperViewConfiguration { var _stepperField: StepperField { - StepperField(.init(decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange), shouldApplyDefaultStyle: true) + StepperField(.init(decrementAction: .init(self.decrementAction), text: self.$text, incrementAction: .init(self.incrementAction), step: self.step, stepRange: self.stepRange, isDecimalSupported: self.isDecimalSupported), shouldApplyDefaultStyle: true) } var _informationView: InformationView {