2023-06-04 18:29:03 +00:00
|
|
|
import "dart:async";
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter_map/flutter_map.dart';
|
|
|
|
import "package:latlong2/latlong.dart";
|
2023-06-04 23:22:21 +00:00
|
|
|
import "package:logging/logging.dart";
|
2023-06-04 18:29:03 +00:00
|
|
|
import "package:photos/db/files_db.dart";
|
|
|
|
import "package:photos/models/file.dart";
|
|
|
|
import "package:photos/models/file_load_result.dart";
|
2023-06-04 23:21:10 +00:00
|
|
|
import "package:photos/services/collections_service.dart";
|
2023-06-04 18:29:03 +00:00
|
|
|
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;
|
2023-06-04 23:22:21 +00:00
|
|
|
final Logger _logger = Logger("_MapScreenState");
|
2023-06-04 18:29:03 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
initialize();
|
|
|
|
}
|
|
|
|
|
2023-06-04 23:22:21 +00:00
|
|
|
Future<void> initialize() async {
|
|
|
|
try {
|
|
|
|
await getFiles();
|
|
|
|
processFiles(allImages);
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("Error initializing map screen", e, s);
|
|
|
|
}
|
2023-06-04 18:29:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> getFiles() async {
|
2023-06-04 23:21:10 +00:00
|
|
|
final ignoredIDs = CollectionsService.instance.getHiddenCollections();
|
|
|
|
allImages = await FilesDB.instance.getAllFilesFromDB(ignoredIDs);
|
2023-06-04 18:29:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void processFiles(List<File> files) {
|
|
|
|
final List<ImageMarker> tempMarkers = [];
|
|
|
|
for (var file in files) {
|
2023-06-04 23:21:51 +00:00
|
|
|
if (file.hasLocation) {
|
|
|
|
tempMarkers.add(
|
|
|
|
ImageMarker(
|
|
|
|
latitude: file.location!.latitude!,
|
|
|
|
longitude: file.location!.longitude!,
|
|
|
|
imageFile: file,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2023-06-04 18:29:03 +00:00
|
|
|
}
|
|
|
|
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(),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|