mirror of
https://github.com/immich-app/immich.git
synced 2025-06-01 04:36:19 -04:00
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 <alex.tran1502@gmail.com>
This commit is contained in:
parent
c49fd2065b
commit
f0ff8581da
@ -1,6 +1,7 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.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/routing/router.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.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/album/album_thumbnail_card.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
|
import 'package:immich_mobile/widgets/common/immich_app_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/common/user_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_avatar.dart';
|
||||||
@ -297,32 +299,34 @@ class LocalAlbumsCollectionCard extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
SizedBox(
|
||||||
height: size,
|
height: size,
|
||||||
width: size,
|
width: size,
|
||||||
decoration: BoxDecoration(
|
child: DecoratedBox(
|
||||||
borderRadius: BorderRadius.circular(20),
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
colors: [
|
gradient: LinearGradient(
|
||||||
context.colorScheme.primary.withAlpha(30),
|
colors: [
|
||||||
context.colorScheme.primary.withAlpha(25),
|
context.colorScheme.primary.withAlpha(30),
|
||||||
],
|
context.colorScheme.primary.withAlpha(25),
|
||||||
begin: Alignment.topCenter,
|
],
|
||||||
end: Alignment.bottomCenter,
|
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(
|
Padding(
|
||||||
@ -353,43 +357,66 @@ class PlacesCollectionCard extends StatelessWidget {
|
|||||||
final widthFactor = isTablet ? 0.25 : 0.5;
|
final widthFactor = isTablet ? 0.25 : 0.5;
|
||||||
final size = context.width * widthFactor - 20.0;
|
final size = context.width * widthFactor - 20.0;
|
||||||
|
|
||||||
return GestureDetector(
|
return FutureBuilder<(Position?, LocationPermission?)>(
|
||||||
onTap: () => context.pushRoute(const PlacesCollectionRoute()),
|
future: MapUtils.checkPermAndGetLocation(
|
||||||
child: Column(
|
context: context,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
silent: true,
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -19,7 +19,8 @@ import 'package:maplibre_gl/maplibre_gl.dart';
|
|||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class PlacesCollectionPage extends HookConsumerWidget {
|
class PlacesCollectionPage extends HookConsumerWidget {
|
||||||
const PlacesCollectionPage({super.key});
|
const PlacesCollectionPage({super.key, this.currentLocation});
|
||||||
|
final LatLng? currentLocation;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final places = ref.watch(getAllPlacesProvider);
|
final places = ref.watch(getAllPlacesProvider);
|
||||||
@ -58,12 +59,14 @@ class PlacesCollectionPage extends HookConsumerWidget {
|
|||||||
height: 200,
|
height: 200,
|
||||||
width: context.width,
|
width: context.width,
|
||||||
child: MapThumbnail(
|
child: MapThumbnail(
|
||||||
onTap: (_, __) => context.pushRoute(const MapRoute()),
|
onTap: (_, __) => context
|
||||||
|
.pushRoute(MapRoute(initialLocation: currentLocation)),
|
||||||
zoom: 8,
|
zoom: 8,
|
||||||
centre: const LatLng(
|
centre: currentLocation ??
|
||||||
21.44950,
|
const LatLng(
|
||||||
-157.91959,
|
21.44950,
|
||||||
),
|
-157.91959,
|
||||||
|
),
|
||||||
showAttribution: false,
|
showAttribution: false,
|
||||||
themeMode:
|
themeMode:
|
||||||
context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||||
|
@ -34,7 +34,8 @@ import 'package:maplibre_gl/maplibre_gl.dart';
|
|||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class MapPage extends HookConsumerWidget {
|
class MapPage extends HookConsumerWidget {
|
||||||
const MapPage({super.key});
|
const MapPage({super.key, this.initialLocation});
|
||||||
|
final LatLng? initialLocation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -235,7 +236,8 @@ class MapPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onZoomToLocation() async {
|
void onZoomToLocation() async {
|
||||||
final (location, error) = await MapUtils.checkPermAndGetLocation(context);
|
final (location, error) =
|
||||||
|
await MapUtils.checkPermAndGetLocation(context: context);
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
if (error == LocationPermission.unableToDetermine && context.mounted) {
|
if (error == LocationPermission.unableToDetermine && context.mounted) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
@ -272,6 +274,7 @@ class MapPage extends HookConsumerWidget {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_MapWithMarker(
|
_MapWithMarker(
|
||||||
|
initialLocation: initialLocation,
|
||||||
style: style,
|
style: style,
|
||||||
selectedMarker: selectedMarker,
|
selectedMarker: selectedMarker,
|
||||||
onMapCreated: onMapCreated,
|
onMapCreated: onMapCreated,
|
||||||
@ -303,6 +306,7 @@ class MapPage extends HookConsumerWidget {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_MapWithMarker(
|
_MapWithMarker(
|
||||||
|
initialLocation: initialLocation,
|
||||||
style: style,
|
style: style,
|
||||||
selectedMarker: selectedMarker,
|
selectedMarker: selectedMarker,
|
||||||
onMapCreated: onMapCreated,
|
onMapCreated: onMapCreated,
|
||||||
@ -368,6 +372,7 @@ class _MapWithMarker extends StatelessWidget {
|
|||||||
final OnStyleLoadedCallback onStyleLoaded;
|
final OnStyleLoadedCallback onStyleLoaded;
|
||||||
final Function()? onMarkerTapped;
|
final Function()? onMarkerTapped;
|
||||||
final ValueNotifier<_AssetMarkerMeta?> selectedMarker;
|
final ValueNotifier<_AssetMarkerMeta?> selectedMarker;
|
||||||
|
final LatLng? initialLocation;
|
||||||
|
|
||||||
const _MapWithMarker({
|
const _MapWithMarker({
|
||||||
required this.style,
|
required this.style,
|
||||||
@ -377,6 +382,7 @@ class _MapWithMarker extends StatelessWidget {
|
|||||||
required this.onStyleLoaded,
|
required this.onStyleLoaded,
|
||||||
required this.selectedMarker,
|
required this.selectedMarker,
|
||||||
this.onMarkerTapped,
|
this.onMarkerTapped,
|
||||||
|
this.initialLocation,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -389,8 +395,10 @@ class _MapWithMarker extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
style.widgetWhen(
|
style.widgetWhen(
|
||||||
onData: (style) => MapLibreMap(
|
onData: (style) => MapLibreMap(
|
||||||
initialCameraPosition:
|
initialCameraPosition: CameraPosition(
|
||||||
const CameraPosition(target: LatLng(0, 0)),
|
target: initialLocation ?? const LatLng(0, 0),
|
||||||
|
zoom: initialLocation != null ? 12 : 0,
|
||||||
|
),
|
||||||
styleString: style,
|
styleString: style,
|
||||||
// This is needed to update the selectedMarker's position on map camera updates
|
// 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]
|
// The changes are notified through the mapController ValueListener which is added in [onMapCreated]
|
||||||
|
@ -46,7 +46,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
Future<void> getCurrentLocation() async {
|
Future<void> getCurrentLocation() async {
|
||||||
var (currentLocation, _) =
|
var (currentLocation, _) =
|
||||||
await MapUtils.checkPermAndGetLocation(context);
|
await MapUtils.checkPermAndGetLocation(context: context);
|
||||||
|
|
||||||
if (currentLocation == null) {
|
if (currentLocation == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -1024,10 +1024,17 @@ class MapLocationPickerRouteArgs {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [MapPage]
|
/// [MapPage]
|
||||||
class MapRoute extends PageRouteInfo<void> {
|
class MapRoute extends PageRouteInfo<MapRouteArgs> {
|
||||||
const MapRoute({List<PageRouteInfo>? children})
|
MapRoute({
|
||||||
: super(
|
Key? key,
|
||||||
|
LatLng? initialLocation,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
MapRoute.name,
|
MapRoute.name,
|
||||||
|
args: MapRouteArgs(
|
||||||
|
key: key,
|
||||||
|
initialLocation: initialLocation,
|
||||||
|
),
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1036,11 +1043,32 @@ class MapRoute extends PageRouteInfo<void> {
|
|||||||
static PageInfo page = PageInfo(
|
static PageInfo page = PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const MapPage();
|
final args =
|
||||||
|
data.argsAs<MapRouteArgs>(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
|
/// generated route for
|
||||||
/// [MemoryPage]
|
/// [MemoryPage]
|
||||||
class MemoryRoute extends PageRouteInfo<MemoryRouteArgs> {
|
class MemoryRoute extends PageRouteInfo<MemoryRouteArgs> {
|
||||||
@ -1333,10 +1361,17 @@ class PhotosRoute extends PageRouteInfo<void> {
|
|||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [PlacesCollectionPage]
|
/// [PlacesCollectionPage]
|
||||||
class PlacesCollectionRoute extends PageRouteInfo<void> {
|
class PlacesCollectionRoute extends PageRouteInfo<PlacesCollectionRouteArgs> {
|
||||||
const PlacesCollectionRoute({List<PageRouteInfo>? children})
|
PlacesCollectionRoute({
|
||||||
: super(
|
Key? key,
|
||||||
|
LatLng? currentLocation,
|
||||||
|
List<PageRouteInfo>? children,
|
||||||
|
}) : super(
|
||||||
PlacesCollectionRoute.name,
|
PlacesCollectionRoute.name,
|
||||||
|
args: PlacesCollectionRouteArgs(
|
||||||
|
key: key,
|
||||||
|
currentLocation: currentLocation,
|
||||||
|
),
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1345,11 +1380,32 @@ class PlacesCollectionRoute extends PageRouteInfo<void> {
|
|||||||
static PageInfo page = PageInfo(
|
static PageInfo page = PageInfo(
|
||||||
name,
|
name,
|
||||||
builder: (data) {
|
builder: (data) {
|
||||||
return const PlacesCollectionPage();
|
final args = data.argsAs<PlacesCollectionRouteArgs>(
|
||||||
|
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
|
/// generated route for
|
||||||
/// [RecentlyAddedPage]
|
/// [RecentlyAddedPage]
|
||||||
class RecentlyAddedRoute extends PageRouteInfo<void> {
|
class RecentlyAddedRoute extends PageRouteInfo<void> {
|
||||||
|
@ -64,12 +64,13 @@ class MapUtils {
|
|||||||
'features': markers.map(_addFeature).toList(),
|
'features': markers.map(_addFeature).toList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
static Future<(Position?, LocationPermission?)> checkPermAndGetLocation(
|
static Future<(Position?, LocationPermission?)> checkPermAndGetLocation({
|
||||||
BuildContext context,
|
required BuildContext context,
|
||||||
) async {
|
bool silent = false,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||||
if (!serviceEnabled) {
|
if (!serviceEnabled && !silent) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _LocationServiceDisabledDialog(),
|
builder: (context) => _LocationServiceDisabledDialog(),
|
||||||
@ -80,7 +81,7 @@ class MapUtils {
|
|||||||
LocationPermission permission = await Geolocator.checkPermission();
|
LocationPermission permission = await Geolocator.checkPermission();
|
||||||
bool shouldRequestPermission = false;
|
bool shouldRequestPermission = false;
|
||||||
|
|
||||||
if (permission == LocationPermission.denied) {
|
if (permission == LocationPermission.denied && !silent) {
|
||||||
shouldRequestPermission = await showDialog(
|
shouldRequestPermission = await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _LocationPermissionDisabledDialog(),
|
builder: (context) => _LocationPermissionDisabledDialog(),
|
||||||
@ -94,15 +95,19 @@ class MapUtils {
|
|||||||
permission == LocationPermission.deniedForever) {
|
permission == LocationPermission.deniedForever) {
|
||||||
// Open app settings only if you did not request for permission before
|
// Open app settings only if you did not request for permission before
|
||||||
if (permission == LocationPermission.deniedForever &&
|
if (permission == LocationPermission.deniedForever &&
|
||||||
!shouldRequestPermission) {
|
!shouldRequestPermission &&
|
||||||
|
!silent) {
|
||||||
await Geolocator.openAppSettings();
|
await Geolocator.openAppSettings();
|
||||||
}
|
}
|
||||||
return (null, LocationPermission.deniedForever);
|
return (null, LocationPermission.deniedForever);
|
||||||
}
|
}
|
||||||
|
|
||||||
Position currentUserLocation = await Geolocator.getCurrentPosition(
|
Position currentUserLocation = await Geolocator.getCurrentPosition(
|
||||||
desiredAccuracy: LocationAccuracy.medium,
|
locationSettings: const LocationSettings(
|
||||||
timeLimit: const Duration(seconds: 5),
|
accuracy: LocationAccuracy.high,
|
||||||
|
distanceFilter: 0,
|
||||||
|
timeLimit: Duration(seconds: 5),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return (currentUserLocation, null);
|
return (currentUserLocation, null);
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
|
@ -46,12 +46,39 @@ class MapAssetGrid extends HookConsumerWidget {
|
|||||||
final gridScrollThrottler =
|
final gridScrollThrottler =
|
||||||
useThrottler(interval: const Duration(milliseconds: 300));
|
useThrottler(interval: const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
// Add a cache for assets we've already loaded
|
||||||
|
final assetCache = useRef<Map<String, Asset>>({});
|
||||||
|
|
||||||
void handleMapEvents(MapEvent event) async {
|
void handleMapEvents(MapEvent event) async {
|
||||||
if (event is MapAssetsInBoundsUpdated) {
|
if (event is MapAssetsInBoundsUpdated) {
|
||||||
assetsInBounds.value = await ref
|
final assetIds = event.assetRemoteIds;
|
||||||
.read(dbProvider)
|
final missingIds = <String>[];
|
||||||
.assets
|
final currentAssets = <Asset>[];
|
||||||
.getAllByRemoteId(event.assetRemoteIds);
|
|
||||||
|
for (final id in assetIds) {
|
||||||
|
final asset = assetCache.value[id];
|
||||||
|
if (asset != null) {
|
||||||
|
currentAssets.add(asset);
|
||||||
|
} else {
|
||||||
|
missingIds.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only fetch missing assets
|
||||||
|
if (missingIds.isNotEmpty) {
|
||||||
|
final newAssets =
|
||||||
|
await ref.read(dbProvider).assets.getAllByRemoteId(missingIds);
|
||||||
|
|
||||||
|
// Add new assets to cache and current list
|
||||||
|
for (final asset in newAssets) {
|
||||||
|
if (asset.remoteId != null) {
|
||||||
|
assetCache.value[asset.remoteId!] = asset;
|
||||||
|
currentAssets.add(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assetsInBounds.value = currentAssets;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +151,7 @@ class MapAssetGrid extends HookConsumerWidget {
|
|||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
// Place it just below the drag handle
|
// Place it just below the drag handle
|
||||||
heightFactor: 0.80,
|
heightFactor: 0.87,
|
||||||
child: assetsInBounds.value.isNotEmpty
|
child: assetsInBounds.value.isNotEmpty
|
||||||
? ref
|
? ref
|
||||||
.watch(assetsTimelineProvider(assetsInBounds.value))
|
.watch(assetsTimelineProvider(assetsInBounds.value))
|
||||||
@ -251,8 +278,18 @@ class _MapSheetDragRegion extends StatelessWidget {
|
|||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
const CustomDraggingHandle(),
|
const CustomDraggingHandle(),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
Text(assetsInBoundsText, style: context.textTheme.bodyLarge),
|
Center(
|
||||||
const Divider(height: 35),
|
child: Text(
|
||||||
|
assetsInBoundsText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
color: context.textTheme.displayLarge?.color
|
||||||
|
?.withValues(alpha: 0.75),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ValueListenableBuilder(
|
ValueListenableBuilder(
|
||||||
@ -260,14 +297,14 @@ class _MapSheetDragRegion extends StatelessWidget {
|
|||||||
builder: (_, value, __) => Visibility(
|
builder: (_, value, __) => Visibility(
|
||||||
visible: value != null,
|
visible: value != null,
|
||||||
child: Positioned(
|
child: Positioned(
|
||||||
right: 15,
|
right: 18,
|
||||||
top: 15,
|
top: 24,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.map_outlined,
|
Icons.map_outlined,
|
||||||
color: context.textTheme.displayLarge?.color,
|
color: context.textTheme.displayLarge?.color,
|
||||||
),
|
),
|
||||||
iconSize: 20,
|
iconSize: 24,
|
||||||
tooltip: 'Zoom to bounds',
|
tooltip: 'Zoom to bounds',
|
||||||
onPressed: () => onZoomToAsset?.call(value!),
|
onPressed: () => onZoomToAsset?.call(value!),
|
||||||
),
|
),
|
||||||
|
@ -20,7 +20,7 @@ class SearchMapThumbnail extends StatelessWidget {
|
|||||||
return ThumbnailWithInfoContainer(
|
return ThumbnailWithInfoContainer(
|
||||||
label: 'search_page_your_map'.tr(),
|
label: 'search_page_your_map'.tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushRoute(const MapRoute());
|
context.pushRoute(MapRoute());
|
||||||
},
|
},
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: MapThumbnail(
|
child: MapThumbnail(
|
||||||
|
@ -696,18 +696,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: geolocator
|
name: geolocator
|
||||||
sha256: "6cb9fb6e5928b58b9a84bdf85012d757fd07aab8215c5205337021c4999bad27"
|
sha256: e7ebfa04ce451daf39b5499108c973189a71a919aa53c1204effda1c5b93b822
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.1.0"
|
version: "14.0.0"
|
||||||
geolocator_android:
|
geolocator_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: geolocator_android
|
name: geolocator_android
|
||||||
sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47"
|
sha256: "114072db5d1dce0ec0b36af2697f55c133bc89a2c8dd513e137c0afe59696ed4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.1"
|
version: "5.0.1+1"
|
||||||
geolocator_apple:
|
geolocator_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -728,10 +728,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: geolocator_web
|
name: geolocator_web
|
||||||
sha256: "49d8f846ebeb5e2b6641fe477a7e97e5dd73f03cbfef3fd5c42177b7300fb0ed"
|
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.1.3"
|
||||||
geolocator_windows:
|
geolocator_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -35,7 +35,7 @@ dependencies:
|
|||||||
flutter_udid: ^3.0.0
|
flutter_udid: ^3.0.0
|
||||||
flutter_web_auth_2: ^5.0.0-alpha.0
|
flutter_web_auth_2: ^5.0.0-alpha.0
|
||||||
fluttertoast: ^8.2.12
|
fluttertoast: ^8.2.12
|
||||||
geolocator: ^11.0.0
|
geolocator: ^14.0.0
|
||||||
hooks_riverpod: ^2.6.1
|
hooks_riverpod: ^2.6.1
|
||||||
http: ^1.3.0
|
http: ^1.3.0
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user