Compare commits

...

4 Commits

Author SHA1 Message Date
Yaros
2424952b9a refactor: add back setRelativeTime 2026-02-19 14:11:41 +01:00
Yaros
733100f6ec refactor: rename customtimerange variables 2026-02-19 14:08:50 +01:00
Yaros
b0f6d5cf38 refactor: rename timerange & remove isvalid 2026-02-19 13:23:40 +01:00
Yaros
39d2e14d3a feat(mobile): custom date range for map 2026-02-14 09:56:09 +01:00
8 changed files with 201 additions and 14 deletions

View File

@@ -1619,6 +1619,7 @@
"not_available": "N/A",
"not_in_any_album": "Not in any album",
"not_selected": "Not selected",
"not_set": "Not set",
"notes": "Notes",
"nothing_here_yet": "Nothing here yet",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",

View File

@@ -73,6 +73,10 @@ enum StoreKey<T> {
autoPlayVideo<bool>._(139),
albumGridView<bool>._(140),
// Map custom time range settings
mapCustomFrom<String>._(141),
mapCustomTo<String>._(142),
// Experimental stuff
photoManagerCustomFilter<bool>._(1000),
betaPromptShown<bool>._(1001),

View File

@@ -27,9 +27,19 @@ class DriftMapRepository extends DriftDatabaseRepository {
condition = condition & _db.remoteAssetEntity.isFavorite.equals(true);
}
if (options.relativeDays != 0) {
final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays));
condition = condition & _db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate);
final from = options.timeRange.from;
final to = options.timeRange.to;
if (from != null || to != null) {
if (from != null) {
condition = condition & _db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(from);
}
if (to != null) {
condition = condition & _db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(to);
}
} else if (options.relativeDays > 0) {
final fromDate = DateTime.now().subtract(Duration(days: options.relativeDays));
condition = condition & _db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(fromDate);
}
return condition;

View File

@@ -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/repositories/db.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:stream_transform/stream_transform.dart';
@@ -21,6 +22,7 @@ class TimelineMapOptions {
final bool includeArchived;
final bool withPartners;
final int relativeDays;
final TimeRange timeRange;
const TimelineMapOptions({
required this.bounds,
@@ -28,6 +30,7 @@ class TimelineMapOptions {
this.includeArchived = false,
this.withPartners = false,
this.relativeDays = 0,
this.timeRange = const TimeRange(),
});
}
@@ -528,7 +531,19 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
query.where(_db.remoteAssetEntity.isFavorite.equals(true));
}
if (options.relativeDays != 0) {
final from = options.timeRange.from;
final to = options.timeRange.to;
if (from != null || to != null) {
// Use custom from/to filters
if (from != null) {
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(from));
}
if (to != null) {
query.where(_db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(to));
}
} else if (options.relativeDays > 0) {
// Use relative days
final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays));
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate));
}
@@ -570,7 +585,19 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
query.where(_db.remoteAssetEntity.isFavorite.equals(true));
}
if (options.relativeDays != 0) {
final from = options.timeRange.from;
final to = options.timeRange.to;
if (from != null || to != null) {
// Use custom from/to filters
if (from != null) {
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(from));
}
if (to != null) {
query.where(_db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(to));
}
} else if (options.relativeDays > 0) {
// Use relative days
final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays));
query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate));
}

View File

