diff --git a/.gitignore b/.gitignore
index 5d4c02131..3a331414f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,4 @@ android/app/.settings/*
fastlane/report.xml
+TensorFlowLiteC.framework
\ No newline at end of file
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml
index b26e945b8..5f349f7f4 100644
--- a/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml
+++ b/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml
@@ -2,5 +2,4 @@
-
diff --git a/android/build.gradle b/android/build.gradle
index 04b667263..be653d816 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -31,6 +31,6 @@ subprojects {
project.evaluationDependsOn(':app')
}
-task clean(type: Delete) {
+tasks.register("clean", Delete) {
delete rootProject.buildDir
}
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index bcef537c9..c047d22df 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -747,4 +747,4 @@
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
-}
+}
\ No newline at end of file
diff --git a/lib/ui/components/home_header_widget.dart b/lib/ui/components/home_header_widget.dart
index 62e747bc3..58c35229f 100644
--- a/lib/ui/components/home_header_widget.dart
+++ b/lib/ui/components/home_header_widget.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
+import 'package:photos/ui/map/map_screen.dart';
import 'package:photos/ui/viewer/search/search_widget.dart';
class HomeHeaderWidget extends StatefulWidget {
@@ -17,18 +18,65 @@ class _HomeHeaderWidgetState extends State {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- IconButtonWidget(
- iconButtonType: IconButtonType.primary,
- icon: Icons.menu_outlined,
- onTap: () {
- Scaffold.of(context).openDrawer();
- },
+ Flexible(
+ flex: 0,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ IconButtonWidget(
+ iconButtonType: IconButtonType.primary,
+ icon: Icons.menu_outlined,
+ onTap: () {
+ Scaffold.of(context).openDrawer();
+ },
+ ),
+ const SizedBox(width: 24),
+ ],
+ ),
),
- AnimatedSwitcher(
- duration: const Duration(milliseconds: 250),
- child: widget.centerWidget,
+ Flexible(
+ child: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 250),
+ child: widget.centerWidget,
+ ),
),
- const SearchIconWidget(),
+ Flexible(
+ flex: 0,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SearchIconWidget(),
+ PopupMenuButton(
+ padding: const EdgeInsets.symmetric(horizontal: 0),
+ offset: const Offset(0, 40),
+ itemBuilder: (context) => [
+ PopupMenuItem(
+ value: 0,
+ child: Row(
+ children: const [
+ Icon(Icons.map_outlined),
+ SizedBox(width: 16),
+ Text(
+ "Map",
+ ),
+ ],
+ ),
+ ),
+ ],
+ onSelected: (item) => {
+ if (item == 0)
+ {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) => const MapScreen(),
+ ),
+ )
+ }
+ },
+ )
+ ],
+ ),
+ )
],
);
}
diff --git a/lib/ui/map/image_marker.dart b/lib/ui/map/image_marker.dart
new file mode 100644
index 000000000..53a56e933
--- /dev/null
+++ b/lib/ui/map/image_marker.dart
@@ -0,0 +1,13 @@
+import "package:photos/models/file.dart";
+
+class ImageMarker {
+ final File imageFile;
+ final double latitude;
+ final double longitude;
+
+ ImageMarker({
+ required this.imageFile,
+ required this.latitude,
+ required this.longitude,
+ });
+}
diff --git a/lib/ui/map/map_button.dart b/lib/ui/map/map_button.dart
new file mode 100644
index 000000000..c1c656a12
--- /dev/null
+++ b/lib/ui/map/map_button.dart
@@ -0,0 +1,25 @@
+import "package:flutter/material.dart";
+
+class MapButton extends StatelessWidget {
+ final String heroTag;
+ final IconData icon;
+ final VoidCallback onPressed;
+
+ const MapButton({
+ super.key,
+ required this.icon,
+ required this.onPressed,
+ required this.heroTag,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return FloatingActionButton(
+ heroTag: heroTag,
+ backgroundColor: Colors.white,
+ mini: true,
+ onPressed: onPressed,
+ child: Icon(icon),
+ );
+ }
+}
diff --git a/lib/ui/map/map_credits.dart b/lib/ui/map/map_credits.dart
new file mode 100644
index 000000000..d250af925
--- /dev/null
+++ b/lib/ui/map/map_credits.dart
@@ -0,0 +1,62 @@
+import "package:flutter/gestures.dart";
+import "package:flutter/material.dart";
+import "package:url_launcher/url_launcher.dart";
+
+class MapCredits extends StatelessWidget {
+ const MapCredits({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 10),
+ child: GestureDetector(
+ child: Text.rich(
+ style: const TextStyle(
+ fontSize: 11,
+ ),
+ TextSpan(
+ text: 'Map © ',
+ children: [
+ TextSpan(
+ text: 'OpenStreetMap',
+ style: const TextStyle(
+ color: Colors.green,
+ decoration: TextDecoration.underline,
+ ),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ launchUrl(Uri.parse('https://www.openstreetmap.org/'));
+ },
+ ),
+ const TextSpan(text: ' contributors'),
+ const TextSpan(text: ' | Tiles © '),
+ TextSpan(
+ text: 'HOT',
+ style: const TextStyle(
+ color: Colors.green,
+ decoration: TextDecoration.underline,
+ ),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ launchUrl(Uri.parse('https://www.hotosm.org/'));
+ },
+ ),
+ const TextSpan(text: ' | Hosted @ '),
+ TextSpan(
+ text: 'OSM France',
+ style: const TextStyle(
+ color: Colors.green,
+ decoration: TextDecoration.underline,
+ ),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () {
+ launchUrl(Uri.parse('https://www.openstreetmap.fr/'));
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/map/map_gallery_tile.dart b/lib/ui/map/map_gallery_tile.dart
new file mode 100644
index 000000000..36b15fa44
--- /dev/null
+++ b/lib/ui/map/map_gallery_tile.dart
@@ -0,0 +1,25 @@
+
+import "package:flutter/material.dart";
+import "package:photos/ui/map/image_marker.dart";
+import "package:photos/ui/map/marker_image.dart";
+
+class MapGalleryTile extends StatelessWidget {
+ final ImageMarker imageMarker;
+
+ const MapGalleryTile({super.key, required this.imageMarker});
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ margin: const EdgeInsets.all(10),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4),
+ color: Colors.black,
+ ),
+ child: MarkerImage(
+ key: key,
+ file: imageMarker.imageFile,
+ seperator: 65,
+ ),
+ );
+ }
+}
diff --git a/lib/ui/map/map_gallery_tile_badge.dart b/lib/ui/map/map_gallery_tile_badge.dart
new file mode 100644
index 000000000..b51afc9cc
--- /dev/null
+++ b/lib/ui/map/map_gallery_tile_badge.dart
@@ -0,0 +1,45 @@
+import "package:flutter/material.dart";
+
+class MapGalleryTileBadge extends StatelessWidget {
+ final int size;
+ const MapGalleryTileBadge({super.key, required this.size});
+
+ 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+';
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Positioned(
+ top: 0,
+ right: 0,
+ child: Container(
+ padding: const EdgeInsets.all(5),
+ decoration: const BoxDecoration(
+ borderRadius: BorderRadius.all(
+ Radius.circular(5),
+ ),
+ shape: BoxShape.rectangle,
+ color: Colors.green,
+ ),
+ child: Text(
+ formatNumber(size),
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 8,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/map/map_marker.dart b/lib/ui/map/map_marker.dart
new file mode 100644
index 000000000..076c168d8
--- /dev/null
+++ b/lib/ui/map/map_marker.dart
@@ -0,0 +1,21 @@
+import "package:flutter/material.dart";
+import "package:flutter_map/flutter_map.dart";
+import "package:latlong2/latlong.dart";
+import "package:photos/ui/map/image_marker.dart";
+import "package:photos/ui/map/marker_image.dart";
+
+Marker mapMarker(ImageMarker imageMarker, String key) {
+ return Marker(
+ key: Key(key),
+ width: 75,
+ height: 75,
+ point: LatLng(
+ imageMarker.latitude,
+ imageMarker.longitude,
+ ),
+ builder: (context) => MarkerImage(
+ file: imageMarker.imageFile,
+ seperator: 85,
+ ),
+ );
+}
diff --git a/lib/ui/map/map_screen.dart b/lib/ui/map/map_screen.dart
new file mode 100644
index 000000000..2baff4210
--- /dev/null
+++ b/lib/ui/map/map_screen.dart
@@ -0,0 +1,187 @@
+import "dart:async";
+import "dart:math";
+
+import 'package:flutter/material.dart';
+import 'package:flutter_map/flutter_map.dart';
+import "package:latlong2/latlong.dart";
+import "package:photos/db/files_db.dart";
+import "package:photos/models/file.dart";
+import "package:photos/models/file_load_result.dart";
+import "package:photos/services/ignored_files_service.dart";
+import "package:photos/ui/map/image_marker.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 {
+ const MapScreen({super.key});
+
+ @override
+ State createState() {
+ return _MapScreenState();
+ }
+}
+
+class _MapScreenState extends State {
+ List imageMarkers = [];
+ List allImages = [];
+ List visibleImages = [];
+ MapController mapController = MapController();
+ bool isLoading = true;
+
+ @override
+ void initState() {
+ super.initState();
+ initialize();
+ }
+
+ void initialize() async {
+ await getFiles();
+ processFiles(allImages);
+ }
+
+ Future getFiles() async {
+ final ignoredIDs = await IgnoredFilesService.instance.ignoredIDs;
+ final ignoredIntIDs = {};
+ for (var element in ignoredIDs) {
+ ignoredIntIDs.add(int.parse(element));
+ }
+ allImages = await FilesDB.instance.getAllFilesFromDB(ignoredIntIDs);
+ }
+
+ void processFiles(List files) {
+ final List tempMarkers = [];
+ for (var file in files) {
+ // if (file.hasLocation && location != null) {
+ final rand = Random();
+ tempMarkers.add(
+ ImageMarker(
+ latitude: 10.786985 + rand.nextDouble() / 10,
+ longitude: 78.6882166 + rand.nextDouble() / 10,
+ imageFile: file,
+ ),
+ );
+ // }
+ }
+ setState(() {
+ imageMarkers = tempMarkers;
+ isLoading = false;
+ });
+ updateVisibleImages(mapController.bounds!);
+ }
+
+ void updateVisibleImages(LatLngBounds bounds) async {
+ final images = imageMarkers
+ .where((imageMarker) {
+ final point = LatLng(imageMarker.latitude, imageMarker.longitude);
+ return bounds.contains(point);
+ })
+ .map((imageMarker) => imageMarker.imageFile)
+ .toList();
+
+ setState(() {
+ visibleImages = images;
+ });
+ }
+
+ 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) {
+ return SafeArea(
+ child: Scaffold(
+ body: Stack(
+ children: [
+ Column(
+ children: [
+ Expanded(
+ child: MapView(
+ updateVisibleImages: updateVisibleImages,
+ controller: mapController,
+ imageMarkers: imageMarkers,
+ ),
+ ),
+ const SizedBox(
+ child: MapCredits(),
+ ),
+ SizedBox(
+ height: 120,
+ child: Center(
+ child: ListView.builder(
+ itemCount: visibleImages.length,
+ 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),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ )
+ ],
+ ),
+ isLoading
+ ? Container(
+ color: Colors.black87,
+ child: const Center(
+ child: CircularProgressIndicator(color: Colors.green),
+ ),
+ )
+ : const SizedBox.shrink(),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/map/map_view.dart b/lib/ui/map/map_view.dart
new file mode 100644
index 000000000..b02dd1889
--- /dev/null
+++ b/lib/ui/map/map_view.dart
@@ -0,0 +1,160 @@
+import "dart:async";
+
+import "package:flutter/material.dart";
+import "package:flutter_map/flutter_map.dart";
+import "package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart";
+import "package:latlong2/latlong.dart";
+import "package:photos/ui/map/image_marker.dart";
+import "package:photos/ui/map/map_button.dart";
+import 'package:photos/ui/map/map_gallery_tile.dart';
+import 'package:photos/ui/map/map_gallery_tile_badge.dart';
+
+import "package:photos/ui/map/map_marker.dart";
+
+class MapView extends StatefulWidget {
+ final List imageMarkers;
+ final Function updateVisibleImages;
+ final MapController controller;
+
+ const MapView({
+ Key? key,
+ required this.updateVisibleImages,
+ required this.imageMarkers,
+ required this.controller,
+ }) : super(key: key);
+
+ @override
+ State createState() => _MapViewState();
+}
+
+class _MapViewState extends State {
+ Timer? _debounceTimer;
+ LatLng center = LatLng(10.732951, 78.405635);
+ bool _isDebouncing = false;
+
+ 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;
+ });
+ }
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: [
+ FlutterMap(
+ mapController: widget.controller,
+ options: MapOptions(
+ center: center,
+ zoom: 5,
+ minZoom: 5,
+ maxZoom: 16.5,
+ onPositionChanged: _onPositionChanged,
+ plugins: [
+ MarkerClusterPlugin(),
+ ],
+ ),
+ layers: [
+ TileLayerOptions(
+ urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ subdomains: ['a', 'b', 'c'],
+ ),
+ MarkerClusterLayerOptions(
+ maxClusterRadius: 100,
+ showPolygon: true,
+ size: const Size(75, 75),
+ fitBoundsOptions: const FitBoundsOptions(
+ padding: EdgeInsets.all(50),
+ ),
+ markers: widget.imageMarkers.asMap().entries.map((marker) {
+ final imageMarker = marker.value;
+ return mapMarker(imageMarker, marker.key.toString());
+ }).toList(),
+ polygonOptions: const PolygonOptions(
+ borderColor: Colors.redAccent,
+ color: Colors.black12,
+ borderStrokeWidth: 3,
+ ),
+ builder: (context, markers) {
+ final index = int.parse(
+ markers.first.key
+ .toString()
+ .replaceAll(RegExp(r'[^0-9]'), ''),
+ );
+ return Stack(
+ children: [
+ MapGalleryTile(
+ key: Key(markers.first.key.toString()),
+ imageMarker: widget.imageMarkers[index],
+ ),
+ MapGalleryTileBadge(size: markers.length)
+ ],
+ );
+ },
+ ),
+ ],
+ ),
+ 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,
+ child: MapButton(
+ icon: Icons.arrow_back,
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ heroTag: 'back',
+ ),
+ ),
+ Positioned(
+ bottom: 10,
+ right: 10,
+ child: Column(
+ children: [
+ MapButton(
+ icon: Icons.add,
+ onPressed: () {
+ widget.controller.move(
+ widget.controller.center,
+ widget.controller.zoom + 1,
+ );
+ },
+ heroTag: 'zoom-in',
+ ),
+ MapButton(
+ icon: Icons.remove,
+ onPressed: () {
+ widget.controller.move(
+ widget.controller.center,
+ widget.controller.zoom - 1,
+ );
+ },
+ heroTag: 'zoom-out',
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/map/marker_image.dart b/lib/ui/map/marker_image.dart
new file mode 100644
index 000000000..32e61af9e
--- /dev/null
+++ b/lib/ui/map/marker_image.dart
@@ -0,0 +1,55 @@
+import "package:flutter/material.dart";
+import "package:photos/models/file.dart";
+import "package:photos/ui/viewer/file/thumbnail_widget.dart";
+
+class MarkerImage extends StatelessWidget {
+ final File file;
+ final double seperator;
+
+ const MarkerImage({super.key, required this.file, required this.seperator});
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: [
+ Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(4),
+ border: Border.all(
+ color: Colors.green,
+ width: 1.75,
+ ),
+ ),
+ child: ThumbnailWidget(file),
+ ),
+ Align(
+ alignment: Alignment.bottomCenter,
+ child: Container(
+ margin: EdgeInsets.only(top: seperator),
+ child: CustomPaint(
+ painter: MarkerPointer(),
+ ),
+ ),
+ )
+ ],
+ );
+ }
+}
+
+class MarkerPointer extends CustomPainter {
+ @override
+ void paint(Canvas canvas, Size size) {
+ final paint = Paint()..color = Colors.green;
+
+ final path = Path();
+ path.moveTo(5, -12);
+ path.lineTo(0, 0);
+ path.lineTo(-5, -12);
+ canvas.drawPath(path, paint);
+ }
+
+ @override
+ bool shouldRepaint(CustomPainter oldDelegate) {
+ return true;
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
index 71ee3c7a1..1e4f69d18 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -49,6 +49,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.4.5"
+ animated_stack_widget:
+ dependency: transitive
+ description:
+ name: animated_stack_widget
+ sha256: ce4788dd158768c9d4388354b6fb72600b78e041a37afc4c279c63ecafcb9408
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.0.4"
archive:
dependency: "direct main"
description:
@@ -696,6 +704,30 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_map:
+ dependency: "direct main"
+ description:
+ name: flutter_map
+ sha256: "9401bcc83b1118ddd35c0b25efaa5af182572707f1887bbb7817c2337fcd8c97"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.13.1"
+ flutter_map_marker_cluster:
+ dependency: "direct main"
+ description:
+ name: flutter_map_marker_cluster
+ sha256: "3eefbe1ed8ef16be52f9992363875992c688f4246e2d99be488b25f238ebfa7b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.4"
+ flutter_map_marker_popup:
+ dependency: transitive
+ description:
+ name: flutter_map_marker_popup
+ sha256: "4ce4eaef4efb1ca38fc0620beb26eb65f4535ba686ae9988d7f7c4ec05fe1e3a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
flutter_native_splash:
dependency: "direct main"
description:
@@ -1020,6 +1052,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.6.1"
+ latlong2:
+ dependency: "direct main"
+ description:
+ name: latlong2
+ sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.2"
like_button:
dependency: "direct main"
description:
@@ -1036,6 +1076,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.1"
+ lists:
+ dependency: transitive
+ description:
+ name: lists
+ sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
loading_animations:
dependency: "direct main"
description:
@@ -1132,6 +1180,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.0"
+ mgrs_dart:
+ dependency: transitive
+ description:
+ name: mgrs_dart
+ sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
mime:
dependency: transitive
description:
@@ -1430,6 +1486,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.1"
+ positioned_tap_detector_2:
+ dependency: transitive
+ description:
+ name: positioned_tap_detector_2
+ sha256: "52e06863ad3e1f82b058fd05054fc8c9caeeb3b47d5cea7a24bd9320746059c1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.4"
process:
dependency: transitive
description:
@@ -1438,6 +1502,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.4"
+ proj4dart:
+ dependency: transitive
+ description:
+ name: proj4dart
+ sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
provider:
dependency: "direct main"
description:
@@ -1868,6 +1940,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
+ transparent_image:
+ dependency: transitive
+ description:
+ name: transparent_image
+ sha256: e8991d955a2094e197ca24c645efec2faf4285772a4746126ca12875e54ca02f
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
tuple:
dependency: "direct main"
description:
@@ -1908,6 +1988,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.0"
+ unicode:
+ dependency: transitive
+ description:
+ name: unicode
+ sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.1"
universal_io:
dependency: transitive
description:
@@ -2137,6 +2225,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.1"
+ wkt_parser:
+ dependency: transitive
+ description:
+ name: wkt_parser
+ sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
xdg_directories:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 819f5ab67..9e535955c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -63,6 +63,8 @@ dependencies:
flutter_local_notifications: ^9.5.3+1
flutter_localizations:
sdk: flutter
+ flutter_map: ^0.13.1
+ flutter_map_marker_cluster: ^0.4.0
flutter_native_splash: ^2.2.0+1
flutter_password_strength: ^0.1.6
flutter_secure_storage: ^8.0.0
@@ -77,6 +79,7 @@ dependencies:
in_app_purchase: ^3.0.7
intl: ^0.17.0
json_annotation: ^4.8.0
+ latlong2: ^0.8.1
like_button: ^2.0.2
loading_animations: ^2.1.0
local_auth: ^2.1.5
@@ -132,6 +135,7 @@ dependencies:
wallpaper_manager_flutter: ^0.0.2
widgets_to_image: ^0.0.2
+
flutter_intl:
enabled: true