Skip to content

Commit 8e9e79b

Browse files
authored
refactor: 💡 [HCPSDKFIORIUIKIT-2232]SortFilter duration picker (SAP#1028)
* refactor: 💡 [HCPSDKFIORIUIKIT-2232]SortFilter duration picker * refactor: 💡 [HCPSDKFIORIUIKIT-2232]SortFilter duration picker
1 parent da3b9e7 commit 8e9e79b

29 files changed

+709
-39
lines changed

Apps/Examples/Examples.xcodeproj/project.pbxproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
554307FB2D5F6A91005AFA6D /* _KeyValueItemExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 554307FA2D5F6A91005AFA6D /* _KeyValueItemExample.swift */; };
3232
55598FAD2CDDB4F6007CFFBB /* ValuePickerExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55598FAC2CDDB4F6007CFFBB /* ValuePickerExample.swift */; };
3333
5590847D2D6D94210007EAAC /* _EmptyStateViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5590847C2D6D94210007EAAC /* _EmptyStateViewExample.swift */; };
34+
5A91FABE2D923BF80024F316 /* _DurationPickerExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A91FABD2D923BF70024F316 /* _DurationPickerExample.swift */; };
3435
5ABFB34B2D27D73B0054C1F3 /* SectionHeaderFooterExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ABFB34A2D27D7310054C1F3 /* SectionHeaderFooterExample.swift */; };
3536
6432FFA02C5164F8008ECE89 /* SegmentedControlExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432FF9F2C5164F8008ECE89 /* SegmentedControlExample.swift */; };
3637
6439F5142CEE892200EF1B42 /* ProcessingIndicatorExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439F5132CEE892200EF1B42 /* ProcessingIndicatorExample.swift */; };
@@ -45,8 +46,8 @@
4546
6D14F05E2C9290F20053BA98 /* BannerMultiMessageCustomInitExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D14F05D2C9290F20053BA98 /* BannerMultiMessageCustomInitExample.swift */; };
4647
6D3A3DE92CDB5F1E004D4597 /* ObjectCellEnhancementExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D3A3DE82CDB5F1E004D4597 /* ObjectCellEnhancementExample.swift */; };
4748
6D66D7F12D02FC7B00F7A97D /* ActivityItemExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D66D7EF2D02FC7B00F7A97D /* ActivityItemExample.swift */; };
48-
6D6E256D2D378025009A62CA /* FilterFormViewExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E256C2D378025009A62CA /* FilterFormViewExamples.swift */; };
4949
6D6E25672D364F78009A62CA /* OnBoardingWelcomeScreenExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E25662D364F78009A62CA /* OnBoardingWelcomeScreenExamples.swift */; };
50+
6D6E256D2D378025009A62CA /* FilterFormViewExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E256C2D378025009A62CA /* FilterFormViewExamples.swift */; };
5051
6D6E86252C50D42000EDB6F4 /* FioriButtonInListExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86242C50D42000EDB6F4 /* FioriButtonInListExample.swift */; };
5152
6D6E86292C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86282C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift */; };
5253
6D6E86672C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6E86662C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift */; };
@@ -296,6 +297,7 @@
296297
554307FA2D5F6A91005AFA6D /* _KeyValueItemExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _KeyValueItemExample.swift; sourceTree = "<group>"; };
297298
55598FAC2CDDB4F6007CFFBB /* ValuePickerExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValuePickerExample.swift; sourceTree = "<group>"; };
298299
5590847C2D6D94210007EAAC /* _EmptyStateViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _EmptyStateViewExample.swift; sourceTree = "<group>"; };
300+
5A91FABD2D923BF70024F316 /* _DurationPickerExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _DurationPickerExample.swift; sourceTree = "<group>"; };
299301
5ABFB34A2D27D7310054C1F3 /* SectionHeaderFooterExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderFooterExample.swift; sourceTree = "<group>"; };
300302
6432FF9F2C5164F8008ECE89 /* SegmentedControlExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentedControlExample.swift; sourceTree = "<group>"; };
301303
6439F5132CEE892200EF1B42 /* ProcessingIndicatorExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessingIndicatorExample.swift; sourceTree = "<group>"; };
@@ -310,8 +312,8 @@
310312
6D14F05D2C9290F20053BA98 /* BannerMultiMessageCustomInitExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BannerMultiMessageCustomInitExample.swift; sourceTree = "<group>"; };
311313
6D3A3DE82CDB5F1E004D4597 /* ObjectCellEnhancementExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCellEnhancementExample.swift; sourceTree = "<group>"; };
312314
6D66D7EF2D02FC7B00F7A97D /* ActivityItemExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityItemExample.swift; sourceTree = "<group>"; };
313-
6D6E256C2D378025009A62CA /* FilterFormViewExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterFormViewExamples.swift; sourceTree = "<group>"; };
314315
6D6E25662D364F78009A62CA /* OnBoardingWelcomeScreenExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingWelcomeScreenExamples.swift; sourceTree = "<group>"; };
316+
6D6E256C2D378025009A62CA /* FilterFormViewExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterFormViewExamples.swift; sourceTree = "<group>"; };
315317
6D6E86242C50D42000EDB6F4 /* FioriButtonInListExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInListExample.swift; sourceTree = "<group>"; };
316318
6D6E86282C50E5F900EDB6F4 /* FioriButtonInListMultipleLineExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInListMultipleLineExample.swift; sourceTree = "<group>"; };
317319
6D6E86662C50FDBE00EDB6F4 /* FioriButtonInCollectionExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FioriButtonInCollectionExample.swift; sourceTree = "<group>"; };
@@ -1017,6 +1019,7 @@
10171019
children = (
10181020
55598FAC2CDDB4F6007CFFBB /* ValuePickerExample.swift */,
10191021
B1D41B1F291A2D97004E64A5 /* DurationPickerExample.swift */,
1022+
5A91FABD2D923BF70024F316 /* _DurationPickerExample.swift */,
10201023
6432FF9F2C5164F8008ECE89 /* SegmentedControlExample.swift */,
10211024
64905D082C7693970062AAD4 /* DateTimePickerExample.swift */,
10221025
);
@@ -1463,6 +1466,7 @@
14631466
8A5579D524C1293C0098003A /* SettingsSeries.swift in Sources */,
14641467
6432FFA02C5164F8008ECE89 /* SegmentedControlExample.swift in Sources */,
14651468
9DEC27B52C3F3DB30070B571 /* KeyValueItemExample.swift in Sources */,
1469+
5A91FABE2D923BF80024F316 /* _DurationPickerExample.swift in Sources */,
14661470
8A557A2224C12C9B0098003A /* CoreContentView.swift in Sources */,
14671471
8A5579D224C1293C0098003A /* Color+Extensions.swift in Sources */,
14681472
6D6E866F2C539CDE00EDB6F4 /* InPlaceLoadingFlexibleButtonExample.swift in Sources */,

