Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 🎸 [JIRA:IOSSDKBUG-434] filterFeedBack list mutil selections shown in section(cherrypick) #869

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ public extension SortFilterItem {
public var displayMode: DisplayMode = .automatic
/// If seachBar in list display mode is shown. Default is `false`.
public var isSearchBarHidden: Bool = false
var disableListEntriesSection: Bool = false

/// Available OptionListPicker modes. Use this enum to define picker mode to present.
public enum DisplayMode {
Expand All @@ -390,7 +391,18 @@ public extension SortFilterItem {
case nameAndValue
}

public init(id: String = UUID().uuidString, name: String, value: [Int], valueOptions: [String], allowsMultipleSelection: Bool, allowsEmptySelection: Bool, barItemDisplayMode: BarItemDisplayMode = .name, isSearchBarHidden: Bool = false, icon: String? = nil, itemLayout: OptionListPickerItemLayoutType = .fixed, displayMode: DisplayMode = .automatic) {
/// Enum for list show entries section
/// The default value is `.default`.
public enum ListEntriesSectionMode {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nesting Violation: Types should be nested at most 1 level deep (nesting)

/// Depend on 'allowsMultipleSelection'
case `default`
/// Enable
case enable
/// Disable
case disable
}

public init(id: String = UUID().uuidString, name: String, value: [Int], valueOptions: [String], allowsMultipleSelection: Bool, allowsEmptySelection: Bool, barItemDisplayMode: BarItemDisplayMode = .name, isSearchBarHidden: Bool = false, icon: String? = nil, itemLayout: OptionListPickerItemLayoutType = .fixed, displayMode: DisplayMode = .automatic, listEntriesSectionMode: ListEntriesSectionMode = .default) {
self.id = id
self.name = name
self.value = value
Expand All @@ -404,6 +416,15 @@ public extension SortFilterItem {
self.icon = icon
self.itemLayout = itemLayout
self.displayMode = displayMode

switch listEntriesSectionMode {
case .default:
self.disableListEntriesSection = allowsMultipleSelection ? false : true
case .disable:
self.disableListEntriesSection = true
case .enable:
self.disableListEntriesSection = false
}
}

mutating func onTap(option: String) {
Expand Down
1 change: 1 addition & 0 deletions Sources/FioriSwiftUICore/Models/ModelDefinitions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ public protocol OptionListPickerItemModel: OptionListPickerComponent {
// sourcery: virtualPropIsSearchBarHidden = "var isSearchBarHidden: Bool = false"
// sourcery: virtualPropPopoverWidth = "let popoverWidth = 393.0"
// sourcery: virtualPropKeyboardHeight = "@State var _keyboardHeight: CGFloat = 0.0"
// sourcery: virtualPropDisableListEntriesSection = "var disableListEntriesSection: Bool = false"
public protocol SearchListPickerItemModel: OptionListPickerComponent {
// sourcery: default.value = nil
// sourcery: no_view
Expand Down
136 changes: 99 additions & 37 deletions Sources/FioriSwiftUICore/Views/SearchListPickerItem+View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,67 @@ public extension SearchListPickerItem {
/// - onTap: The closure when tap on item.
/// - selectAll: The closure when click 'Select All' button.
/// - updateSearchListPickerHeight: The closure to update the parent view.
init(value: Binding<[Int]>, valueOptions: [String] = [], hint: String? = nil, allowsMultipleSelection: Bool, allowsEmptySelection: Bool, isSearchBarHidden: Bool = false, onTap: ((_ index: Int) -> Void)? = nil, selectAll: ((_ isAll: Bool) -> Void)? = nil, updateSearchListPickerHeight: ((CGFloat) -> Void)? = nil) {
/// - disableListEntriesSection: A boolean value to indicate to disable entries section or not.
init(value: Binding<[Int]>, valueOptions: [String] = [], hint: String? = nil, allowsMultipleSelection: Bool, allowsEmptySelection: Bool, isSearchBarHidden: Bool = false, disableListEntriesSection: Bool, onTap: ((_ index: Int) -> Void)? = nil, selectAll: ((_ isAll: Bool) -> Void)? = nil, updateSearchListPickerHeight: ((CGFloat) -> Void)? = nil) {
self.init(value: value, valueOptions: valueOptions, hint: hint, onTap: onTap)

self.allowsMultipleSelection = allowsMultipleSelection
self.allowsEmptySelection = allowsEmptySelection
self.isSearchBarHidden = isSearchBarHidden
self.selectAll = selectAll
self.updateSearchListPickerHeight = updateSearchListPickerHeight
self.disableListEntriesSection = disableListEntriesSection
}
}

extension SearchListPickerItem: View {
public var body: some View {
VStack(spacing: 0) {
if allowsMultipleSelection {
if _value.count != _valueOptions.count || allowsEmptySelection {
self.selectAllView()
}
} else if _value.count == _valueOptions.count {
self.selectAllView()
}

Divider().edgesIgnoringSafeArea(.all)
List {
ForEach(_valueOptions.filter { _searchText.isEmpty || $0.localizedStandardContains(_searchText) }, id: \.self) { item in
let isSelected = self.isItemSelected(item)
HStack {
Text(item)
.lineLimit(1)
.foregroundStyle(Color.preferredColor(.primaryLabel))
.font(.fiori(forTextStyle: .body, weight: .regular))
Spacer()
if isSelected {
Image(systemName: "checkmark")
#if !os(visionOS)
.foregroundStyle(Color.preferredColor(.tintColor))
#else
.foregroundStyle(Color.preferredColor(.primaryLabel))
#endif
if !disableListEntriesSection, _value.count > 0 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty Count Violation: Prefer checking isEmpty over comparing count to zero. (empty_count)

self.selectionHeader()

Section {
let selectedOptions = _value.wrappedValue.map { _valueOptions[$0] }
ForEach(selectedOptions.filter { _searchText.isEmpty || $0.localizedStandardContains(_searchText) }, id: \.self) { item in
self.rowView(value: item, isSelected: true)
.padding(0)
.contentShape(Rectangle())
.onTapGesture {
guard let index = findIndex(of: item) else {
return
}
_onTap?(index)
}
}

Rectangle().fill(Color.preferredColor(.primaryGroupedBackground))
.frame(height: 30)
.listRowInsets(EdgeInsets())
}
.padding(0)
.contentShape(Rectangle())
.onTapGesture {
guard let index = findIndex(of: item) else {
return
}

Section {
if allowsMultipleSelection {
if _value.count != _valueOptions.count || allowsEmptySelection {
self.selectAllView()
}
_onTap?(index)
} else if _value.count == _valueOptions.count {
self.selectAllView()
} else {
EmptyView()
}
ForEach(_valueOptions.filter { _searchText.isEmpty || $0.localizedStandardContains(_searchText) }, id: \.self) { item in
let isSelected = self.isItemSelected(item)
self.rowView(value: item, isSelected: isSelected)
.padding(0)
.contentShape(Rectangle())
.onTapGesture {
guard let index = findIndex(of: item) else {
return
}
_onTap?(index)
}
}
}
}
Expand All @@ -69,10 +82,10 @@ extension SearchListPickerItem: View {
}
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 totalSpacing: CGFloat = (UIDevice.current.userInterfaceIdiom != .phone ? 8 : 16) * 2
let totalPadding: CGFloat = (UIDevice.current.userInterfaceIdiom != .phone ? 13 : 16) * 2
let safeAreaInset = self.getSafeAreaInsets()
var maxScrollViewHeight = popverHeight - totalSpacing - totalPadding - (self.isSearchBarHidden ? 0 : 52) - 56 - safeAreaInset.top - safeAreaInset.bottom - (UIDevice.current.userInterfaceIdiom == .pad ? 250 : 30)
var maxScrollViewHeight = popverHeight - totalSpacing - totalPadding - (self.isSearchBarHidden ? 0 : 52) - 56 - safeAreaInset.top - safeAreaInset.bottom - (UIDevice.current.userInterfaceIdiom != .phone ? 250 : 30)
maxScrollViewHeight -= self._keyboardHeight
self._height = min(scrollView.contentSize.height, maxScrollViewHeight)
var isSelectAllViewShow = false
Expand All @@ -90,8 +103,10 @@ extension SearchListPickerItem: View {
.frame(minWidth: UIDevice.current.userInterfaceIdiom != .phone ? popoverWidth : nil)
.scrollContentBackground(.hidden)
.padding(0)
.environment(\.defaultMinListRowHeight, 0)
.environment(\.defaultMinListHeaderHeight, 0)
.ifApply(!isSearchBarHidden, content: { v in
v.searchable(text: $_searchText, placement: .automatic)
v.searchable(text: $_searchText, placement: .navigationBarDrawer(displayMode: .always))
.onReceive(NotificationCenter.default.publisher(for: UIApplication.keyboardDidShowNotification)) { notif in
let rect = (notif.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect) ?? .zero
self._keyboardHeight = rect.height
Expand All @@ -103,6 +118,43 @@ extension SearchListPickerItem: View {
}
}

private func rowView(value: String, isSelected: Bool) -> some View {
HStack {
Text(value)
.lineLimit(1)
.foregroundStyle(Color.preferredColor(.primaryLabel))
.font(.fiori(forTextStyle: .body, weight: .regular))
Spacer()
if isSelected {
Image(systemName: "checkmark")
#if !os(visionOS)
.foregroundStyle(Color.preferredColor(.tintColor))
#else
.foregroundStyle(Color.preferredColor(.primaryLabel))
#endif
}
}
}

private func selectionHeader() -> some View {
HStack {
Text(NSLocalizedString("Selected", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""))
.foregroundStyle(Color.preferredColor(.secondaryLabel))
.font(.fiori(forTextStyle: .subheadline, weight: .regular))
Spacer()
}
.padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom != .phone ? 13 : 16)
.padding([.top, .bottom], 8)
.background(Color.preferredColor(.secondaryGroupedBackground))
.listRowInsets(EdgeInsets())
.alignmentGuide(.listRowSeparatorLeading) { dimensions in
dimensions[.leading]
}
.alignmentGuide(.listRowSeparatorTrailing) { dimensions in
dimensions[.trailing]
}
}

private func selectAllView() -> some View {
HStack {
Text(NSLocalizedString("All", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""))
Expand All @@ -113,10 +165,20 @@ extension SearchListPickerItem: View {
selectAll?(_value.count != _valueOptions.count)
}) {
Text(_value.count == _valueOptions.count ? NSLocalizedString("Deselect All", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: "") : NSLocalizedString("Select All", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""))
}
.foregroundStyle(Color.preferredColor(.tintColor))
.font(.fiori(forTextStyle: .subheadline, weight: .regular))
}.buttonStyle(PlainButtonStyle())
}
.padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom == .pad ? 13 : 16)
.padding([.leading, .trailing], UIDevice.current.userInterfaceIdiom != .phone ? 13 : 16)
.padding([.top, .bottom], 8)
.background(Color.preferredColor(.secondaryGroupedBackground))
.listRowInsets(EdgeInsets())
.alignmentGuide(.listRowSeparatorLeading) { dimensions in
dimensions[.leading]
}
.alignmentGuide(.listRowSeparatorTrailing) { dimensions in
dimensions[.trailing]
}
}

private func isItemSelected(_ item: String) -> Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ struct PickerMenuItem: View {
})
.buttonStyle(ApplyButtonStyle())
} components: {
SearchListPickerItem(value: self.$item.workingValue, valueOptions: self.item.valueOptions, hint: nil, allowsMultipleSelection: self.item.allowsMultipleSelection, allowsEmptySelection: self.item.allowsEmptySelection, isSearchBarHidden: self.item.isSearchBarHidden) { index in
SearchListPickerItem(value: self.$item.workingValue, valueOptions: self.item.valueOptions, hint: nil, allowsMultipleSelection: self.item.allowsMultipleSelection, allowsEmptySelection: self.item.allowsEmptySelection, isSearchBarHidden: self.item.isSearchBarHidden, disableListEntriesSection: self.item.disableListEntriesSection) { index in
self.item.onTap(option: self.item.valueOptions[index])
} selectAll: { isAll in
self.item.selectAll(isAll)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ extension _SortFilterCFGItemContainer: View {
valueOptions: self._items[r][c].picker.valueOptions,
allowsMultipleSelection: self._items[r][c].picker.allowsMultipleSelection,
allowsEmptySelection: self._items[r][c].picker.allowsEmptySelection,
isSearchBarHidden: self._items[r][c].picker.isSearchBarHidden
isSearchBarHidden: self._items[r][c].picker.isSearchBarHidden,
disableListEntriesSection: self._items[r][c].picker.disableListEntriesSection
) { index in
self._items[r][c].picker.onTap(option: self._items[r][c].picker.valueOptions[index])
} selectAll: { isAll in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ public struct SearchListPickerItem {
var _hint: String? = nil
var _onTap: ((_ index: Int) -> Void)? = nil

var disableListEntriesSection: Bool = false
var updateSearchListPickerHeight: ((CGFloat) -> ())? = nil
@State var _height: CGFloat = 44
var allowsEmptySelection: Bool = false
var allowsMultipleSelection: Bool = false
var isSearchBarHidden: Bool = false
var selectAll: ((Bool) -> ())? = nil
@State var _height: CGFloat = 44
@State var _searchViewCornerRadius: CGFloat = 18
@State var _searchText: String = ""
var allowsEmptySelection: Bool = false
let popoverWidth = 393.0
var selectAll: ((Bool) -> ())? = nil
@State var _keyboardHeight: CGFloat = 0.0

@State var _searchViewCornerRadius: CGFloat = 18

public init(model: SearchListPickerItemModel) {
self.init(value: Binding<[Int]>(get: { model.value }, set: { model.value = $0 }), valueOptions: model.valueOptions, hint: model.hint, onTap: model.onTap)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
/* XMSG: Placeholder text in chart when there is no data */
"No Data" = "No Data";

Check warning on line 2 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "No Data" is unused (unused)

/* 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" = "Save";

Check warning on line 5 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "Save" is unused (unused)

/* 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" = "Re-enter Signature";

Check warning on line 8 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "Re-enter Signature" is unused (unused)

/* 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" = "Clear";

Check warning on line 11 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "Clear" is unused (unused)

/* 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" = "Cancel";

Check warning on line 14 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "Cancel" is unused (unused)

/* 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" = "Signature";

Check warning on line 17 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "Signature" is unused (unused)

/* 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" = "Tap to Sign";

Check warning on line 20 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "Tap to Sign" is unused (unused)

/* XACT: The accessibility label for the signature area */
"Signature Area" = "Signature Area";

Check warning on line 23 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "Signature Area" is unused (unused)

/* XACT: The accessibility hint for the signature area */
"Double tap and drag to sign" = "Double tap and drag to sign";

Check warning on line 26 in Sources/FioriSwiftUICore/_localization/en.lproj/FioriSwiftUICore.strings

View workflow job for this annotation

GitHub Actions / LocalizableTextCommentsCheck

Unused Violation: Localized string "Double tap and drag to sign" is unused (unused)

/* XACT: The accessibility label for the signature image */
"Signature Image" = "Signature Image";
Expand Down Expand Up @@ -255,7 +255,29 @@
"Select All" = "Select All";
/* XBUT: The \"Deselect All\" button title on the List Picker header */
"Deselect All" = "Deselect All";

/* XBUT: Reset action of filter feedback bar, see https://experience.sap.com/fiori-design-ios/article/filter-feedback-bar/#behavior-and-interaction */
"Reset" = "Reset";
/* XBUT: Apply action of filter feedback bar, see https://experience.sap.com/fiori-design-ios/article/filter-feedback-bar/#behavior-and-interaction */
"Apply" = "Apply";

/* XBUT: The \"Selected\" title on the List Picker header */
"Selected" = "Selected";

/* XACT: The accessibility hint for Text Input Views, editable */
"Text field, Double tap to edit" = "Text field, Double tap to edit";

/* XACT: The accessibility hint for Text Input Views, editing */
"Text field, is editing" = "Text field, is editing";

/* XACT: The accessibility hint for Text Input Views, editing, with clear button */
"Text field, is editing. Double tap to clear text" = "Text field, is editing. Double tap to clear text";

/* XACT: The default accessibility label for the custom action in TextFieldFormView if none was specified */
"Custom Action" = "Custom Action";

/* XBUT: Error messages shown to users in DocumentScannerView */
"Failed to create PDF page from images" = "Failed to create PDF page from images.";

/* XBUT: Cancel message shown to users in DocumentScannerView */
"User cancelled the scan" = "User cancelled the scan.";
Loading