feat(mobile): map view (#3661)

* feat(mobile): map page - add map view

* map: add map-markers

* feat(map): add relative date filter

* fix: do not let users scroll past map bounds

* fix: fetch relative date from store to state on init

* feat(mobile):re-fetch markers only on filter change

* feat(mobile) - asset bottom sheet in map page

* feat(mobile): display markers based on bottom sheet scroll

* fix: exif-bottom-sheet - rebase conflict

* feat(mobile): map-view - strongly typed map page events

* feat(map): zoom to asset

* chore: dart analyzer fixes

* map-page move attribution to top-right

* feat(mobile): map view - asset selection handling

* feat(mobile): map-view display map in places row

* fix: make asset marker icon responsive

* optimise map page rebuilds

* refactor(mobile): map page

* feat(mobile): map-view: Go to location

* map-view(mobile): minor refactor

* fix(mobile): Handle invalid coords gracefully

* small styling

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
shalong-tanwen 2023-08-27 05:07:35 +00:00 committed by GitHub
parent 305889f32b
commit cb391342d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 2268 additions and 139 deletions

View file

@ -0,0 +1,110 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart';
import 'package:immich_mobile/modules/search/ui/curated_row.dart';
import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:latlong2/latlong.dart';
class CuratedPlacesRow extends CuratedRow {
const CuratedPlacesRow({
super.key,
required super.content,
super.imageSize,
super.onTap,
});
@override
Widget build(BuildContext context) {
Widget buildMapThumbnail() {
return GestureDetector(
onTap: () => AutoRouter.of(context).push(
const MapRoute(),
),
child: SizedBox(
height: imageSize,
width: imageSize,
child: Stack(
children: [
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: MapThumbnail(
zoom: 2,
coords: LatLng(
47,
5,
),
height: imageSize,
showAttribution: false,
isDarkTheme: Theme.of(context).brightness == Brightness.dark,
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black,
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.blueGrey.withOpacity(0.0),
Colors.black.withOpacity(0.4),
],
stops: const [0.0, 1.0],
),
),
),
const Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(bottom: 10),
child: Text(
"Your Map",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
),
],
),
),
);
}
return ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
itemBuilder: (context, index) {
// Injecting Map thumbnail as the first element
if (index == 0) {
return buildMapThumbnail();
}
// The actual index is 1 less than the virutal index since we inject map into the first position
final actualIndex = index - 1;
final object = content[actualIndex];
final thumbnailRequestUrl =
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/${object.id}';
return SizedBox(
width: imageSize,
height: imageSize,
child: Padding(
padding: const EdgeInsets.only(right: 10.0),
child: ThumbnailWithInfo(
imageUrl: thumbnailRequestUrl,
textInfo: object.label,
onTap: () => onTap?.call(object, actualIndex),
),
),
);
},
// Adding 1 to inject map thumbnail as first element
itemCount: content.length + 1,
);
}
}

View file

@ -7,7 +7,7 @@ import 'package:immich_mobile/modules/search/models/curated_content.dart';
import 'package:immich_mobile/modules/search/providers/people.provider.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
import 'package:immich_mobile/modules/search/ui/curated_people_row.dart';
import 'package:immich_mobile/modules/search/ui/curated_row.dart';
import 'package:immich_mobile/modules/search/ui/curated_places_row.dart';
import 'package:immich_mobile/modules/search/ui/immich_search_bar.dart';
import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart';
import 'package:immich_mobile/modules/search/ui/search_row_title.dart';
@ -69,7 +69,7 @@ class SearchPage extends HookConsumerWidget {
buildPeople() {
return SizedBox(
height: MediaQuery.of(context).size.width / 3,
height: imageSize,
child: curatedPeople.when(
loading: () => const Center(child: ImmichLoadingIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
@ -105,7 +105,7 @@ class SearchPage extends HookConsumerWidget {
child: curatedLocation.when(
loading: () => const Center(child: ImmichLoadingIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
data: (locations) => CuratedRow(
data: (locations) => CuratedPlacesRow(
content: locations
.map(
(o) => CuratedContent(
@ -155,6 +155,7 @@ class SearchPage extends HookConsumerWidget {
),
top: 0,
),
const SizedBox(height: 10.0),
buildPlaces(),
const SizedBox(height: 24.0),
Padding(