Apps/Examples/Examples/FioriSwiftUICore/CoreContentView.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ struct CoreContentView: View {
230230
Text("ListPickerItem")
231231
}
232232

233+
NavigationLink(
234+
destination: _DurationPickerExample())
235+
{
236+
Text("_DurationPicker")
237+
}
238+
233239
NavigationLink(
234240
destination: DurationPickerExample())
235241
{
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import FioriSwiftUICore
2+
import SwiftUI
3+
4+
struct _DurationPickerExample: View {
5+
@State var selection1: Int = 0
6+
@State var selection2: Int = 244
7+
@State var selection3: Int = 100
8+
9+
var formatter: MeasurementFormatter {
10+
let formatter = MeasurementFormatter()
11+
formatter.locale = Locale(identifier: "zh-CN")
12+
formatter.unitStyle = .long
13+
formatter.unitOptions = .providedUnit
14+
return formatter
15+
}
16+
17+
var body: some View {
18+
VStack {
19+
_DurationPicker(selection: self.$selection1)
20+
Divider()
21+
Text("Total \(self.selection1) minutes")
22+
Text("\(self.selection1 / 60) Hrs, \(self.selection1 % 60) Min")
23+
24+
_DurationPicker(selection: self.$selection2, minimumMinutes: 1, minuteInterval: 2)
25+
Divider()
26+
Text("Total \(self.selection2) minutes")
27+
28+
_DurationPicker(selection: self.$selection3, maximumMinutes: 505, minimumMinutes: 60, minuteInterval: 2)
29+
.measurementFormatter(self.formatter)
30+
Divider()
31+
Text("Total \(self.selection3) minutes")
32+
}
33+
}
34+
}
35+
36+
struct _DurationPickerExample_Previews: PreviewProvider {
37+
static var previews: some View {
38+
_DurationPickerExample()
39+
}
40+
}

Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterExample.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ struct SortFilterExample: View {
4141
[
4242
.title(item: .init(name: "Title", text: "This is default text.", placeholder: "Please input", maxTextLength: 20, isCharCountEnabled: true, charCountBeyondLimitMsg: "Char count beyond limit"), showsOnFilterFeedbackBar: true),
4343
.note(item: .init(name: "Note", text: "This is default text.", placeholder: "Please input", maxTextLength: 200, isCharCountEnabled: true, charCountBeyondLimitMsg: "Char count beyond limit"), showsOnFilterFeedbackBar: false)
44-
]
44+
],
45+
[.durationPicker(item: .init(name: "Duration", value: 0, maximumMinutes: 505, minimumMinutes: 0, minuteInterval: 1), showsOnFilterFeedbackBar: true)]
4546
]
4647

