mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat(mobile): custom date range for map
This commit is contained in:
@@ -1619,6 +1619,7 @@
|
|||||||
"not_available": "N/A",
|
"not_available": "N/A",
|
||||||
"not_in_any_album": "Not in any album",
|
"not_in_any_album": "Not in any album",
|
||||||
"not_selected": "Not selected",
|
"not_selected": "Not selected",
|
||||||
|
"not_set": "Not set",
|
||||||
"notes": "Notes",
|
"notes": "Notes",
|
||||||
"nothing_here_yet": "Nothing here yet",
|
"nothing_here_yet": "Nothing here yet",
|
||||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ enum StoreKey<T> {
|
|||||||
autoPlayVideo<bool>._(139),
|
autoPlayVideo<bool>._(139),
|
||||||
albumGridView<bool>._(140),
|
albumGridView<bool>._(140),
|
||||||
|
|
||||||
|
// Map custom time range settings
|
||||||
|
mapCustomFrom<String>._(141),
|
||||||
|
mapCustomTo<String>._(142),
|
||||||
|
|
||||||
// Experimental stuff
|
// Experimental stuff
|
||||||
photoManagerCustomFilter<bool>._(1000),
|
photoManagerCustomFilter<bool>._(1000),
|
||||||
betaPromptShown<bool>._(1001),
|
betaPromptShown<bool>._(1001),
|
||||||
|
|||||||
@@ -27,9 +27,16 @@ class DriftMapRepository extends DriftDatabaseRepository {
|
|||||||
condition = condition & _db.remoteAssetEntity.isFavorite.equals(true);
|
condition = condition & _db.remoteAssetEntity.isFavorite.equals(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.relativeDays != 0) {
|
if (options.customTimeRange.isValid) {
|
||||||
final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays));
|
if (options.customTimeRange.from != null) {
|
||||||
condition = condition & _db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate);
|
condition = condition & _db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(options.customTimeRange.from!);
|
||||||
|
}
|
||||||
|
if (options.customTimeRange.to != null) {
|
||||||
|
condition = condition & _db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(options.customTimeRange.to!);
|
||||||
|
}
|
||||||
|
} else if (options.relativeDays > 0) {
|
||||||
|
final fromDate = DateTime.now().subtract(Duration(days: options.relativeDays));
|
||||||
|
condition = condition & _db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(fromDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return condition;
|
return condition;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
import 'package:stream_transform/stream_transform.dart';
|
import 'package:stream_transform/stream_transform.dart';
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ class TimelineMapOptions {
|
|||||||
final bool includeArchived;
|
final bool includeArchived;
|
||||||
final bool withPartners;
|
final bool withPartners;
|
||||||
final int relativeDays;
|
final int relativeDays;
|
||||||
|
final CustomTimeRange customTimeRange;
|
||||||
|
|
||||||
const TimelineMapOptions({
|
const TimelineMapOptions({
|
||||||
required this.bounds,
|
required this.bounds,
|
||||||
@@ -28,6 +30,7 @@ class TimelineMapOptions {
|
|||||||
this.includeArchived = false,
|
this.includeArchived = false,
|
||||||
this.withPartners = false,
|
this.withPartners = false,
|
||||||
this.relativeDays = 0,
|
this.relativeDays = 0,
|
||||||
|
this.customTimeRange = const CustomTimeRange(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +531,16 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
query.where(_db.remoteAssetEntity.isFavorite.equals(true));
|
query.where(_db.remoteAssetEntity.isFavorite.equals(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.relativeDays != 0) {
|
if (options.customTimeRange.isValid) {
|
||||||
|
// Use custom from/to filters
|
||||||
|
if (options.customTimeRange.from != null) {
|
||||||
|
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(options.customTimeRange.from!));
|
||||||
|
}
|
||||||
|
if (options.customTimeRange.to != null) {
|
||||||
|
query.where(_db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(options.customTimeRange.to!));
|
||||||
|
}
|
||||||
|
} else if (options.relativeDays > 0) {
|
||||||
|
// Use relative days
|
||||||
final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays));
|
final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays));
|
||||||
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate));
|
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate));
|
||||||
}
|
}
|
||||||
@@ -570,7 +582,16 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
query.where(_db.remoteAssetEntity.isFavorite.equals(true));
|
query.where(_db.remoteAssetEntity.isFavorite.equals(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.relativeDays != 0) {
|
if (options.customTimeRange.isValid) {
|
||||||
|
// Use custom from/to filters
|
||||||
|
if (options.customTimeRange.from != null) {
|
||||||
|
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(options.customTimeRange.from!));
|
||||||
|
}
|
||||||
|
if (options.customTimeRange.to != null) {
|
||||||
|
query.where(_db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(options.customTimeRange.to!));
|
||||||
|
}
|
||||||
|
} else if (options.relativeDays > 0) {
|
||||||
|
// Use relative days
|
||||||
final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays));
|
final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays));
|
||||||
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate));
|
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,22 @@ import 'package:immich_mobile/providers/map/map_state.provider.dart';
|
|||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
|
|
||||||
|
class CustomTimeRange {
|
||||||
|
final DateTime? from;
|
||||||
|
final DateTime? to;
|
||||||
|
|
||||||
|
const CustomTimeRange({this.from, this.to});
|
||||||
|
|
||||||
|
bool get isValid => from != null || to != null;
|
||||||
|
|
||||||
|
CustomTimeRange copyWith({DateTime? from, DateTime? to}) {
|
||||||
|
return CustomTimeRange(from: from ?? this.from, to: to ?? this.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomTimeRange clearFrom() => CustomTimeRange(to: to);
|
||||||
|
CustomTimeRange clearTo() => CustomTimeRange(from: from);
|
||||||
|
}
|
||||||
|
|
||||||
class MapState {
|
class MapState {
|
||||||
final ThemeMode themeMode;
|
final ThemeMode themeMode;
|
||||||
final LatLngBounds bounds;
|
final LatLngBounds bounds;
|
||||||
@@ -16,6 +32,7 @@ class MapState {
|
|||||||
final bool includeArchived;
|
final bool includeArchived;
|
||||||
final bool withPartners;
|
final bool withPartners;
|
||||||
final int relativeDays;
|
final int relativeDays;
|
||||||
|
final CustomTimeRange customTimeRange;
|
||||||
|
|
||||||
const MapState({
|
const MapState({
|
||||||
this.themeMode = ThemeMode.system,
|
this.themeMode = ThemeMode.system,
|
||||||
@@ -24,6 +41,7 @@ class MapState {
|
|||||||
this.includeArchived = false,
|
this.includeArchived = false,
|
||||||
this.withPartners = false,
|
this.withPartners = false,
|
||||||
this.relativeDays = 0,
|
this.relativeDays = 0,
|
||||||
|
this.customTimeRange = const CustomTimeRange(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -41,6 +59,7 @@ class MapState {
|
|||||||
bool? includeArchived,
|
bool? includeArchived,
|
||||||
bool? withPartners,
|
bool? withPartners,
|
||||||
int? relativeDays,
|
int? relativeDays,
|
||||||
|
CustomTimeRange? customTimeRange,
|
||||||
}) {
|
}) {
|
||||||
return MapState(
|
return MapState(
|
||||||
bounds: bounds ?? this.bounds,
|
bounds: bounds ?? this.bounds,
|
||||||
@@ -49,6 +68,7 @@ class MapState {
|
|||||||
includeArchived: includeArchived ?? this.includeArchived,
|
includeArchived: includeArchived ?? this.includeArchived,
|
||||||
withPartners: withPartners ?? this.withPartners,
|
withPartners: withPartners ?? this.withPartners,
|
||||||
relativeDays: relativeDays ?? this.relativeDays,
|
relativeDays: relativeDays ?? this.relativeDays,
|
||||||
|
customTimeRange: customTimeRange ?? this.customTimeRange,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +78,7 @@ class MapState {
|
|||||||
includeArchived: includeArchived,
|
includeArchived: includeArchived,
|
||||||
withPartners: withPartners,
|
withPartners: withPartners,
|
||||||
relativeDays: relativeDays,
|
relativeDays: relativeDays,
|
||||||
|
customTimeRange: customTimeRange,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,9 +125,22 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setCustomTimeRange(CustomTimeRange range) {
|
||||||
|
ref
|
||||||
|
.read(appSettingsServiceProvider)
|
||||||
|
.setSetting(AppSettingsEnum.mapCustomFrom, range.from == null ? "" : range.from!.toIso8601String());
|
||||||
|
ref
|
||||||
|
.read(appSettingsServiceProvider)
|
||||||
|
.setSetting(AppSettingsEnum.mapCustomTo, range.to == null ? "" : range.to!.toIso8601String());
|
||||||
|
state = state.copyWith(customTimeRange: range);
|
||||||
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MapState build() {
|
MapState build() {
|
||||||
final appSettingsService = ref.read(appSettingsServiceProvider);
|
final appSettingsService = ref.read(appSettingsServiceProvider);
|
||||||
|
final customFrom = appSettingsService.getSetting(AppSettingsEnum.mapCustomFrom);
|
||||||
|
final customTo = appSettingsService.getSetting(AppSettingsEnum.mapCustomTo);
|
||||||
return MapState(
|
return MapState(
|
||||||
themeMode: ThemeMode.values[appSettingsService.getSetting(AppSettingsEnum.mapThemeMode)],
|
themeMode: ThemeMode.values[appSettingsService.getSetting(AppSettingsEnum.mapThemeMode)],
|
||||||
onlyFavorites: appSettingsService.getSetting(AppSettingsEnum.mapShowFavoriteOnly),
|
onlyFavorites: appSettingsService.getSetting(AppSettingsEnum.mapShowFavoriteOnly),
|
||||||
@@ -114,6 +148,10 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||||||
withPartners: appSettingsService.getSetting(AppSettingsEnum.mapwithPartners),
|
withPartners: appSettingsService.getSetting(AppSettingsEnum.mapwithPartners),
|
||||||
relativeDays: appSettingsService.getSetting(AppSettingsEnum.mapRelativeDate),
|
relativeDays: appSettingsService.getSetting(AppSettingsEnum.mapRelativeDate),
|
||||||
bounds: LatLngBounds(northeast: const LatLng(0, 0), southwest: const LatLng(0, 0)),
|
bounds: LatLngBounds(northeast: const LatLng(0, 0), southwest: const LatLng(0, 0)),
|
||||||
|
customTimeRange: CustomTimeRange(
|
||||||
|
from: customFrom.isNotEmpty ? DateTime.parse(customFrom) : null,
|
||||||
|
to: customTo.isNotEmpty ? DateTime.parse(customTo) : null,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,35 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||||
|
import 'package:immich_mobile/widgets/map/map_settings/map_custom_time_range.dart';
|
||||||
import 'package:immich_mobile/widgets/map/map_settings/map_settings_list_tile.dart';
|
import 'package:immich_mobile/widgets/map/map_settings/map_settings_list_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/map/map_settings/map_settings_time_dropdown.dart';
|
import 'package:immich_mobile/widgets/map/map_settings/map_settings_time_dropdown.dart';
|
||||||
import 'package:immich_mobile/widgets/map/map_settings/map_theme_picker.dart';
|
import 'package:immich_mobile/widgets/map/map_settings/map_theme_picker.dart';
|
||||||
|
|
||||||
class DriftMapSettingsSheet extends HookConsumerWidget {
|
class DriftMapSettingsSheet extends ConsumerStatefulWidget {
|
||||||
const DriftMapSettingsSheet({super.key});
|
const DriftMapSettingsSheet({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState<DriftMapSettingsSheet> createState() => _DriftMapSettingsSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DriftMapSettingsSheetState extends ConsumerState<DriftMapSettingsSheet> {
|
||||||
|
late bool useCustomRange;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final mapState = ref.read(mapStateProvider);
|
||||||
|
useCustomRange = mapState.customTimeRange.isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
final mapState = ref.watch(mapStateProvider);
|
final mapState = ref.watch(mapStateProvider);
|
||||||
|
|
||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
expand: false,
|
expand: false,
|
||||||
initialChildSize: 0.6,
|
initialChildSize: useCustomRange ? 0.7 : 0.6,
|
||||||
builder: (ctx, scrollController) => SingleChildScrollView(
|
builder: (ctx, scrollController) => SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
child: Card(
|
child: Card(
|
||||||
@@ -47,10 +62,41 @@ class DriftMapSettingsSheet extends HookConsumerWidget {
|
|||||||
selected: mapState.withPartners,
|
selected: mapState.withPartners,
|
||||||
onChanged: (withPartners) => ref.read(mapStateProvider.notifier).switchWithPartners(withPartners),
|
onChanged: (withPartners) => ref.read(mapStateProvider.notifier).switchWithPartners(withPartners),
|
||||||
),
|
),
|
||||||
MapTimeDropDown(
|
if (useCustomRange) ...[
|
||||||
relativeTime: mapState.relativeDays,
|
MapCustomTimeRange(
|
||||||
onTimeChange: (time) => ref.read(mapStateProvider.notifier).setRelativeTime(time),
|
customTimeRange: mapState.customTimeRange,
|
||||||
),
|
onChanged: (range) {
|
||||||
|
ref.read(mapStateProvider.notifier).setCustomTimeRange(range);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
useCustomRange = false;
|
||||||
|
ref.read(mapStateProvider.notifier).setRelativeTime(0);
|
||||||
|
ref.read(mapStateProvider.notifier).setCustomTimeRange(const CustomTimeRange());
|
||||||
|
}),
|
||||||
|
child: Text("remove_custom_date_range".t(context: context)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] else ...[
|
||||||
|
MapTimeDropDown(
|
||||||
|
relativeTime: mapState.relativeDays,
|
||||||
|
onTimeChange: (time) => ref.read(mapStateProvider.notifier).setRelativeTime(time),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
useCustomRange = true;
|
||||||
|
ref.read(mapStateProvider.notifier).setRelativeTime(0);
|
||||||
|
ref.read(mapStateProvider.notifier).setCustomTimeRange(const CustomTimeRange());
|
||||||
|
}),
|
||||||
|
child: Text("use_custom_date_range".t(context: context)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ enum AppSettingsEnum<T> {
|
|||||||
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
|
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
|
||||||
mapwithPartners<bool>(StoreKey.mapwithPartners, null, false),
|
mapwithPartners<bool>(StoreKey.mapwithPartners, null, false),
|
||||||
mapRelativeDate<int>(StoreKey.mapRelativeDate, null, 0),
|
mapRelativeDate<int>(StoreKey.mapRelativeDate, null, 0),
|
||||||
|
mapCustomFrom<String>(StoreKey.mapCustomFrom, null, ""),
|
||||||
|
mapCustomTo<String>(StoreKey.mapCustomTo, null, ""),
|
||||||
allowSelfSignedSSLCert<bool>(StoreKey.selfSignedCert, null, false),
|
allowSelfSignedSSLCert<bool>(StoreKey.selfSignedCert, null, false),
|
||||||
ignoreIcloudAssets<bool>(StoreKey.ignoreIcloudAssets, null, false),
|
ignoreIcloudAssets<bool>(StoreKey.ignoreIcloudAssets, null, false),
|
||||||
selectedAlbumSortReverse<bool>(StoreKey.selectedAlbumSortReverse, null, true),
|
selectedAlbumSortReverse<bool>(StoreKey.selectedAlbumSortReverse, null, true),
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class MapCustomTimeRange extends StatelessWidget {
|
||||||
|
const MapCustomTimeRange({super.key, required this.customTimeRange, required this.onChanged});
|
||||||
|
|
||||||
|
final CustomTimeRange customTimeRange;
|
||||||
|
final Function(CustomTimeRange) onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text("date_after".t(context: context)),
|
||||||
|
subtitle: Text(
|
||||||
|
customTimeRange.from != null
|
||||||
|
? DateFormat.yMMMd().add_jm().format(customTimeRange.from!)
|
||||||
|
: "not_set".t(context: context),
|
||||||
|
),
|
||||||
|
trailing: customTimeRange.from != null
|
||||||
|
? IconButton(icon: const Icon(Icons.close), onPressed: () => onChanged(customTimeRange.clearFrom()))
|
||||||
|
: null,
|
||||||
|
onTap: () async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: customTimeRange.from ?? DateTime.now(),
|
||||||
|
firstDate: DateTime(1970),
|
||||||
|
lastDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
onChanged(customTimeRange.copyWith(from: picked));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text("date_before".t(context: context)),
|
||||||
|
subtitle: Text(
|
||||||
|
customTimeRange.to != null
|
||||||
|
? DateFormat.yMMMd().add_jm().format(customTimeRange.to!)
|
||||||
|
: "not_set".t(context: context),
|
||||||
|
),
|
||||||
|
trailing: customTimeRange.to != null
|
||||||
|
? IconButton(icon: const Icon(Icons.close), onPressed: () => onChanged(customTimeRange.clearTo()))
|
||||||
|
: null,
|
||||||
|
onTap: () async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: customTimeRange.to ?? DateTime.now(),
|
||||||
|
firstDate: DateTime(1970),
|
||||||
|
lastDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
onChanged(customTimeRange.copyWith(to: picked));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user