-
Notifications
You must be signed in to change notification settings - Fork 0
Date range selector sheet #181
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
Merged
Merged
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
474ed49
feat: date range selector sheet
jvsena42 85acede
fix: sheet background
jvsena42 b6a2e44
feat: use project buttons
jvsena42 d5c82fe
chore: remve date buttons
jvsena42 bedff43
chore: selected date component
jvsena42 27e43e9
chore: remove old component
jvsena42 30c4d12
chore: replace DatePicker with MultiDatePicker
jvsena42 14cfd80
fix: selected date text
jvsena42 ded8408
feat: selected date text style
jvsena42 d1f44a1
chore: padding
jvsena42 e93e1e9
chore: sheet wrapper
jvsena42 65db24b
fix: date formmating
jvsena42 5965f76
fix: layout jump, single date selection, sheet header
jvsena42 c098033
fix: implement a custom calendar component
jvsena42 f73b1b2
fix: selected day background
jvsena42 7faaa27
fix: colors
jvsena42 37697b4
fix: week day style
jvsena42 bbd719a
fix: chevron style
jvsena42 d562d19
fix: middle day background color
jvsena42 95605cf
fix(activity): fix weekday headers, polishing
pwltr 4053641
Merge branch 'master' into feat/date-picker-activity
jvsena42 f12a7a3
chore:remove comments
jvsena42 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
199 changes: 199 additions & 0 deletions
199
Bitkit/Components/Activity/DateRangeSelectorSheet.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| import SwiftUI | ||
|
|
||
| // MARK: - DateRangeSelectorSheet | ||
|
|
||
| struct DateRangeSelectorSheet: View { | ||
| @Environment(\.dismiss) private var dismiss | ||
| @Environment(\.calendar) var calendar | ||
| @ObservedObject var viewModel: ActivityListViewModel | ||
|
|
||
| @State private var selectedDates: Set<DateComponents> = [] | ||
| @State private var startDate: Date? | ||
| @State private var endDate: Date? | ||
|
|
||
| let datePickerComponents: Set<Calendar.Component> = [.calendar, .era, .year, .month, .day] | ||
|
|
||
| init(viewModel: ActivityListViewModel) { | ||
| self.viewModel = viewModel | ||
|
|
||
| // Initialize with current date range if exists | ||
| var initialDates: Set<DateComponents> = [] | ||
| if let start = viewModel.startDate, let end = viewModel.endDate { | ||
| let calendar = Calendar.current | ||
| var currentDate = calendar.startOfDay(for: start) | ||
| let endOfDay = calendar.startOfDay(for: end) | ||
|
|
||
| while currentDate <= endOfDay { | ||
| if let components = calendar.dateComponents(datePickerComponents, from: currentDate) as DateComponents? { | ||
| initialDates.insert(components) | ||
jvsena42 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate) ?? currentDate | ||
| } | ||
| } | ||
|
|
||
| _selectedDates = State(initialValue: initialDates) | ||
| _startDate = State(initialValue: viewModel.startDate) | ||
| _endDate = State(initialValue: viewModel.endDate) | ||
| } | ||
|
|
||
| private var hasSelection: Bool { | ||
| startDate != nil && endDate != nil | ||
| } | ||
|
|
||
| private var datesBinding: Binding<Set<DateComponents>> { | ||
| Binding { | ||
| selectedDates | ||
| } set: { newValue in | ||
| if newValue.isEmpty { | ||
| selectedDates = newValue | ||
| startDate = nil | ||
| endDate = nil | ||
| } else if newValue.count > selectedDates.count { | ||
| // Date was added | ||
| if newValue.count == 1 { | ||
| // First date selected - set as start | ||
| selectedDates = newValue | ||
| if let components = newValue.first, | ||
| let date = calendar.date(from: components) | ||
| { | ||
| startDate = date | ||
| endDate = nil | ||
| } | ||
| } else if newValue.count == 2 { | ||
| // Second date selected - fill the range | ||
| selectedDates = filledRange(selectedDates: newValue) | ||
| updateStartEndDates() | ||
| } else if let firstMissingDate = newValue.subtracting(selectedDates).first { | ||
| // Additional date tapped - start new range | ||
| selectedDates = [firstMissingDate] | ||
| if let date = calendar.date(from: firstMissingDate) { | ||
| startDate = date | ||
| endDate = nil | ||
| } | ||
| } | ||
| } else if let firstMissingDate = selectedDates.subtracting(newValue).first { | ||
| // Date was removed - start new range from this date | ||
| selectedDates = [firstMissingDate] | ||
| if let date = calendar.date(from: firstMissingDate) { | ||
| startDate = date | ||
| endDate = nil | ||
| } | ||
| } else { | ||
| selectedDates = [] | ||
| startDate = nil | ||
| endDate = nil | ||
| } | ||
| } | ||
| } | ||
|
|
||
| var body: some View { | ||
| VStack(spacing: 0) { | ||
| BodyMBoldText(t("wallet__filter_title"), textColor: .white) | ||
| .padding(.top, 32) | ||
|
|
||
| // Date Range Picker | ||
| VStack(alignment: .leading, spacing: 16) { | ||
| // Calendar | ||
| MultiDatePicker("", selection: datesBinding) | ||
| .datePickerStyle(.graphical) | ||
| .tint(.brandAccent) | ||
| .padding(.horizontal, 16) | ||
| .padding(.top, 26) | ||
|
|
||
| // Display selected range | ||
| if let start = startDate { | ||
| HStack { | ||
| if let end = endDate { | ||
| BodyMSBText( | ||
| "\(start.formatted(.dateTime.month(.abbreviated).day().year())) - \(end.formatted(.dateTime.month(.abbreviated).day().year()))" | ||
| ) | ||
| } else { | ||
| BodyMSBText(start.formatted(.dateTime.month(.abbreviated).day().year())) | ||
| } | ||
| } | ||
| .frame(maxWidth: .infinity, alignment: .center) | ||
| .padding(.bottom, 36) | ||
| .padding(.horizontal, 16) | ||
| } | ||
| } | ||
|
|
||
| Spacer() | ||
|
|
||
| // Action buttons | ||
| HStack(spacing: 16) { | ||
| CustomButton( | ||
| title: t("wallet__filter_clear"), | ||
| variant: .secondary, | ||
| isDisabled: !hasSelection | ||
| ) { | ||
| selectedDates = [] | ||
| startDate = nil | ||
| endDate = nil | ||
| viewModel.clearDateRange() | ||
| dismiss() | ||
| } | ||
| .accessibilityIdentifier("CalendarClearButton") | ||
|
|
||
| CustomButton( | ||
| title: t("wallet__filter_apply"), | ||
| variant: .primary, | ||
| isDisabled: !hasSelection | ||
| ) { | ||
| viewModel.startDate = startDate | ||
| viewModel.endDate = endDate | ||
| dismiss() | ||
| } | ||
| .accessibilityIdentifier("CalendarApplyButton") | ||
| } | ||
| .padding(.horizontal, 16) | ||
| .padding(.bottom, 16) | ||
| } | ||
| .sheetBackground() | ||
| .presentationDetents([.height(600)]) | ||
| .presentationDragIndicator(.visible) | ||
| } | ||
|
|
||
| // MARK: - Helper Methods | ||
|
|
||
| private func filledRange(selectedDates: Set<DateComponents>) -> Set<DateComponents> { | ||
| let allDates = selectedDates.compactMap { calendar.date(from: $0) } | ||
| guard allDates.count == 2, | ||
| let startDate = allDates.min(), | ||
| let endDate = allDates.max() | ||
| else { | ||
| return selectedDates | ||
| } | ||
|
|
||
| var dateRange: Set<DateComponents> = [] | ||
| var currentDate = startDate | ||
|
|
||
| while currentDate <= endDate { | ||
| if let components = calendar.dateComponents(datePickerComponents, from: currentDate) as DateComponents? { | ||
| dateRange.insert(components) | ||
| } | ||
jvsena42 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| guard let nextDate = calendar.date(byAdding: .day, value: 1, to: currentDate) else { | ||
| break | ||
| } | ||
| currentDate = nextDate | ||
| } | ||
|
|
||
| return dateRange | ||
| } | ||
|
|
||
| private func updateStartEndDates() { | ||
| let allDates = selectedDates.compactMap { calendar.date(from: $0) } | ||
| startDate = allDates.min() | ||
| endDate = allDates.max() | ||
| } | ||
| } | ||
|
|
||
| #Preview("Empty State") { | ||
| DateRangeSelectorSheet(viewModel: ActivityListViewModel()) | ||
| } | ||
|
|
||
| #Preview("With Selection") { | ||
| let viewModel = ActivityListViewModel() | ||
| viewModel.startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date()) | ||
| viewModel.endDate = Date() | ||
| return DateRangeSelectorSheet(viewModel: viewModel) | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.