4748
@State private var isShowingFullCFG: Bool = false

Apps/Examples/Examples/FioriSwiftUICore/SortFilter/SortFilterView+Extensions.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ extension View {
2020
return self.json(item: v)
2121
case .note(let v, _):
2222
return self.json(item: v)
23+
case .durationPicker(let v, _):
24+
return self.json(item: v)
2325
}
2426
}
2527

@@ -50,4 +52,8 @@ extension View {
5052
func json(item: SortFilterItem.NoteItem) -> String {
5153
"{name: \(item.name), value: \(item.text)}"
5254
}
55+
56+
func json(item: SortFilterItem.DurationPickerItem) -> String {
57+
"{name: \(item.name), value: \(String(describing: item.value))}"
58+
}
5359
}

Sources/FioriSwiftUICore/Components/MultiPropertyComponents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ protocol __ProgressIndicator: _ComponentMultiPropGenerating {
3232
var progressIndicatorText_: String? { get }
3333
}
3434

35-
protocol _DurationPicker: _ComponentMultiPropGenerating, AnyObject {
35+
protocol __DurationPicker: _ComponentMultiPropGenerating, AnyObject {
3636
// sourcery: bindingProperty
3737
// sourcery: no_view
3838
var selection: Int { get set }

Sources/FioriSwiftUICore/DataTypes/SortFilter+DataType.swift

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public enum SortFilterItem: Identifiable, Hashable {
2222
return item.id
2323
case .note(let item, _):
2424
return item.id
25+
case .durationPicker(let item, _):
26+
return item.id
2527
}
2628
}
2729

@@ -85,6 +87,13 @@ public enum SortFilterItem: Identifiable, Hashable {
8587
///
8688
/// 2. A section of view containing a SwiftUI NoteFormView with Fiori style
8789
case note(item: NoteItem, showsOnFilterFeedbackBar: Bool)
90+
91+
/// The type of UI control is used to build:
92+
///
93+
/// 1. Sort & Filter's menu item associated with a popover containing a SwiftUI DurationPicker with Fiori style
94+
///
95+
/// 2. A section of view containing a SwiftUI DurationPicker with Fiori style
96+
case durationPicker(item: DurationPickerItem, showsOnFilterFeedbackBar: Bool)
8897

8998
public var showsOnFilterFeedbackBar: Bool {
9099
switch self {
@@ -104,6 +113,8 @@ public enum SortFilterItem: Identifiable, Hashable {
104113
return showsOnFilterFeedbackBar
105114
case .note(_, let showsOnFilterFeedbackBar):
106115
return showsOnFilterFeedbackBar
116+
case .durationPicker(_, let showsOnFilterFeedbackBar):
117+
return showsOnFilterFeedbackBar
107118
}
108119
}
109120

@@ -150,6 +161,11 @@ public enum SortFilterItem: Identifiable, Hashable {
150161
hasher.combine(item.originalText)
151162
hasher.combine(item.workingText)
152163
hasher.combine(item.text)
164+
case .durationPicker(let item, _):
165+
hasher.combine(item.id)
166+
hasher.combine(item.originalValue)
167+
hasher.combine(item.workingValue)
168+
hasher.combine(item.value)
153169
}
154170
}
155171
}
@@ -315,6 +331,26 @@ extension SortFilterItem {
315331
}
316332
}
317333

