feat[map_gallery]: implement map gallery
This commit is contained in:
parent
3d07ca4e05
commit
6e2ab6d6ba
16 changed files with 754 additions and 13 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -42,3 +42,4 @@ android/app/.settings/*
|
|||
|
||||
|
||||
fastlane/report.xml
|
||||
TensorFlowLiteC.framework
|
|
@ -2,5 +2,4 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
|
@ -31,6 +31,6 @@ subprojects {
|
|||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
|
|
@ -747,4 +747,4 @@
|
|||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
}
|
|
@ -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<HomeHeaderWidget> {
|
|||
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<int>(
|
||||
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(),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
13
lib/ui/map/image_marker.dart
Normal file
13
lib/ui/map/image_marker.dart
Normal file
|
@ -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,
|
||||
});
|
||||
}
|
25
lib/ui/map/map_button.dart
Normal file
25
lib/ui/map/map_button.dart
Normal file
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
62
lib/ui/map/map_credits.dart
Normal file
62
lib/ui/map/map_credits.dart
Normal file
|
@ -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/'));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
25
lib/ui/map/map_gallery_tile.dart
Normal file
25
lib/ui/map/map_gallery_tile.dart
Normal file
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
45
lib/ui/map/map_gallery_tile_badge.dart
Normal file
45
lib/ui/map/map_gallery_tile_badge.dart
Normal file
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
21
lib/ui/map/map_marker.dart
Normal file
21
lib/ui/map/map_marker.dart
Normal file
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
187
lib/ui/map/map_screen.dart
Normal file
187
lib/ui/map/map_screen.dart
Normal file
|
@ -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<StatefulWidget> createState() {
|
||||
return _MapScreenState();
|
||||
}
|
||||
}
|
||||
|
||||
class _MapScreenState extends State<MapScreen> {
|
||||
List<ImageMarker> imageMarkers = [];
|
||||
List<File> allImages = [];
|
||||
List<File> visibleImages = [];
|
||||
MapController mapController = MapController();
|
||||
bool isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initialize();
|
||||
}
|
||||
|
||||
void initialize() async {
|
||||
await getFiles();
|
||||
processFiles(allImages);
|
||||
}
|
||||
|
||||
Future<void> getFiles() async {
|
||||
final ignoredIDs = await IgnoredFilesService.instance.ignoredIDs;
|
||||
final ignoredIntIDs = <int>{};
|
||||
for (var element in ignoredIDs) {
|
||||
ignoredIntIDs.add(int.parse(element));
|
||||
}
|
||||
allImages = await FilesDB.instance.getAllFilesFromDB(ignoredIntIDs);
|
||||
}
|
||||
|
||||
void processFiles(List<File> files) {
|
||||
final List<ImageMarker> 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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
160
lib/ui/map/map_view.dart
Normal file
160
lib/ui/map/map_view.dart
Normal file
|
@ -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<ImageMarker> 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<StatefulWidget> createState() => _MapViewState();
|
||||
}
|
||||
|
||||
class _MapViewState extends State<MapView> {
|
||||
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',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
55
lib/ui/map/marker_image.dart
Normal file
55
lib/ui/map/marker_image.dart
Normal file
|
@ -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: <Widget>[
|
||||
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;
|
||||
}
|
||||
}
|
96
pubspec.lock
96
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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue