-
Notifications
You must be signed in to change notification settings - Fork 315
Expand file tree
/
Copy pathmodals.dart
More file actions
390 lines (346 loc) · 11.8 KB
/
modals.dart
File metadata and controls
390 lines (346 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
// Copyright (c) 2021 Simform Solutions. All rights reserved.
// Use of this source code is governed by a MIT-style license
// that can be found in the LICENSE file.
import 'package:flutter/material.dart';
import '../calendar_view.dart';
/// Settings for hour lines
class HourIndicatorSettings {
final double height;
final Color color;
final double offset;
final LineStyle lineStyle;
final double dashWidth;
final double dashSpaceWidth;
final int startHour;
/// Settings for hour lines
const HourIndicatorSettings(
{this.height = 1.0,
this.offset = 0.0,
this.color = Colors.grey,
this.lineStyle = LineStyle.solid,
this.dashWidth = 4,
this.dashSpaceWidth = 4,
this.startHour = 0})
: assert(height >= 0, "Height must be greater than or equal to 0.");
factory HourIndicatorSettings.none() => HourIndicatorSettings(
color: Colors.transparent,
height: 0.0,
);
}
/// Settings for divider between FullDay events and weekdays header
class DividerSettings {
/// Thickness of the divider line
final double thickness;
/// Height of the divider
final double height;
/// Color of the divider line
final Color color;
/// Settings for divider between FullDay events and weekdays header
const DividerSettings({
this.thickness = 1.0,
this.height = 1.0,
this.color = Colors.grey,
}) : assert(thickness >= 0, "Thickness must be greater than or equal to 0."),
assert(height >= 0, "Height must be greater than or equal to 0.");
factory DividerSettings.none() => DividerSettings(
color: Colors.transparent,
thickness: 0.0,
height: 0.0,
);
}
/// Settings for live time line
class LiveTimeIndicatorSettings {
/// Color of time indicator.
final Color color;
/// Height of time indicator.
final double height;
/// offset of time indicator.
final double offset;
/// StringProvider for time string
final StringProvider? timeStringBuilder;
/// Flag to show bullet at left side or not.
final bool showBullet;
/// Flag to show time on live time line.
final bool showTime;
/// Flag to show time background view.
final bool showTimeBackgroundView;
/// Radius of bullet.
final double bulletRadius;
/// Width of time backgroud view.
final double timeBackgroundViewWidth;
/// Function that provides the DateTime to be used for the live time indicator.
///
/// If not provided, [DateTime.now] will be used as the default behavior.
///
/// Example usage:
/// ```dart
/// // Show time for New York timezone (UTC-4)
/// currentTimeProvider: () {
/// final utcNow = DateTime.now().toUtc();
/// return utcNow.subtract(Duration(hours: 4));
/// }
/// ```
final DateTime Function()? currentTimeProvider;
/// Flag to show only today's events.
final bool onlyShowToday;
/// Settings for live time line
const LiveTimeIndicatorSettings({
this.height = 1.0,
this.offset = 5.0,
this.color = Colors.grey,
this.timeStringBuilder,
this.showBullet = true,
this.showTime = false,
this.showTimeBackgroundView = false,
this.bulletRadius = 5.0,
this.timeBackgroundViewWidth = 60.0,
this.currentTimeProvider,
this.onlyShowToday = false,
}) : assert(height >= 0, "Height must be greater than or equal to 0.");
factory LiveTimeIndicatorSettings.none() => LiveTimeIndicatorSettings(
color: Colors.transparent,
height: 0.0,
offset: 0.0,
showBullet: false,
);
}
/// Set `frequency = RepeatFrequency.daily` to repeat every day
/// starting from event date (Inclusive).
///
/// Set `frequency = RepeatFrequency.weekly` & provide list of weekdays
/// to repeat on.
///
/// [startDate]: Defines start date of repeating events.
/// [endDate]: Defines end date of repeating events.
/// [occurrence]: Defines repetition of an event for the given number of
/// occurrences.
///
/// [frequency]: Defines mode of repetition like repeat daily, weekly, monthly
/// or yearly.
///
/// [weekdays]: Contains list of weekdays to repeat starting from 0 index.
/// By default selected weekday is the start date of an event.
///
/// Note: Use constructor .withCalculatedEndDate to calculate
/// end date of recurring event automatically.
class RecurrenceSettings {
RecurrenceSettings({
required this.startDate,
this.endDate,
this.occurrences,
this.frequency = RepeatFrequency.doNotRepeat,
this.recurrenceEndOn = RecurrenceEnd.never,
this.excludeDates,
List<int>? weekdays,
}) : weekdays = weekdays ?? [startDate.weekday];
/// If recurrence event does not have an end date it will calculate end date
/// from the start date.
///
/// Specify `endDate` to end an event on specific date.
///
/// End date for Repeat Frequency - daily.
/// Ex. If event start date is 11-11-24 and interval is 5 then new end date
/// will be 15-11-24.
///
/// End date for Repeat Frequency - weekly
/// Ex. If event start date is 1-11-24 and interval is 5 then new end date
/// will be 29-11-24.
RecurrenceSettings.withCalculatedEndDate({
required this.startDate,
DateTime? endDate,
this.occurrences,
this.frequency = RepeatFrequency.doNotRepeat,
this.recurrenceEndOn = RecurrenceEnd.never,
this.excludeDates,
List<int>? weekdays,
}) : weekdays = weekdays ?? [startDate.weekday] {
this.endDate = endDate ?? _getEndDate(startDate);
}
final DateTime startDate;
late DateTime? endDate;
final int? occurrences;
final RepeatFrequency frequency;
final RecurrenceEnd recurrenceEndOn;
final List<int> weekdays;
final List<DateTime>? excludeDates;
// For recurrence patterns other than weekly, where the event may not repeat
// on the start date.
// Excludes one occurrence since the event is already counted
// for the start date.
int get _occurrences => (occurrences ?? 1) - 1;
/// Calculates the end date for a monthly recurring event
/// based on the start date and the number of occurrences.
///
/// If the next month does not have the event date and the recurrence
/// is still set to repeat for the given number of occurrences,
/// it will keep looking for a valid date in the following month.
///
/// Example: If the start date is 29-01-25 and the recurrence ends
/// after 2 occurrences,
/// the end date will be 29-03-25 because February does not have a 29th date.
/// Similarly for 30/31 date as well.
DateTime get _endDateMonthly {
var repetition = _occurrences;
var nextDate = startDate;
while (repetition > 0) {
nextDate = DateTime(
nextDate.year,
nextDate.month + 1,
nextDate.day,
);
// Adjust the date if the resulting month does not have the same day
// as the start date
// Example: DateTime(2024, 10 + 1, 31) gives 2024-12-01
if (nextDate.day != startDate.day) {
nextDate = DateTime(
nextDate.year,
nextDate.month,
startDate.day,
);
}
repetition--;
}
return nextDate;
}
/// Returns the calculated end date for the selected weekdays and occurrences,
/// or null if the conditions are not met.
///
/// If the weekday of event start date is not in list of selected weekdays
/// then it will find for the next valid weekdays to repeat on.
///
/// Example: If the start date is 12-11-24 (Tuesday), and the selected
/// weekdays are [Tuesday, Wednesday] for 3 occurrences,
/// the event will repeat on 12-11-24, 13-11-24, and 19-11-24.
///
/// Example: If the start date is 26-11-24 (Tuesday),
/// if all weeks are selected but the number of occurrences is 1,
/// the event will only be shown on the start date.
DateTime? get _endDateWeekly {
if (weekdays.isEmpty) {
return null;
}
// Contains the recurring weekdays in sorted order
final sortedWeekdays = weekdays..sort();
var remainingOccurrences = occurrences ?? 1;
var currentDate = startDate;
// Check if the start date is one of the recurring weekdays
if (sortedWeekdays.contains(startDate.weekday - 1)) {
remainingOccurrences--;
}
while (remainingOccurrences > 0) {
// Find the next valid weekday
final nextWeekday = sortedWeekdays.firstWhere(
(day) => day > currentDate.weekday - 1,
orElse: () => sortedWeekdays.first,
);
// Calculate the days to the next occurrence
final daysToAdd = (nextWeekday - (currentDate.weekday - 1) + 7) % 7;
// Move the current date to the next occurrence
currentDate = currentDate.add(Duration(days: daysToAdd));
if (daysToAdd == 0 && nextWeekday == sortedWeekdays.first) {
currentDate = currentDate.add(const Duration(days: 7));
}
remainingOccurrences--;
}
return currentDate;
}
/// Calculate end date for yearly recurring event
DateTime get _endDateYearly {
var repetition = _occurrences;
var nextDate = startDate;
// If the start date is not 29th Feb, we can directly calculate last year.
if (startDate.day != 29 && startDate.month != DateTime.february) {
return DateTime(
nextDate.year + repetition,
startDate.month,
startDate.day,
);
}
// TODO(Shubham): Optimize for larger recurrences if required
while (repetition > 0) {
final newDate = DateTime(
nextDate.year + 1,
startDate.month,
startDate.day,
);
// If month changes that means that date does not exist in given year
if (newDate.month != startDate.month) {
nextDate = DateTime(
newDate.year,
);
continue;
}
nextDate = newDate;
repetition--;
}
return nextDate;
}
/// Determines the end date for a recurring event based on the
/// `RepeatFrequency` & `RecurrenceEnd`.
///
/// Returns null if the end date is not applicable.
/// For example: An event that "does not repeat" and event that "never ends".
DateTime? _getEndDate(DateTime endDate) {
if (frequency == RepeatFrequency.doNotRepeat ||
recurrenceEndOn == RecurrenceEnd.never) {
return null;
} else if (recurrenceEndOn == RecurrenceEnd.onDate) {
return endDate;
} else if (recurrenceEndOn == RecurrenceEnd.after) {
return _handleOccurrence(endDate);
} else {
return null;
}
}
/// Returns the end date for a recurring event based on the specified
/// number of occurrences.
///
/// This method requires at least one occurrence to process the recurrence.
/// The recurrence starts from the event's start date.
DateTime? _handleOccurrence(DateTime endDate) {
if ((occurrences ?? 0) < 1) {
return endDate;
}
switch (frequency) {
case RepeatFrequency.doNotRepeat:
return null;
case RepeatFrequency.daily:
return endDate.add(Duration(days: _occurrences));
case RepeatFrequency.weekly:
return _endDateWeekly ?? endDate;
case RepeatFrequency.monthly:
return _endDateMonthly;
case RepeatFrequency.yearly:
return _endDateYearly;
}
}
@override
String toString() {
return 'start date: $startDate, '
'end date: $endDate, '
'interval: $occurrences, '
'frequency: $frequency '
'weekdays: $weekdays'
'recurrence Ends on: $recurrenceEndOn'
'exclude dates: $excludeDates';
}
RecurrenceSettings copyWith({
DateTime? startDate,
DateTime? endDate,
int? occurrences,
RepeatFrequency? frequency,
RecurrenceEnd? recurrenceEndOn,
List<int>? weekdays,
List<DateTime>? excludeDates,
}) {
return RecurrenceSettings(
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
occurrences: occurrences ?? this.occurrences,
frequency: frequency ?? this.frequency,
recurrenceEndOn: recurrenceEndOn ?? this.recurrenceEndOn,
weekdays: weekdays ?? this.weekdays,
excludeDates: excludeDates ?? this.excludeDates,
);
}
}