334+
var durationPicker: DurationPickerItem {
335+
get {
336+
switch self {
337+
case .durationPicker(let item, _):
338+
return item
339+
default:
340+
fatalError("Unexpected value \(self)")
341+
}
342+
}
343+
344+
set {
345+
switch self {
346+
case .durationPicker(_, let showsOnFilterFeedbackBar):
347+
self = .durationPicker(item: newValue, showsOnFilterFeedbackBar: showsOnFilterFeedbackBar)
348+
default:
349+
fatalError("Unexpected value \(self)")
350+
}
351+
}
352+
}
353+
318354
var isChanged: Bool {
319355
switch self {
320356
case .picker(let item, _):
@@ -333,6 +369,8 @@ extension SortFilterItem {
333369
return item.isChanged
334370
case .note(let item, _):
335371
return item.isChanged
372+
case .durationPicker(let item, _):
373+
return item.isChanged
336374
}
337375
}
338376

@@ -354,6 +392,8 @@ extension SortFilterItem {
354392
return item.isOriginal
355393
case .note(let item, _):
356394
return item.isOriginal
395+
case .durationPicker(let item, _):
396+
return item.isOriginal
357397
}
358398
}
359399

@@ -383,6 +423,9 @@ extension SortFilterItem {
383423
case .note(var item, _):
384424
item.cancel()
385425
self.note = item
426+
case .durationPicker(var item, _):
427+
item.cancel()
428+
self.durationPicker = item
386429
}
387430
}
388431

@@ -412,6 +455,9 @@ extension SortFilterItem {
412455
case .note(var item, _):
413456
item.reset()
414457
self.note = item
458+
case .durationPicker(var item, _):
459+
item.reset()
460+
self.durationPicker = item
415461
}
416462
}
417463

