From 5b299439c3111d468d639ff04f6f68d6d7376bca Mon Sep 17 00:00:00 2001 From: "Gu, Jiajun (external - Project)" Date: Fri, 27 Sep 2024 13:44:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20[JIRA:IOSSDKBUG-347]=20F?= =?UTF-8?q?ilterFeedbackBar=20picker=20enhance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SortFilter/SortFilterExample.swift | 2 +- .../DataTypes/SortFilter+DataType.swift | 4 +- .../Models/ModelDefinitions.swift | 4 + .../Views/FilterFeedbackBarButton+View.swift | 4 +- .../Views/OptionListPickerItem+View.swift | 77 ++++++++++++++++++- .../FilterFeedbackBarItem+View.swift | 2 +- .../OptionListPickerItem+API.generated.swift | 6 +- ...PickerItemModel+Extensions.generated.swift | 6 +- 8 files changed, 97 insertions(+), 8 deletions(-) diff --git a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift index 55f1234e3..46c7ca5ca 100644 --- a/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift +++ b/Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift @@ -6,7 +6,7 @@ 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", "Accepted", "Rejected"], allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock"), showsOnFilterFeedbackBar: true) + .picker(item: .init(name: "JIRA Status", value: [0], valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review Pending Pending Pending Pending Pending", "Accepted Medium", "Rejected"], allowsMultipleSelection: true, allowsEmptySelection: true, icon: "clock", itemLayout: .flexible), showsOnFilterFeedbackBar: true) ], [ .picker(item: .init(name: "Priority", value: [0], valueOptions: ["High", "Medium", "Low"], allowsMultipleSelection: true, allowsEmptySelection: true, icon: "filemenu.and.cursorarrow"), showsOnFilterFeedbackBar: true), diff --git a/Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift b/Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift index ce1c670ac..48fd5df04 100644 --- a/Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift +++ b/Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift @@ -310,8 +310,9 @@ public extension SortFilterItem { public let allowsMultipleSelection: Bool public let allowsEmptySelection: Bool public let icon: String? + public var itemLayout: OptionListPickerItemLayoutType = .fixed - public init(id: String = UUID().uuidString, name: String, value: [Int], valueOptions: [String], allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil) { + public init(id: String = UUID().uuidString, name: String, value: [Int], valueOptions: [String], allowsMultipleSelection: Bool, allowsEmptySelection: Bool, icon: String? = nil, itemLayout: OptionListPickerItemLayoutType = .fixed) { self.id = id self.name = name self.value = value @@ -321,6 +322,7 @@ public extension SortFilterItem { self.allowsMultipleSelection = allowsMultipleSelection self.allowsEmptySelection = allowsEmptySelection self.icon = icon + self.itemLayout = itemLayout } mutating func onTap(option: String) { diff --git a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift index e9e921007..ae53ac967 100644 --- a/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift +++ b/Sources/FioriSwiftUICore/Models/ModelDefinitions.swift @@ -512,6 +512,10 @@ public protocol FilterFeedbackBarButtonModel: LeftIconComponent, TitleComponent // sourcery: add_env_props = "filterFeedbackBarStyle" // sourcery: generated_component_not_configurable public protocol OptionListPickerItemModel: OptionListPickerComponent { + // sourcery: default.value = .fixed + // sourcery: no_view + var itemLayout: OptionListPickerItemLayoutType { get set } + // sourcery: default.value = nil // sourcery: no_view var onTap: ((_ index: Int) -> Void)? { get } diff --git a/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift b/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift index f57e38b6b..ed27cee40 100644 --- a/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift +++ b/Sources/FioriSwiftUICore/Views/FilterFeedbackBarButton+View.swift @@ -68,7 +68,7 @@ public struct DefaultOptionListPickerStyle: OptionListPickerStyle { let minTouchHeight: CGFloat /// :nodoc: - public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.primaryFill), fillColorUnselected: Color = .preferredColor(.secondaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 16, spacing: CGFloat = 6, borderWidth: CGFloat = 1, minHeight: CGFloat = 44, minTouchHeight: CGFloat = 56) { + public init(font: Font = .system(.body), foregroundColorSelected: Color = .preferredColor(.tintColor), foregroundColorUnselected: Color = .preferredColor(.tertiaryLabel), fillColorSelected: Color = .preferredColor(.secondaryGroupedBackground), fillColorUnselected: Color = .preferredColor(.tertiaryFill), strokeColorSelected: Color = .preferredColor(.tintColor), strokeColorUnselected: Color = .preferredColor(.separator), cornerRadius: CGFloat = 16, spacing: CGFloat = 4, borderWidth: CGFloat = 1, minHeight: CGFloat = 44, minTouchHeight: CGFloat = 50) { self.font = font self.foregroundColorSelected = foregroundColorSelected self.foregroundColorUnselected = foregroundColorUnselected @@ -89,7 +89,9 @@ public struct DefaultOptionListPickerStyle: OptionListPickerStyle { HStack(spacing: self.spacing) { configuration.leftIcon configuration.title + .lineLimit(1) } + .padding([.leading, .trailing], configuration.isSelected ? 9 : 20) .font(self.font) .foregroundColor(configuration.isSelected ? self.foregroundColorSelected : self.foregroundColorUnselected) .frame(maxWidth: .infinity, minHeight: self.minHeight) diff --git a/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift b/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift index 4ac261ec9..19a9ea8b3 100644 --- a/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/OptionListPickerItem+View.swift @@ -1,7 +1,23 @@ import SwiftUI +/// Available OptionListPickerItem layout types. Use this enum to define item layout type to present. +public enum OptionListPickerItemLayoutType { + /// Fixed width + case fixed + /// Column width will be flexible + case flexible +} + extension OptionListPickerItem: View { public var body: some View { + if _itemLayout == .flexible { + self.generateFlexibleContent() + } else { + self.generateFixedContent() + } + } + + private func generateFixedContent() -> some View { Grid(horizontalSpacing: 16) { ForEach(0 ..< Int(ceil(Double(_valueOptions.count) / 2.0)), id: \.self) { rowIndex in GridRow { @@ -27,6 +43,21 @@ extension OptionListPickerItem: View { } } } + + private func generateFlexibleContent() -> some View { + OptionListPickerCustomLayout { + ForEach(0 ..< _valueOptions.count, id: \.self) { optionIndex in + FilterFeedbackBarButton( + leftIcon: _value.wrappedValue.contains(optionIndex) ? Image(systemName: "checkmark") : nil, + title: _valueOptions[optionIndex], + isSelected: _value.wrappedValue.contains(optionIndex) + ) + .onTapGesture { + _onTap?(optionIndex) + } + } + } + } } /* @@ -43,7 +74,7 @@ extension OptionListPickerItem: View { #Preview { VStack { Spacer() - OptionListPickerItem(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) + OptionListPickerItem(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review review", "Accepted", "Rejected"], hint: nil) .frame(width: 375) Spacer() OptionListPickerItem(value: Binding<[Int]>(get: { [0, 1, 2] }, set: { print($0) }), valueOptions: ["Received", "Started", "Hold", "Transfer", "Completed", "Pending Review", "Accepted", "Rejected"], hint: nil) @@ -52,3 +83,47 @@ extension OptionListPickerItem: View { Spacer() } } + +struct OptionListPickerCustomLayout: Layout { + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { + guard let containerWidth = proposal.width else { + return .zero + } + var containerHeight = 0.0 + var currentRowX = 16.0 + for index in 0 ..< subviews.count { + let subview = subviews[index] + let subviewSize = subview.sizeThatFits(.unspecified) + let subviewWidth = min(subviewSize.width, containerWidth) + if index == 0 { + containerHeight += subviewSize.height + } + if currentRowX + subviewWidth + 16.0 > containerWidth { + containerHeight += subviewSize.height + containerHeight += 6 + currentRowX = 16.0 + } + currentRowX += subviewWidth + 6.0 + } + return CGSize(width: containerWidth, height: containerHeight) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { + guard let containerWidth = proposal.width else { return } + var currentY: CGFloat = bounds.minY + var currentRowX = 16.0 + for subview in subviews { + let subviewSize = subview.sizeThatFits(.unspecified) + let subviewWidth = min(subviewSize.width, containerWidth) + if currentRowX + subviewWidth + 16.0 > containerWidth { + currentY += subviewSize.height + currentY += 6 + currentRowX = 16.0 + subview.place(at: CGPoint(x: currentRowX, y: currentY), proposal: ProposedViewSize(width: subviewWidth, height: subviewSize.height)) + } else { + subview.place(at: CGPoint(x: currentRowX, y: currentY), proposal: ProposedViewSize(width: subviewWidth, height: subviewSize.height)) + } + currentRowX += subviewWidth + 6.0 + } + } +} diff --git a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift index 58313b856..0031060f1 100644 --- a/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift +++ b/Sources/FioriSwiftUICore/Views/SortFilter/FilterFeedbackBarItem+View.swift @@ -165,7 +165,7 @@ struct PickerMenuItem: View { }) .buttonStyle(ApplyButtonStyle()) } components: { - OptionListPickerItem(value: self.$item.workingValue, valueOptions: self.item.valueOptions, hint: nil) { index in + OptionListPickerItem(value: self.$item.workingValue, valueOptions: self.item.valueOptions, hint: nil, itemLayout: self.item.itemLayout) { index in self.item.onTap(option: self.item.valueOptions[index]) } .padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16) diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPickerItem+API.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPickerItem+API.generated.swift index 029d2c949..a3e3bd04b 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPickerItem+API.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/API/OptionListPickerItem+API.generated.swift @@ -8,16 +8,18 @@ public struct OptionListPickerItem { var _value: Binding<[Int]> var _valueOptions: [String] var _hint: String? = nil + var _itemLayout: OptionListPickerItemLayoutType var _onTap: ((_ index: Int) -> Void)? = nil public init(model: OptionListPickerItemModel) { - self.init(value: Binding<[Int]>(get: { model.value }, set: { model.value = $0 }), valueOptions: model.valueOptions, hint: model.hint, onTap: model.onTap) + self.init(value: Binding<[Int]>(get: { model.value }, set: { model.value = $0 }), valueOptions: model.valueOptions, hint: model.hint, itemLayout: model.itemLayout, onTap: model.onTap) } - public init(value: Binding<[Int]>, valueOptions: [String] = [], hint: String? = nil, onTap: ((_ index: Int) -> Void)? = nil) { + public init(value: Binding<[Int]>, valueOptions: [String] = [], hint: String? = nil, itemLayout: OptionListPickerItemLayoutType = .fixed, onTap: ((_ index: Int) -> Void)? = nil) { self._value = value self._valueOptions = valueOptions self._hint = hint + self._itemLayout = itemLayout self._onTap = onTap } } diff --git a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerItemModel+Extensions.generated.swift b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerItemModel+Extensions.generated.swift index 5382f2628..0ccb053d8 100644 --- a/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerItemModel+Extensions.generated.swift +++ b/Sources/FioriSwiftUICore/_generated/ViewModels/Model+Extensions/OptionListPickerItemModel+Extensions.generated.swift @@ -3,7 +3,11 @@ import SwiftUI public extension OptionListPickerItemModel { - var onTap: ((_ index: Int) -> Void)? { + var itemLayout: OptionListPickerItemLayoutType { + return .fixed + } + + var onTap: ((_ index: Int) -> Void)? { return nil } }