From f8b48007989138abe8ee6be951cd3dff36d74c3b Mon Sep 17 00:00:00 2001 From: Soheil Novinfard Date: Fri, 24 Jan 2025 15:52:37 +0000 Subject: [PATCH] PoC - Option B --- .../Calendar/Classes/BPKCalendar.swift | 2 + .../Core/CalendarSelectionHandler.swift | 3 +- .../Core/CalendarTypeContainerFactory.swift | 3 - .../Range/RangeCalendarContainer.swift | 44 ------- .../Single/SingleCalendarContainer.swift | 2 - Example/Backpack.xcodeproj/project.pbxproj | 4 - ...alendarExampleOnSelectionHandlerView.swift | 124 ------------------ .../Calendar/CalendarExampleRangeView.swift | 67 ++++++---- .../Calendar/CalendarExampleSingleView.swift | 8 +- .../Groups/CalendarGroups.swift | 1 - 10 files changed, 50 insertions(+), 208 deletions(-) delete mode 100644 Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleOnSelectionHandlerView.swift diff --git a/Backpack-SwiftUI/Calendar/Classes/BPKCalendar.swift b/Backpack-SwiftUI/Calendar/Classes/BPKCalendar.swift index 8a5f43f30..e44357b59 100644 --- a/Backpack-SwiftUI/Calendar/Classes/BPKCalendar.swift +++ b/Backpack-SwiftUI/Calendar/Classes/BPKCalendar.swift @@ -29,6 +29,8 @@ import SwiftUI /// the `Calendar` struct in Swift. /// - validRange: The range of dates that the calendar should allow the user to select. /// This is specified as a`ClosedRange`. +/// - onSelectHandler: A callback function that handles calendar selection. +/// Without Providing this handler the selection would NOT perform /// - initialMonthScroll: The initial scrolling to the month using `MonthScroll` /// /// The `BPKCalendar` view also allows you to specify an accessory action. This is a closure that takes a string and diff --git a/Backpack-SwiftUI/Calendar/Classes/Core/CalendarSelectionHandler.swift b/Backpack-SwiftUI/Calendar/Classes/Core/CalendarSelectionHandler.swift index 2b34addfa..075b8bcf6 100644 --- a/Backpack-SwiftUI/Calendar/Classes/Core/CalendarSelectionHandler.swift +++ b/Backpack-SwiftUI/Calendar/Classes/Core/CalendarSelectionHandler.swift @@ -1,5 +1,4 @@ -/// A custom callback function that handles calendar selection. -/// It gives the flexibility to use different selection behaviour after a date is tapped by the user. +/// A callback function that handles calendar selection. /// - Parameters: /// - CalendarSelectionSimpleType: The type of calendar selection (range or single). /// - Date: The date which is tapped diff --git a/Backpack-SwiftUI/Calendar/Classes/Core/CalendarTypeContainerFactory.swift b/Backpack-SwiftUI/Calendar/Classes/Core/CalendarTypeContainerFactory.swift index 7a07afafd..5b2d5d255 100644 --- a/Backpack-SwiftUI/Calendar/Classes/Core/CalendarTypeContainerFactory.swift +++ b/Backpack-SwiftUI/Calendar/Classes/Core/CalendarTypeContainerFactory.swift @@ -93,9 +93,6 @@ struct CalendarTypeContainerFactory: dateFormatter: accessibilityDateFormatter ), month: month, - selectionHandler: DefaultRangeCalendarSelectionHandler( - instructionAfterSelectingDate: accessibilityConfigurations.returnDatePrompt - ), monthHeader: { monthHeader(month) }, dayAccessoryView: dayAccessoryView, onSelectHandler: onSelectHandler diff --git a/Backpack-SwiftUI/Calendar/Classes/Range/RangeCalendarContainer.swift b/Backpack-SwiftUI/Calendar/Classes/Range/RangeCalendarContainer.swift index 0ade3994b..4be0ca370 100644 --- a/Backpack-SwiftUI/Calendar/Classes/Range/RangeCalendarContainer.swift +++ b/Backpack-SwiftUI/Calendar/Classes/Range/RangeCalendarContainer.swift @@ -18,48 +18,12 @@ import SwiftUI -protocol RangeCalendarSelectionHandler { - func newStateFor( - selection date: Date, - currentSelection: CalendarRangeSelectionState? - ) -> CalendarRangeSelectionState -} - -struct DefaultRangeCalendarSelectionHandler: RangeCalendarSelectionHandler { - let instructionAfterSelectingDate: String - - func newStateFor( - selection date: Date, - currentSelection: CalendarRangeSelectionState? - ) -> CalendarRangeSelectionState { - switch currentSelection { - case .intermediate(let initialDateSelection): - if date < initialDateSelection { - UIAccessibility.post( - notification: .announcement, - argument: instructionAfterSelectingDate - ) - return .intermediate(date) - } else { - return .range(initialDateSelection...date) - } - default: - UIAccessibility.post( - notification: .announcement, - argument: instructionAfterSelectingDate - ) - return .intermediate(date) - } - } -} - struct RangeCalendarMonthContainer: View { @Binding var selectionState: CalendarRangeSelectionState? let calendar: Calendar let validRange: ClosedRange let accessibilityProvider: RangeDayAccessibilityProvider let month: Date - let selectionHandler: RangeCalendarSelectionHandler @ViewBuilder let monthHeader: MonthHeader @ViewBuilder let dayAccessoryView: (Date) -> DayAccessoryView var onSelectHandler: CalendarSelectionHandler? @@ -70,11 +34,6 @@ struct RangeCalendarMonthContainer: V .range(selectionState), date ) - } else { - selectionState = selectionHandler.newStateFor( - selection: date, - currentSelection: selectionState - ) } } @@ -234,9 +193,6 @@ struct RangeCalendarContainer_Previews: PreviewProvider { dateFormatter: Self.formatter ), month: start, - selectionHandler: DefaultRangeCalendarSelectionHandler( - instructionAfterSelectingDate: "" - ), monthHeader: { BPKText("\(Self.formatter.string(from: start))") }, diff --git a/Backpack-SwiftUI/Calendar/Classes/Single/SingleCalendarContainer.swift b/Backpack-SwiftUI/Calendar/Classes/Single/SingleCalendarContainer.swift index 2ad52f4f4..0d40cf3e7 100644 --- a/Backpack-SwiftUI/Calendar/Classes/Single/SingleCalendarContainer.swift +++ b/Backpack-SwiftUI/Calendar/Classes/Single/SingleCalendarContainer.swift @@ -55,8 +55,6 @@ struct SingleCalendarMonthContainer: } onSelection: { if let onSelectHandler { onSelectHandler(.single(selection), dayDate) - } else { - selection = .single(dayDate) } } .accessibilityAddTraits(.isButton) diff --git a/Example/Backpack.xcodeproj/project.pbxproj b/Example/Backpack.xcodeproj/project.pbxproj index 1a3fe91d6..5132b640b 100644 --- a/Example/Backpack.xcodeproj/project.pbxproj +++ b/Example/Backpack.xcodeproj/project.pbxproj @@ -24,7 +24,6 @@ 25006AB02C60B1740038D7CA /* SearchControlInputExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25006AAF2C60B1740038D7CA /* SearchControlInputExampleView.swift */; }; 256F76152BB479850047AD1C /* SearchInputSummaryExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 256F76142BB479850047AD1C /* SearchInputSummaryExampleView.swift */; }; 25D1337D2C73EFD7002C9562 /* SearchControlInputUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25D1337C2C73EFD7002C9562 /* SearchControlInputUITest.swift */; }; - 281E51CC2D43C0C4002E7BF5 /* CalendarExampleOnSelectionHandlerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 281E51CB2D43C0C4002E7BF5 /* CalendarExampleOnSelectionHandlerView.swift */; }; 2832E45B2CDE1561000B6DF4 /* CalendarExampleWholeMonthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2832E45A2CDE1561000B6DF4 /* CalendarExampleWholeMonthView.swift */; }; 2A62FDDD2AB89F4500D545E5 /* TextAreaExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A62FDDC2AB89F4500D545E5 /* TextAreaExampleView.swift */; }; 3A7D2D47214AB9F400ECBD5B /* BPKButtonsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A7D2D46214AB9F400ECBD5B /* BPKButtonsViewController.m */; }; @@ -260,7 +259,6 @@ 25006AAF2C60B1740038D7CA /* SearchControlInputExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchControlInputExampleView.swift; sourceTree = ""; }; 256F76142BB479850047AD1C /* SearchInputSummaryExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchInputSummaryExampleView.swift; sourceTree = ""; }; 25D1337C2C73EFD7002C9562 /* SearchControlInputUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchControlInputUITest.swift; sourceTree = ""; }; - 281E51CB2D43C0C4002E7BF5 /* CalendarExampleOnSelectionHandlerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarExampleOnSelectionHandlerView.swift; sourceTree = ""; }; 2832E45A2CDE1561000B6DF4 /* CalendarExampleWholeMonthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarExampleWholeMonthView.swift; sourceTree = ""; }; 2A62FDDC2AB89F4500D545E5 /* TextAreaExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextAreaExampleView.swift; sourceTree = ""; }; 3A7D2D45214AB9F400ECBD5B /* BPKButtonsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BPKButtonsViewController.h; sourceTree = ""; }; @@ -609,7 +607,6 @@ isa = PBXGroup; children = ( 537AA67E2B050D1000D97B42 /* CalendarExampleRangeView.swift */, - 281E51CB2D43C0C4002E7BF5 /* CalendarExampleOnSelectionHandlerView.swift */, 5318E3332AF506FA00C66D18 /* CalendarExampleSingleView.swift */, 2832E45A2CDE1561000B6DF4 /* CalendarExampleWholeMonthView.swift */, 530E84BE2D28461400009A4F /* CalendarExampleDynamicView.swift */, @@ -1894,7 +1891,6 @@ 5390DB5D29098CE400F0F790 /* RadiusTokensViewController.swift in Sources */, 79CE553529BEF26400CC89D3 /* PriceGroupsProvider.swift in Sources */, E38947D422F5F0E900A357DB /* BottomSheetViewController.swift in Sources */, - 281E51CC2D43C0C4002E7BF5 /* CalendarExampleOnSelectionHandlerView.swift in Sources */, D2B8A01B2147FF6A002290DE /* PreviewCollectionViewHeader.swift in Sources */, 53C6621729EA0DAB00BF1A62 /* NudgerExampleView.swift in Sources */, 1750EB6329915230005226DF /* CardButtonsViewController.swift in Sources */, diff --git a/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleOnSelectionHandlerView.swift b/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleOnSelectionHandlerView.swift deleted file mode 100644 index db096e1fb..000000000 --- a/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleOnSelectionHandlerView.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -/* - * Backpack - Skyscanner's Design System - * - * Copyright © 2023 Skyscanner Ltd. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import SwiftUI -import Backpack_SwiftUI - -struct CalendarExampleOnSelectionHandlerView: View { - @State var selection: CalendarRangeSelectionState? - - let validRange: ClosedRange - let calendar: Calendar - let formatter: DateFormatter - - init() { - let calendar = Calendar.current - let start = calendar.date(from: .init(year: 2023, month: 11, day: 6))! - let end = calendar.date(from: .init(year: 2024, month: 11, day: 28))! - - self.validRange = start...end - self.calendar = calendar - - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.locale = calendar.locale - formatter.timeZone = calendar.timeZone - self.formatter = formatter - var selectionStart = calendar.date(from: .init(year: 2023, month: 11, day: 23))! - var selectionEnd = calendar.date(from: .init(year: 2023, month: 12, day: 2))! - - _selection = State(initialValue: .range(selectionStart...selectionEnd)) - } - - var body: some View { - VStack { - HStack { - BPKText("Selected inbound:", style: .caption) - if case .range(let selectedRange) = selection { - BPKText("\(formatter.string(from: selectedRange.lowerBound))", style: .caption) - } else if case .intermediate(let selectedDate) = selection { - BPKText("\(formatter.string(from: selectedDate))", style: .caption) - } - } - HStack { - BPKText("Selected outbound:", style: .caption) - if case .range(let selectedRange) = selection { - BPKText("\(formatter.string(from: selectedRange.upperBound))", style: .caption) - } - } - calendarView - } - } - - @ViewBuilder - var calendarView: some View { - let accessibilityConfigurations = RangeAccessibilityConfigurations( - startSelectionHint: "Double tap to select departure date", - endSelectionHint: "Double tap to select return date", - startSelectionState: "Selected as departure date", - endSelectionState: "Selected as return date", - betweenSelectionState: "Between departure and return date", - startAndEndSelectionState: "Selected as both departure and return date", - returnDatePrompt: "Now please select a return date" - ) - BPKCalendar( - selectionType: .range( - selection: $selection, - accessibilityConfigurations: accessibilityConfigurations - ), - calendar: calendar, - validRange: validRange, - - // HERE => Customising the selection behaviour via a handler - onSelectHandler: { state, date in - guard case .range(let rangeState) = state else { return } - switch rangeState { - case .intermediate(let initialDateSelection): - if date < initialDateSelection { - selection = .intermediate(date) - } else { - selection = .range(initialDateSelection...date) - } - case .range(let range): - // M1C requirement can be handled here for the consumer - // via using a feature flag - if date >= range.lowerBound { - // M1C - selection = .range(range.lowerBound...date) - } else { - // M1B - selection = .intermediate(date) - } - default: - selection = .intermediate(date) - } - }, - dayAccessoryView: { _ in - BPKIconView(.search, size: .small) - .foregroundColor(.accentColor) - } - ) - } -} - -struct CalendarExampleOnSelectionHandlerView_Previews: PreviewProvider { - static var previews: some View { - CalendarExampleOnSelectionHandlerView() - } -} diff --git a/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleRangeView.swift b/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleRangeView.swift index 8aa8fa818..2db6b446a 100644 --- a/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleRangeView.swift +++ b/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleRangeView.swift @@ -96,32 +96,7 @@ struct CalendarExampleRangeView: View { ), calendar: calendar, validRange: validRange, - onSelectHandler: { state, date in - switch state { - // swiftlint:disable switch_case_alignment - case .range(let rangeState): - switch rangeState { - case .intermediate(let initialDateSelection): - if date < initialDateSelection { - selection = .intermediate(date) - } else { - selection = .range(initialDateSelection...date) - } - case .range(let range): - // M1C requirement can be handled here for the consumer - // via using a feature flag - if date > range.lowerBound && date < range.upperBound { - selection = .range(range.lowerBound...date) - } else { - selection = .intermediate(date) - } - default: - selection = .intermediate(date) - } - default: - break - } - }, + onSelectHandler: handleSelection, dayAccessoryView: { _ in BPKIconView(.search, size: .small) .foregroundColor(.accentColor) @@ -135,10 +110,48 @@ struct CalendarExampleRangeView: View { ), calendar: calendar, validRange: validRange, - initialMonthScroll: monthScroll + initialMonthScroll: monthScroll, + onSelectHandler: handleSelection ) } } + + // without providing the selection handler by consumer, + // range selection does not work + private func handleSelection(state: CalendarSelectionSimpleType, date: Date) { + switch state { + // swiftlint:disable switch_case_alignment + case .range(let rangeState): + switch rangeState { + case .intermediate(let initialDateSelection): + if date < initialDateSelection { + selection = .intermediate(date) + } else { + selection = .range(initialDateSelection...date) + } + case .range(let range): + // M1C requirement can be handled here for the consumer + // via using a feature flag + let m1cEnabled = false + if m1cEnabled { + if date >= range.lowerBound { + selection = .range(range.lowerBound...date) + + } else { + selection = .intermediate(date) + } + } else { + selection = .intermediate(date) + } + default: + selection = .intermediate(date) + } + case .single(let singleState): + // Not applicable for the consumer + // who only uses the range selector + break + } + } } struct CalendarExampleRangeView_Previews: PreviewProvider { diff --git a/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleSingleView.swift b/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleSingleView.swift index ed78d7d01..54d05a0fd 100644 --- a/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleSingleView.swift +++ b/Example/Backpack/SwiftUI/Components/Calendar/CalendarExampleSingleView.swift @@ -20,6 +20,7 @@ import SwiftUI import Backpack_SwiftUI +// swiftlint:disable closure_body_length struct CalendarExampleSingleView: View { @State var selection: CalendarSingleSelectionState? private var monthScroll: MonthScroll? @@ -71,7 +72,12 @@ struct CalendarExampleSingleView: View { ), calendar: calendar, validRange: validRange, - initialMonthScroll: monthScroll + initialMonthScroll: monthScroll, + onSelectHandler: { _, date in // unused state + // without providing the selection handler by consumer, + // single selection does not work + selection = .single(date) + } ) .monthAccessoryAction { _ in return CalendarMonthAccessoryAction( diff --git a/Example/Backpack/Utils/FeatureStories/Groups/CalendarGroups.swift b/Example/Backpack/Utils/FeatureStories/Groups/CalendarGroups.swift index e7c0dc44f..5b350143b 100644 --- a/Example/Backpack/Utils/FeatureStories/Groups/CalendarGroups.swift +++ b/Example/Backpack/Utils/FeatureStories/Groups/CalendarGroups.swift @@ -80,7 +80,6 @@ struct CalendarGroupsProvider { presentableCalendar("Range Selection", view: CalendarExampleRangeView(showAccessoryViews: false)), presentableCalendar("Single Selection", view: CalendarExampleSingleView()), presentableCalendar("With Accessory Views", view: CalendarExampleRangeView(showAccessoryViews: true)), - presentableCalendar("With Custom Selection Handler", view: CalendarExampleOnSelectionHandlerView()), presentableCalendar("With Whole Month Selection", view: CalendarExampleWholeMonthView()), presentableCalendar( "With Initial Month Scrolling",