@@ -441,6 +487,9 @@ extension SortFilterItem {
441487
case .note(var item, _):
442488
item.apply()
443489
self.note = item
490+
case .durationPicker(var item, _):
491+
item.apply()
492+
self.durationPicker = item
444493
}
445494
}
446495
}
@@ -1315,4 +1364,81 @@ public extension SortFilterItem {
13151364
"\(self.name): \(self.text)"
13161365
}
13171366
}
1367+
1368+
/// Data structure for integer type durantion picker
1369+
struct DurationPickerItem: Identifiable, Equatable {
1370+
public let id: String
1371+
public var name: String
1372+
public var value: Int
1373+
var workingValue: Int
1374+
let originalValue: Int
1375+
public let icon: String?
1376+
1377+
public let maximumMinutes: Int
1378+
public let minimumMinutes: Int
1379+
public let minuteInterval: Int
1380+
public let measurementFormatter: MeasurementFormatter
1381+
public init(id: String = UUID().uuidString, name: String, value: Int, maximumMinutes: Int = 1439, minimumMinutes: Int = 0, minuteInterval: Int = 1, measurementFormatter: MeasurementFormatter = MeasurementFormatter(), icon: String? = nil) {
1382+
self.id = id
1383+
self.name = name
1384+
self.value = value
1385+
self.workingValue = value
1386+
self.originalValue = value
1387+
self.maximumMinutes = maximumMinutes
1388+
self.minimumMinutes = minimumMinutes
1389+
self.minuteInterval = minuteInterval
1390+
self.measurementFormatter = measurementFormatter
1391+
self.icon = icon
1392+
}
1393+
1394+
mutating func reset() {
1395+
self.workingValue = self.originalValue
1396+
}
1397+
1398+
mutating func cancel() {
1399+
self.workingValue = self.value
1400+
}
1401+
1402+
mutating func apply() {
1403+
self.value = self.workingValue
1404+
}
1405+
1406+
var isChecked: Bool {
1407+
true
1408+
}
1409+
1410+
var label: String {
1411+
let hour = self.value / 60
1412+
let min = self.value % 60
1413+
var durationString = ""
1414+
1415+
if self.value == 0 {
1416+
durationString = "0" + " " + self.measurementFormatter.string(from: UnitDuration.minutes)
1417+
} else {
1418+
if hour > 0 {
1419+
durationString = "\(hour)" + " " + self.measurementFormatter.string(from: UnitDuration.hours)
1420+
}
1421+
if !durationString.isEmpty {
1422+
durationString += " "
1423+
}
1424+
if min > 0 {
1425+
durationString += "\(min)" + " " + self.measurementFormatter.string(from: UnitDuration.minutes)
1426+
}
1427+
}
1428+
1429+
return "\(self.name): \(durationString)"
1430+
}
1431+
1432+
mutating func setValue(newValue: DurationPickerItem) {
1433+
self.value = newValue.value
1434+
}
1435+
1436+
var isChanged: Bool {
1437+
self.value != self.workingValue
1438+
}
1439+
1440+
var isOriginal: Bool {
1441+
self.workingValue == self.originalValue
1442+
}
1443+
}
13181444
}

Sources/FioriSwiftUICore/DurationPicker/DurationPickerViewWrapper.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,22 @@ struct DurationPickerViewWrapper: UIViewRepresentable {
3131
self.measurementFormatter = measurementFormatter
3232
}
3333

34-
func updateUIView(_ uiView: UIViewType, context: Context) {}
34+
func updateUIView(_ uiView: UIViewType, context: Context) {
35+
for subView in uiView.subviews where subView is UIPickerView {
36+
if let pickerView = subView as? UIPickerView {
37+
let hour = self.selection / 60
38+
let minute = self.selection % 60
39+
40+
if let hourIndex = hours.firstIndex(of: hour) {
41+
pickerView.selectRow(hourIndex, inComponent: 0, animated: true)
42+
if let minuteIndex = minutesForHour(hourIndex).firstIndex(of: minute) {
43+
pickerView.selectRow(minuteIndex, inComponent: 1, animated: true)
44+
}
45+
}
46+
break
47+
}
48+
}
49+
}
3550

3651
func makeUIView(context: Context) -> some UIView {
3752
let container = UIView()
@@ -109,7 +124,11 @@ struct DurationPickerViewWrapper: UIViewRepresentable {
109124
return self.parent.hours[row].description
110125
case 1:
111126
let hourIndex = pickerView.selectedRow(inComponent: 0)
112-
return self.parent.minutesForHour(hourIndex)[row].description
127+
if self.parent.minutesForHour(hourIndex).count > row {
128+
return self.parent.minutesForHour(hourIndex)[row].description
129+
} else {
130+
return ""
131+
}
113132
default:
114133
return ""
115134
}
@@ -147,7 +166,9 @@ struct DurationPickerViewWrapper: UIViewRepresentable {
147166
} else {
148167
let view = self.setupMinuteView(label, forComponent: component)
149168
let hourIndex = pickerView.selectedRow(inComponent: 0)
150-
view.accessibilityLabel = self.parent.minutesForHour(hourIndex)[row].description + self.parent.minuteText
169+
if self.parent.minutesForHour(hourIndex).count > row {
170+
view.accessibilityLabel = self.parent.minutesForHour(hourIndex)[row].description + self.parent.minuteText
171+
}
151172
return view
152173
}
153174
}

0 commit comments

Comments
 (0)