From 9e063c993c36f814c48ee0dd1fbebfaa24a0536d Mon Sep 17 00:00:00 2001
From: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>
Date: Mon, 21 Apr 2025 00:54:37 -0400
Subject: [PATCH 001/102] fix(docs): Database dump warnings (#17676)
* docs
* admin page
* roadmap
* whitespace
* whitespace
* no danger
---
.../docs/administration/backup-and-restore.md | 31 ++++++++++++-------
docs/src/pages/roadmap.tsx | 4 +--
i18n/en.json | 10 +++---
3 files changed, 27 insertions(+), 18 deletions(-)
diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md
index 817a7dca6d..7e55e4e88f 100644
--- a/docs/docs/administration/backup-and-restore.md
+++ b/docs/docs/administration/backup-and-restore.md
@@ -23,23 +23,32 @@ Refer to the official [postgres documentation](https://www.postgresql.org/docs/c
It is not recommended to directly backup the `DB_DATA_LOCATION` folder. Doing so while the database is running can lead to a corrupted backup that cannot be restored.
:::
-### Automatic Database Backups
+### Automatic Database Dumps
-For convenience, Immich will automatically create database backups by default. The backups are stored in `UPLOAD_LOCATION/backups`.
-As mentioned above, you should make your own backup of these together with the asset folders as noted below.
-You can adjust the schedule and amount of kept backups in the [admin settings](http://my.immich.app/admin/system-settings?isOpen=backup).
-By default, Immich will keep the last 14 backups and create a new backup every day at 2:00 AM.
+:::warning
+The automatic database dumps can be used to restore the database in the event of damage to the Postgres database files.
+There is no monitoring for these dumps and you will not be notified if they are unsuccessful.
+:::
-#### Trigger Backup
+:::caution
+The database dumps do **NOT** contain any pictures or videos, only metadata. They are only usable with a copy of the other files in `UPLOAD_LOCATION` as outlined below.
+:::
-You are able to trigger a backup in the [admin job status page](http://my.immich.app/admin/jobs-status).
-Visit the page, open the "Create job" modal from the top right, select "Backup Database" and click "Confirm".
-A job will run and trigger a backup, you can verify this worked correctly by checking the logs or the backup folder.
-This backup will count towards the last X backups that will be kept based on your settings.
+For disaster-recovery purposes, Immich will automatically create database dumps. The dumps are stored in `UPLOAD_LOCATION/backups`.
+Please be sure to make your own, independent backup of the database together with the asset folders as noted below.
+You can adjust the schedule and amount of kept database dumps in the [admin settings](http://my.immich.app/admin/system-settings?isOpen=backup).
+By default, Immich will keep the last 14 database dumps and create a new dump every day at 2:00 AM.
+
+#### Trigger Dump
+
+You are able to trigger a database dump in the [admin job status page](http://my.immich.app/admin/jobs-status).
+Visit the page, open the "Create job" modal from the top right, select "Create Database Dump" and click "Confirm".
+A job will run and trigger a dump, you can verify this worked correctly by checking the logs or the `backups/` folder.
+This dumps will count towards the last `X` dumps that will be kept based on your settings.
#### Restoring
-We hope to make restoring simpler in future versions, for now you can find the backups in the `UPLOAD_LOCATION/backups` folder on your host.
+We hope to make restoring simpler in future versions, for now you can find the database dumps in the `UPLOAD_LOCATION/backups` folder on your host.
Then please follow the steps in the following section for restoring the database.
### Manual Backup and Restore
diff --git a/docs/src/pages/roadmap.tsx b/docs/src/pages/roadmap.tsx
index b7ded8e8c9..205b976aed 100644
--- a/docs/src/pages/roadmap.tsx
+++ b/docs/src/pages/roadmap.tsx
@@ -266,8 +266,8 @@ const milestones: Item[] = [
withRelease({
icon: mdiDatabaseOutline,
iconColor: 'brown',
- title: 'Automatic database backups',
- description: 'Database backups are now integrated into the Immich server',
+ title: 'Automatic database dumps',
+ description: 'Database dumps are now integrated into the Immich server',
release: 'v1.120.0',
}),
{
diff --git a/i18n/en.json b/i18n/en.json
index c4b4746871..9951717de6 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -39,11 +39,11 @@
"authentication_settings_disable_all": "Are you sure you want to disable all login methods? Login will be completely disabled.",
"authentication_settings_reenable": "To re-enable, use a Server Command.",
"background_task_job": "Background Tasks",
- "backup_database": "Backup Database",
- "backup_database_enable_description": "Enable database backups",
- "backup_keep_last_amount": "Amount of previous backups to keep",
- "backup_settings": "Backup Settings",
- "backup_settings_description": "Manage database backup settings",
+ "backup_database": "Create Database Dump",
+ "backup_database_enable_description": "Enable database dumps",
+ "backup_keep_last_amount": "Amount of previous dumps to keep",
+ "backup_settings": "Database Dump Settings",
+ "backup_settings_description": "Manage database dump settings. Note: These jobs are not monitored and you will not be notified of failure.",
"check_all": "Check All",
"cleanup": "Cleanup",
"cleared_jobs": "Cleared jobs for: {job}",
From 21a6eb30ff7333dbdf4899cd05686c0bd6cfe535 Mon Sep 17 00:00:00 2001
From: aviv926 <51673860+aviv926@users.noreply.github.com>
Date: Mon, 21 Apr 2025 07:55:58 +0300
Subject: [PATCH 002/102] feat(docs): documentation update (#17720)
Documentation update
---
.../docs/features/ml-hardware-acceleration.md | 2 +-
docs/src/pages/roadmap.tsx | 20 ++++++++++++++++++-
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/docs/docs/features/ml-hardware-acceleration.md b/docs/docs/features/ml-hardware-acceleration.md
index 8371e726b9..a94f8c8c64 100644
--- a/docs/docs/features/ml-hardware-acceleration.md
+++ b/docs/docs/features/ml-hardware-acceleration.md
@@ -42,7 +42,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- The GPU must have compute capability 5.2 or greater.
- The server must have the official NVIDIA driver installed.
-- The installed driver must be >= 535 (it must support CUDA 12.2).
+- The installed driver must be >= 545 (it must support CUDA 12.3).
- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed.
#### ROCm
diff --git a/docs/src/pages/roadmap.tsx b/docs/src/pages/roadmap.tsx
index 205b976aed..4dc391cb27 100644
--- a/docs/src/pages/roadmap.tsx
+++ b/docs/src/pages/roadmap.tsx
@@ -76,6 +76,7 @@ import {
mdiWeb,
mdiDatabaseOutline,
mdiLinkEdit,
+ mdiTagFaces,
mdiMovieOpenPlayOutline,
} from '@mdi/js';
import Layout from '@theme/Layout';
@@ -83,6 +84,8 @@ import React from 'react';
import { Item, Timeline } from '../components/timeline';
const releases = {
+ 'v1.130.0': new Date(2025, 2, 25),
+ 'v1.127.0': new Date(2025, 1, 26),
'v1.122.0': new Date(2024, 11, 5),
'v1.120.0': new Date(2024, 10, 6),
'v1.114.0': new Date(2024, 8, 6),
@@ -242,6 +245,21 @@ const roadmap: Item[] = [
];
const milestones: Item[] = [
+ withRelease({
+ icon: mdiFolderMultiple,
+ iconColor: 'brown',
+ title: 'Folders view in the mobile app',
+ description: 'Browse your photos and videos in their folder structure inside the mobile app',
+ release: 'v1.130.0',
+ }),
+ withRelease({
+ icon: mdiTagFaces,
+ iconColor: 'teal',
+ title: 'Manual face tagging',
+ description:
+ 'Manually tag or remove faces in photos and videos, even when automatic detection misses or misidentifies them.',
+ release: 'v1.127.0',
+ }),
{
icon: mdiStar,
iconColor: 'gold',
@@ -300,7 +318,7 @@ const milestones: Item[] = [
withRelease({
icon: mdiFolderMultiple,
iconColor: 'brown',
- title: 'Folders',
+ title: 'Folders view',
description: 'Browse your photos and videos in their folder structure',
release: 'v1.113.0',
}),
From c49fd2065bcce54415066bb49a604f4a5fabb17e Mon Sep 17 00:00:00 2001
From: Yaros
Date: Mon, 21 Apr 2025 07:18:25 +0200
Subject: [PATCH 003/102] chore(mobile): bump ios deployment target (#17715)
* chore: bump ios deployment target
* podfile
---------
Co-authored-by: Alex
---
mobile/ios/Podfile | 4 ++--
mobile/ios/Podfile.lock | 4 ++--
mobile/ios/Runner.xcodeproj/project.pbxproj | 6 +++---
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/mobile/ios/Podfile b/mobile/ios/Podfile
index a98032db20..ca0166a382 100644
--- a/mobile/ios/Podfile
+++ b/mobile/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
+platform :ios, '14.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -45,7 +45,7 @@ post_install do |installer|
installer.generated_projects.each do |project|
project.targets.each do |target|
target.build_configurations.each do |config|
- config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
end
end
end
diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock
index 908ee84aed..9740d6aa52 100644
--- a/mobile/ios/Podfile.lock
+++ b/mobile/ios/Podfile.lock
@@ -224,7 +224,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
- background_downloader: b42a56120f5348bff70e74222f0e9e6f7f1a1537
+ background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
@@ -261,6 +261,6 @@ SPEC CHECKSUMS:
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
-PODFILE CHECKSUM: 03b7eead4ee77b9e778179eeb0f3b5513617451c
+PODFILE CHECKSUM: 7ce312f2beab01395db96f6969d90a447279cf45
COCOAPODS: 1.16.2
diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj
index 83c231d741..69f122cf17 100644
--- a/mobile/ios/Runner.xcodeproj/project.pbxproj
+++ b/mobile/ios/Runner.xcodeproj/project.pbxproj
@@ -546,7 +546,7 @@
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -690,7 +690,7 @@
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -720,7 +720,7 @@
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
From f0ff8581da6a8e67338f2123c8c24f7557058bf1 Mon Sep 17 00:00:00 2001
From: Yaros
Date: Mon, 21 Apr 2025 07:55:13 +0200
Subject: [PATCH 004/102] feat(mobile): map improvements (#17714)
* fix: remove unnecessary db operations in map
* feat: use user's location for map thumbnails
* chore: refactored handleMapEvents
* fix: location fails fetching & update geolocator
* chore: minor refactor
* chore: small style tweak
---------
Co-authored-by: Alex
---
mobile/lib/pages/library/library.page.dart | 145 +++++++++++-------
.../places/places_collection.page.dart | 15 +-
mobile/lib/pages/search/map/map.page.dart | 16 +-
.../search/map/map_location_picker.page.dart | 2 +-
mobile/lib/routing/router.gr.dart | 72 ++++++++-
mobile/lib/utils/map_utils.dart | 21 ++-
mobile/lib/widgets/map/map_asset_grid.dart | 57 +++++--
.../widgets/search/search_map_thumbnail.dart | 2 +-
mobile/pubspec.lock | 12 +-
mobile/pubspec.yaml | 2 +-
10 files changed, 240 insertions(+), 104 deletions(-)
diff --git a/mobile/lib/pages/library/library.page.dart b/mobile/lib/pages/library/library.page.dart
index 1852fb7877..c08a1c715d 100644
--- a/mobile/lib/pages/library/library.page.dart
+++ b/mobile/lib/pages/library/library.page.dart
@@ -1,6 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
+import 'package:geolocator/geolocator.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
@@ -12,6 +13,7 @@ import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
+import 'package:immich_mobile/utils/map_utils.dart';
import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart';
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
import 'package:immich_mobile/widgets/common/user_avatar.dart';
@@ -297,32 +299,34 @@ class LocalAlbumsCollectionCard extends HookConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Container(
+ SizedBox(
height: size,
width: size,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20),
- gradient: LinearGradient(
- colors: [
- context.colorScheme.primary.withAlpha(30),
- context.colorScheme.primary.withAlpha(25),
- ],
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ gradient: LinearGradient(
+ colors: [
+ context.colorScheme.primary.withAlpha(30),
+ context.colorScheme.primary.withAlpha(25),
+ ],
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ ),
+ ),
+ child: GridView.count(
+ crossAxisCount: 2,
+ padding: const EdgeInsets.all(12),
+ crossAxisSpacing: 8,
+ mainAxisSpacing: 8,
+ physics: const NeverScrollableScrollPhysics(),
+ children: albums.take(4).map((album) {
+ return AlbumThumbnailCard(
+ album: album,
+ showTitle: false,
+ );
+ }).toList(),
),
- ),
- child: GridView.count(
- crossAxisCount: 2,
- padding: const EdgeInsets.all(12),
- crossAxisSpacing: 8,
- mainAxisSpacing: 8,
- physics: const NeverScrollableScrollPhysics(),
- children: albums.take(4).map((album) {
- return AlbumThumbnailCard(
- album: album,
- showTitle: false,
- );
- }).toList(),
),
),
Padding(
@@ -353,43 +357,66 @@ class PlacesCollectionCard extends StatelessWidget {
final widthFactor = isTablet ? 0.25 : 0.5;
final size = context.width * widthFactor - 20.0;
- return GestureDetector(
- onTap: () => context.pushRoute(const PlacesCollectionRoute()),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- height: size,
- width: size,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(20),
- color: context.colorScheme.secondaryContainer.withAlpha(100),
- ),
- child: IgnorePointer(
- child: MapThumbnail(
- zoom: 8,
- centre: const LatLng(
- 21.44950,
- -157.91959,
- ),
- showAttribution: false,
- themeMode:
- context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
- ),
- ),
- ),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(
- 'places'.tr(),
- style: context.textTheme.titleSmall?.copyWith(
- color: context.colorScheme.onSurface,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- ],
+ return FutureBuilder<(Position?, LocationPermission?)>(
+ future: MapUtils.checkPermAndGetLocation(
+ context: context,
+ silent: true,
),
+ builder: (context, snapshot) {
+ var position = snapshot.data?.$1;
+ return GestureDetector(
+ onTap: () => context.pushRoute(
+ PlacesCollectionRoute(
+ currentLocation: position != null
+ ? LatLng(position.latitude, position.longitude)
+ : null,
+ ),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ height: size,
+ width: size,
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ borderRadius:
+ const BorderRadius.all(Radius.circular(20)),
+ color: context.colorScheme.secondaryContainer
+ .withAlpha(100),
+ ),
+ child: IgnorePointer(
+ child: snapshot.connectionState ==
+ ConnectionState.waiting
+ ? const Center(child: CircularProgressIndicator())
+ : MapThumbnail(
+ zoom: 8,
+ centre: LatLng(
+ position?.latitude ?? 21.44950,
+ position?.longitude ?? -157.91959,
+ ),
+ showAttribution: false,
+ themeMode: context.isDarkTheme
+ ? ThemeMode.dark
+ : ThemeMode.light,
+ ),
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ 'places'.tr(),
+ style: context.textTheme.titleSmall?.copyWith(
+ color: context.colorScheme.onSurface,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
);
},
);
diff --git a/mobile/lib/pages/library/places/places_collection.page.dart b/mobile/lib/pages/library/places/places_collection.page.dart
index f9a2d4292c..5f2dea0dec 100644
--- a/mobile/lib/pages/library/places/places_collection.page.dart
+++ b/mobile/lib/pages/library/places/places_collection.page.dart
@@ -19,7 +19,8 @@ import 'package:maplibre_gl/maplibre_gl.dart';
@RoutePage()
class PlacesCollectionPage extends HookConsumerWidget {
- const PlacesCollectionPage({super.key});
+ const PlacesCollectionPage({super.key, this.currentLocation});
+ final LatLng? currentLocation;
@override
Widget build(BuildContext context, WidgetRef ref) {
final places = ref.watch(getAllPlacesProvider);
@@ -58,12 +59,14 @@ class PlacesCollectionPage extends HookConsumerWidget {
height: 200,
width: context.width,
child: MapThumbnail(
- onTap: (_, __) => context.pushRoute(const MapRoute()),
+ onTap: (_, __) => context
+ .pushRoute(MapRoute(initialLocation: currentLocation)),
zoom: 8,
- centre: const LatLng(
- 21.44950,
- -157.91959,
- ),
+ centre: currentLocation ??
+ const LatLng(
+ 21.44950,
+ -157.91959,
+ ),
showAttribution: false,
themeMode:
context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
diff --git a/mobile/lib/pages/search/map/map.page.dart b/mobile/lib/pages/search/map/map.page.dart
index 0e64759241..b80b96f94f 100644
--- a/mobile/lib/pages/search/map/map.page.dart
+++ b/mobile/lib/pages/search/map/map.page.dart
@@ -34,7 +34,8 @@ import 'package:maplibre_gl/maplibre_gl.dart';
@RoutePage()
class MapPage extends HookConsumerWidget {
- const MapPage({super.key});
+ const MapPage({super.key, this.initialLocation});
+ final LatLng? initialLocation;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -235,7 +236,8 @@ class MapPage extends HookConsumerWidget {
}
void onZoomToLocation() async {
- final (location, error) = await MapUtils.checkPermAndGetLocation(context);
+ final (location, error) =
+ await MapUtils.checkPermAndGetLocation(context: context);
if (error != null) {
if (error == LocationPermission.unableToDetermine && context.mounted) {
ImmichToast.show(
@@ -272,6 +274,7 @@ class MapPage extends HookConsumerWidget {
body: Stack(
children: [
_MapWithMarker(
+ initialLocation: initialLocation,
style: style,
selectedMarker: selectedMarker,
onMapCreated: onMapCreated,
@@ -303,6 +306,7 @@ class MapPage extends HookConsumerWidget {
body: Stack(
children: [
_MapWithMarker(
+ initialLocation: initialLocation,
style: style,
selectedMarker: selectedMarker,
onMapCreated: onMapCreated,
@@ -368,6 +372,7 @@ class _MapWithMarker extends StatelessWidget {
final OnStyleLoadedCallback onStyleLoaded;
final Function()? onMarkerTapped;
final ValueNotifier<_AssetMarkerMeta?> selectedMarker;
+ final LatLng? initialLocation;
const _MapWithMarker({
required this.style,
@@ -377,6 +382,7 @@ class _MapWithMarker extends StatelessWidget {
required this.onStyleLoaded,
required this.selectedMarker,
this.onMarkerTapped,
+ this.initialLocation,
});
@override
@@ -389,8 +395,10 @@ class _MapWithMarker extends StatelessWidget {
children: [
style.widgetWhen(
onData: (style) => MapLibreMap(
- initialCameraPosition:
- const CameraPosition(target: LatLng(0, 0)),
+ initialCameraPosition: CameraPosition(
+ target: initialLocation ?? const LatLng(0, 0),
+ zoom: initialLocation != null ? 12 : 0,
+ ),
styleString: style,
// This is needed to update the selectedMarker's position on map camera updates
// The changes are notified through the mapController ValueListener which is added in [onMapCreated]
diff --git a/mobile/lib/pages/search/map/map_location_picker.page.dart b/mobile/lib/pages/search/map/map_location_picker.page.dart
index 9d526d8080..f27deae052 100644
--- a/mobile/lib/pages/search/map/map_location_picker.page.dart
+++ b/mobile/lib/pages/search/map/map_location_picker.page.dart
@@ -46,7 +46,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
Future getCurrentLocation() async {
var (currentLocation, _) =
- await MapUtils.checkPermAndGetLocation(context);
+ await MapUtils.checkPermAndGetLocation(context: context);
if (currentLocation == null) {
return;
diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart
index a78371e05e..89e83e8159 100644
--- a/mobile/lib/routing/router.gr.dart
+++ b/mobile/lib/routing/router.gr.dart
@@ -1024,10 +1024,17 @@ class MapLocationPickerRouteArgs {
/// generated route for
/// [MapPage]
-class MapRoute extends PageRouteInfo {
- const MapRoute({List? children})
- : super(
+class MapRoute extends PageRouteInfo {
+ MapRoute({
+ Key? key,
+ LatLng? initialLocation,
+ List? children,
+ }) : super(
MapRoute.name,
+ args: MapRouteArgs(
+ key: key,
+ initialLocation: initialLocation,
+ ),
initialChildren: children,
);
@@ -1036,11 +1043,32 @@ class MapRoute extends PageRouteInfo {
static PageInfo page = PageInfo(
name,
builder: (data) {
- return const MapPage();
+ final args =
+ data.argsAs(orElse: () => const MapRouteArgs());
+ return MapPage(
+ key: args.key,
+ initialLocation: args.initialLocation,
+ );
},
);
}
+class MapRouteArgs {
+ const MapRouteArgs({
+ this.key,
+ this.initialLocation,
+ });
+
+ final Key? key;
+
+ final LatLng? initialLocation;
+
+ @override
+ String toString() {
+ return 'MapRouteArgs{key: $key, initialLocation: $initialLocation}';
+ }
+}
+
/// generated route for
/// [MemoryPage]
class MemoryRoute extends PageRouteInfo {
@@ -1333,10 +1361,17 @@ class PhotosRoute extends PageRouteInfo {
/// generated route for
/// [PlacesCollectionPage]
-class PlacesCollectionRoute extends PageRouteInfo {
- const PlacesCollectionRoute({List? children})
- : super(
+class PlacesCollectionRoute extends PageRouteInfo {
+ PlacesCollectionRoute({
+ Key? key,
+ LatLng? currentLocation,
+ List? children,
+ }) : super(
PlacesCollectionRoute.name,
+ args: PlacesCollectionRouteArgs(
+ key: key,
+ currentLocation: currentLocation,
+ ),
initialChildren: children,
);
@@ -1345,11 +1380,32 @@ class PlacesCollectionRoute extends PageRouteInfo {
static PageInfo page = PageInfo(
name,
builder: (data) {
- return const PlacesCollectionPage();
+ final args = data.argsAs(
+ orElse: () => const PlacesCollectionRouteArgs());
+ return PlacesCollectionPage(
+ key: args.key,
+ currentLocation: args.currentLocation,
+ );
},
);
}
+class PlacesCollectionRouteArgs {
+ const PlacesCollectionRouteArgs({
+ this.key,
+ this.currentLocation,
+ });
+
+ final Key? key;
+
+ final LatLng? currentLocation;
+
+ @override
+ String toString() {
+ return 'PlacesCollectionRouteArgs{key: $key, currentLocation: $currentLocation}';
+ }
+}
+
/// generated route for
/// [RecentlyAddedPage]
class RecentlyAddedRoute extends PageRouteInfo {
diff --git a/mobile/lib/utils/map_utils.dart b/mobile/lib/utils/map_utils.dart
index 44f7ebf271..df1ff28d8f 100644
--- a/mobile/lib/utils/map_utils.dart
+++ b/mobile/lib/utils/map_utils.dart
@@ -64,12 +64,13 @@ class MapUtils {
'features': markers.map(_addFeature).toList(),
};
- static Future<(Position?, LocationPermission?)> checkPermAndGetLocation(
- BuildContext context,
- ) async {
+ static Future<(Position?, LocationPermission?)> checkPermAndGetLocation({
+ required BuildContext context,
+ bool silent = false,
+ }) async {
try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
- if (!serviceEnabled) {
+ if (!serviceEnabled && !silent) {
showDialog(
context: context,
builder: (context) => _LocationServiceDisabledDialog(),
@@ -80,7 +81,7 @@ class MapUtils {
LocationPermission permission = await Geolocator.checkPermission();
bool shouldRequestPermission = false;
- if (permission == LocationPermission.denied) {
+ if (permission == LocationPermission.denied && !silent) {
shouldRequestPermission = await showDialog(
context: context,
builder: (context) => _LocationPermissionDisabledDialog(),
@@ -94,15 +95,19 @@ class MapUtils {
permission == LocationPermission.deniedForever) {
// Open app settings only if you did not request for permission before
if (permission == LocationPermission.deniedForever &&
- !shouldRequestPermission) {
+ !shouldRequestPermission &&
+ !silent) {
await Geolocator.openAppSettings();
}
return (null, LocationPermission.deniedForever);
}
Position currentUserLocation = await Geolocator.getCurrentPosition(
- desiredAccuracy: LocationAccuracy.medium,
- timeLimit: const Duration(seconds: 5),
+ locationSettings: const LocationSettings(
+ accuracy: LocationAccuracy.high,
+ distanceFilter: 0,
+ timeLimit: Duration(seconds: 5),
+ ),
);
return (currentUserLocation, null);
} catch (error, stack) {
diff --git a/mobile/lib/widgets/map/map_asset_grid.dart b/mobile/lib/widgets/map/map_asset_grid.dart
index 18003cf293..a9ddc86df9 100644
--- a/mobile/lib/widgets/map/map_asset_grid.dart
+++ b/mobile/lib/widgets/map/map_asset_grid.dart
@@ -46,12 +46,39 @@ class MapAssetGrid extends HookConsumerWidget {
final gridScrollThrottler =
useThrottler(interval: const Duration(milliseconds: 300));
+ // Add a cache for assets we've already loaded
+ final assetCache = useRef