@@ -9,6 +9,20 @@ import 'package:immich_mobile/providers/map/map_state.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class TimeRange {
final DateTime? from;
final DateTime? to;
const TimeRange({this.from, this.to});
TimeRange copyWith({DateTime? from, DateTime? to}) {
return TimeRange(from: from ?? this.from, to: to ?? this.to);
}
TimeRange clearFrom() => TimeRange(to: to);
TimeRange clearTo() => TimeRange(from: from);
}
class MapState {
final ThemeMode themeMode;
final LatLngBounds bounds;
@@ -16,6 +30,7 @@ class MapState {
final bool includeArchived;
final bool withPartners;
final int relativeDays;
final TimeRange timeRange;
const MapState({
this.themeMode = ThemeMode.system,
@@ -24,6 +39,7 @@ class MapState {
this.includeArchived = false,
this.withPartners = false,
this.relativeDays = 0,
this.timeRange = const TimeRange(),
});
@override
@@ -41,6 +57,7 @@ class MapState {
bool? includeArchived,
bool? withPartners,
int? relativeDays,
TimeRange? timeRange,
}) {
return MapState(
bounds: bounds ?? this.bounds,
@@ -49,6 +66,7 @@ class MapState {
includeArchived: includeArchived ?? this.includeArchived,
withPartners: withPartners ?? this.withPartners,
relativeDays: relativeDays ?? this.relativeDays,
timeRange: timeRange ?? this.timeRange,
);
}
@@ -57,7 +75,7 @@ class MapState {
onlyFavorites: onlyFavorites,
includeArchived: includeArchived,
withPartners: withPartners,
relativeDays: relativeDays,
timeRange: timeRange,
);
}
@@ -104,16 +122,32 @@ class MapStateNotifier extends Notifier<MapState> {
EventStream.shared.emit(const MapMarkerReloadEvent());
}
void setTimeRange(TimeRange 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(timeRange: range);
EventStream.shared.emit(const MapMarkerReloadEvent());
}
@override
MapState build() {
final appSettingsService = ref.read(appSettingsServiceProvider);
final customFrom = appSettingsService.getSetting(AppSettingsEnum.mapCustomFrom);
final customTo = appSettingsService.getSetting(AppSettingsEnum.mapCustomTo);
return MapState(
themeMode: ThemeMode.values[appSettingsService.getSetting(AppSettingsEnum.mapThemeMode)],
onlyFavorites: appSettingsService.getSetting(AppSettingsEnum.mapShowFavoriteOnly),
includeArchived: appSettingsService.getSetting(AppSettingsEnum.mapIncludeArchived),
withPartners: appSettingsService.getSetting(AppSettingsEnum.mapwithPartners),
relativeDays: appSettingsService.getSetting(AppSettingsEnum.mapRelativeDate),
bounds: LatLngBounds(northeast: const LatLng(0, 0), southwest: const LatLng(0, 0)),
timeRange: TimeRange(
from: customFrom.isNotEmpty ? DateTime.parse(customFrom) : null,
to: customTo.isNotEmpty ? DateTime.parse(customTo) : null,
),
);
}
}

View File

@@ -2,20 +2,36 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.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_time_dropdown.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});
@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);
final timeRange = mapState.timeRange;
useCustomRange = timeRange.from != null || timeRange.to != null;
}
@override
Widget build(BuildContext context) {
final mapState = ref.watch(mapStateProvider);
return DraggableScrollableSheet(
expand: false,
initialChildSize: 0.6,
initialChildSize: useCustomRange ? 0.7 : 0.6,
builder: (ctx, scrollController) => SingleChildScrollView(
controller: scrollController,
child: Card(
@@ -47,10 +63,41 @@ class DriftMapSettingsSheet extends HookConsumerWidget {
selected: mapState.withPartners,
onChanged: (withPartners) => ref.read(mapStateProvider.notifier).switchWithPartners(withPartners),
),
MapTimeDropDown(
relativeTime: mapState.relativeDays,
onTimeChange: (time) => ref.read(mapStateProvider.notifier).setRelativeTime(time),
),
if (useCustomRange) ...[
MapTimeRange(
timeRange: mapState.timeRange,
onChanged: (range) {
ref.read(mapStateProvider.notifier).setTimeRange(range);
},
),
Align(
alignment: Alignment.centerLeft,
child: TextButton(
onPressed: () => setState(() {
useCustomRange = false;
ref.read(mapStateProvider.notifier).setRelativeTime(0);
ref.read(mapStateProvider.notifier).setTimeRange(const TimeRange());
}),
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).setTimeRange(const TimeRange());
}),
child: Text("use_custom_date_range".t(context: context)),
),
),
],
const SizedBox(height: 20),
],
),

View File

@@ -40,6 +40,8 @@ enum AppSettingsEnum<T> {
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
mapwithPartners<bool>(StoreKey.mapwithPartners, null, false),
mapRelativeDate<int>(StoreKey.mapRelativeDate, null, 0),
mapCustomFrom<String>(StoreKey.mapCustomFrom, null, ""),
mapCustomTo<String>(StoreKey.mapCustomTo, null, ""),
allowSelfSignedSSLCert<bool>(StoreKey.selfSignedCert, null, false),
ignoreIcloudAssets<bool>(StoreKey.ignoreIcloudAssets, null, false),
selectedAlbumSortReverse<bool>(StoreKey.selectedAlbumSortReverse, null, true),

View File

@@ -0,0 +1,62 @@
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 MapTimeRange extends StatelessWidget {
const MapTimeRange({super.key, required this.timeRange, required this.onChanged});
final TimeRange timeRange;
final Function(TimeRange) onChanged;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: Text("date_after".t(context: context)),
subtitle: Text(
timeRange.from != null
? DateFormat.yMMMd().add_jm().format(timeRange.from!)
: "not_set".t(context: context),
),
trailing: timeRange.from != null
? IconButton(icon: const Icon(Icons.close), onPressed: () => onChanged(timeRange.clearFrom()))
: null,
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: timeRange.from ?? DateTime.now(),
firstDate: DateTime(1970),
lastDate: DateTime.now(),
);
if (picked != null) {
onChanged(timeRange.copyWith(from: picked));
}
},
),
ListTile(
title: Text("date_before".t(context: context)),
subtitle: Text(
timeRange.to != null ? DateFormat.yMMMd().add_jm().format(timeRange.to!) : "not_set".t(context: context),
),
trailing: timeRange.to != null
? IconButton(icon: const Icon(Icons.close), onPressed: () => onChanged(timeRange.clearTo()))
: null,
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: timeRange.to ?? DateTime.now(),
firstDate: DateTime(1970),
lastDate: DateTime.now(),
);
if (picked != null) {
onChanged(timeRange.copyWith(to: picked));
}
},
),
],
);
}
}