feat(initialZoom): optimal initial zoom for map

This commit is contained in:
Muhesh7 2023-06-05 17:17:38 +05:30
parent 45302c6550
commit b4b60d5719
3 changed files with 130 additions and 123 deletions

View file

@ -0,0 +1,64 @@
import "package:flutter/material.dart";
import "package:photos/models/file.dart";
import "package:photos/models/file_load_result.dart";
import "package:photos/ui/viewer/file/detail_page.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
import "package:photos/utils/navigation_util.dart";
class ImageTile extends StatelessWidget {
final File image;
final int index;
final List<File> allImages;
final List<File> visibleImages;
const ImageTile({
super.key,
required this.image,
required this.index,
required this.allImages,
required this.visibleImages,
});
void onTap(BuildContext context, File image, int index) {
final page = DetailPage(
DetailPageConfiguration(
List.unmodifiable(visibleImages),
(
creationStartTime,
creationEndTime, {
limit,
asc,
}) async {
final result = FileLoadResult(allImages, false);
return result;
},
index,
'Map',
),
);
routeToPage(
context,
page,
forceCustomPageRoute: true,
);
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onTap(context, image, index),
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 10,
),
width: 100,
height: 100,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: ThumbnailWidget(image),
),
),
);
}
}

View file

@ -7,13 +7,10 @@ import 'package:flutter_map/flutter_map.dart';
import "package:latlong2/latlong.dart";
import "package:logging/logging.dart";
import "package:photos/models/file.dart";
import "package:photos/models/file_load_result.dart";
import "package:photos/ui/map/image_marker.dart";
import 'package:photos/ui/map/image_tile.dart';
import "package:photos/ui/map/map_credits.dart";
import "package:photos/ui/map/map_view.dart";
import "package:photos/ui/viewer/file/detail_page.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
import "package:photos/utils/navigation_util.dart";
class MapScreen extends StatefulWidget {
// Add a function parameter where the function returns a Future<List<File>>
@ -38,7 +35,10 @@ class _MapScreenState extends State<MapScreen> {
MapController mapController = MapController();
bool isLoading = true;
double initialZoom = 4.0;
LatLng center = LatLng(10.732951, 78.405635);
double maxZoom = 16.0;
double minZoom = 3.0;
int debounceDuration = 200;
LatLng center = LatLng(0, 0);
final Logger _logger = Logger("_MapScreenState");
@override
@ -56,23 +56,12 @@ class _MapScreenState extends State<MapScreen> {
}
}
// Simple function to estimate zoom level
double estimateZoomLevel(
double range,
double maxRange,
double minZoom,
double maxZoom,
) {
if (range >= maxRange) return minZoom;
return maxZoom - ((range / maxRange) * (maxZoom - minZoom));
}
void processFiles(List<File> files) {
late double minLat, maxLat, minLon, maxLon;
final List<ImageMarker> tempMarkers = [];
bool hasAnyLocation = false;
for (var file in files) {
if (file.hasLocation) {
if (file.location != null) {
if (!hasAnyLocation) {
minLat = file.location!.latitude!;
minLon = file.location!.longitude!;
@ -99,9 +88,15 @@ class _MapScreenState extends State<MapScreen> {
minLat + (maxLat - minLat) / 2,
minLon + (maxLon - minLon) / 2,
);
final double latZoom = estimateZoomLevel(maxLat - minLat, 90, 0, 19);
final double lonZoom = estimateZoomLevel(maxLon - minLon, 180, 0, 19);
final latRange = maxLat - minLat;
final lonRange = maxLon - minLon;
final latZoom = log(360.0 / latRange) / log(2);
final lonZoom = log(180.0 / lonRange) / log(2);
initialZoom = min(latZoom, lonZoom);
if (initialZoom < minZoom) initialZoom = minZoom;
if (initialZoom > maxZoom) initialZoom = maxZoom;
if (kDebugMode) {
debugPrint("Info for map: center $center, initialZoom $initialZoom");
debugPrint("Info for map: minLat $minLat, maxLat $maxLat");
@ -111,12 +106,22 @@ class _MapScreenState extends State<MapScreen> {
setState(() {
imageMarkers = tempMarkers;
isLoading = false;
});
updateVisibleImages(mapController.bounds!);
mapController.move(
center,
initialZoom,
);
Timer(Duration(milliseconds: debounceDuration), () {
updateVisibleImages(mapController.bounds!);
setState(() {
isLoading = false;
});
});
}
void updateVisibleImages(LatLngBounds bounds) async {
void updateVisibleImages(LatLngBounds bounds) {
final images = imageMarkers
.where((imageMarker) {
final point = LatLng(imageMarker.latitude, imageMarker.longitude);
@ -130,47 +135,8 @@ class _MapScreenState extends State<MapScreen> {
});
}
String formatNumber(int number) {
if (number <= 99) {
return number.toString();
} else if (number <= 999) {
return '${(number / 100).toStringAsFixed(0)}00+';
} else if (number >= 1000 && number < 2000) {
return '1K+';
} else {
final int thousands = ((number - 1) ~/ 1000);
return '${thousands}K+';
}
}
void onTap(File image, int index) {
final page = DetailPage(
DetailPageConfiguration(
List.unmodifiable(visibleImages),
(
creationStartTime,
creationEndTime, {
limit,
asc,
}) async {
final result = FileLoadResult(allImages, false);
return result;
},
index,
'Map',
),
);
routeToPage(
context,
page,
forceCustomPageRoute: true,
);
}
@override
Widget build(BuildContext context) {
_logger.info('Building with Zoom $initialZoom');
return SafeArea(
child: Scaffold(
body: Stack(
@ -179,11 +145,14 @@ class _MapScreenState extends State<MapScreen> {
children: [
Expanded(
child: MapView(
updateVisibleImages: updateVisibleImages,
controller: mapController,
imageMarkers: imageMarkers,
initialZoom: initialZoom,
updateVisibleImages: updateVisibleImages,
center: center,
initialZoom: initialZoom,
minZoom: minZoom,
maxZoom: maxZoom,
debounceDuration: debounceDuration,
),
),
const SizedBox(
@ -197,20 +166,11 @@ class _MapScreenState extends State<MapScreen> {
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final image = visibleImages[index];
return InkWell(
onTap: () => onTap(image, index),
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 10,
),
width: 100,
height: 100,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: ThumbnailWidget(image),
),
),
return ImageTile(
image: image,
allImages: allImages,
visibleImages: visibleImages,
index: index,
);
},
),
@ -219,9 +179,8 @@ class _MapScreenState extends State<MapScreen> {
],
),
isLoading
? Container(
color: Colors.black87,
child: const Center(
? const SizedBox.expand(
child: Center(
child: CircularProgressIndicator(color: Colors.green),
),
)

View file

@ -14,16 +14,22 @@ class MapView extends StatefulWidget {
final List<ImageMarker> imageMarkers;
final Function updateVisibleImages;
final MapController controller;
final double initialZoom;
final LatLng center;
final double minZoom;
final double maxZoom;
final double initialZoom;
final int debounceDuration;
const MapView({
Key? key,
required this.updateVisibleImages,
required this.imageMarkers,
required this.controller,
required this.initialZoom,
required this.center,
required this.minZoom,
required this.maxZoom,
required this.initialZoom,
required this.debounceDuration,
}) : super(key: key);
@override
@ -32,45 +38,37 @@ class MapView extends StatefulWidget {
class _MapViewState extends State<MapView> {
Timer? _debounceTimer;
late LatLng center;
bool _isDebouncing = false;
@override
void initState() {
super.initState();
center = widget.center;
debugPrint("ZoomMapView: ${widget.initialZoom}");
// widget.controller.onReady.then((_) {
// widget.controller.move(center, widget.initialZoom);
// });
}
void _onPositionChanged(position, hasGesture) {
if (position.bounds != null) {
if (!_isDebouncing) {
_isDebouncing = true;
_debounceTimer?.cancel(); // Cancel previous debounce timer
_debounceTimer = Timer(const Duration(milliseconds: 200), () {
widget.updateVisibleImages(position.bounds!);
_isDebouncing = false;
});
}
}
void dispose() {
_debounceTimer?.cancel();
_debounceTimer = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint("MapViewBuild: ${widget.initialZoom}");
return Stack(
children: [
FlutterMap(
mapController: widget.controller,
options: MapOptions(
center: center,
zoom: widget.initialZoom,
minZoom: 0,
maxZoom: 19,
onPositionChanged: _onPositionChanged,
minZoom: widget.minZoom,
maxZoom: widget.maxZoom,
onPositionChanged: (position, hasGesture) {
if (position.bounds != null) {
if (!_isDebouncing) {
_isDebouncing = true;
_debounceTimer?.cancel();
_debounceTimer = Timer(
Duration(milliseconds: widget.debounceDuration), () {
widget.updateVisibleImages(position.bounds!);
_isDebouncing = false;
});
}
}
},
plugins: [
MarkerClusterPlugin(),
],
@ -115,20 +113,6 @@ class _MapViewState extends State<MapView> {
),
],
),
Positioned(
bottom: 10,
left: 10,
child: MapButton(
icon: Icons.my_location,
onPressed: () {
widget.controller.move(
center,
widget.controller.zoom,
);
},
heroTag: 'location',
),
),
Positioned(
top: 10,
left: 10,