Add reverse geocoding and show asset location on map in detail view (#43)

* Added reserve geocoding, location in search suggestion, and search by location
* Added mapbox sdk to app
* Added mapbox to image detailed view
This commit is contained in:
Alex
2022-03-10 16:09:03 -06:00
committed by GitHub
parent 251c92ff1e
commit 026f3c24e9
30 changed files with 12112 additions and 184 deletions

View File

@@ -1,7 +1,12 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/immich_asset_with_exif.model.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:intl/intl.dart';
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:path/path.dart' as p;
class ExifBottomSheet extends ConsumerWidget {
@@ -11,6 +16,54 @@ class ExifBottomSheet extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
_buildMap() {
return ref.watch(serverInfoProvider).mapboxInfo.isEnable
? Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Container(
height: 150,
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(15)),
),
child: MapboxMap(
doubleClickZoomEnabled: false,
zoomGesturesEnabled: true,
scrollGesturesEnabled: false,
accessToken: ref.watch(serverInfoProvider).mapboxInfo.mapboxSecret,
styleString: 'mapbox://styles/mapbox/streets-v11',
initialCameraPosition: CameraPosition(
zoom: 15.0,
target: LatLng(assetDetail.exifInfo!.latitude!, assetDetail.exifInfo!.longitude!),
),
onMapCreated: (MapboxMapController mapController) async {
final ByteData bytes = await rootBundle.load("assets/location-pin.png");
final Uint8List list = bytes.buffer.asUint8List();
await mapController.addImage("assetImage", list);
await mapController.addSymbol(
SymbolOptions(
geometry: LatLng(assetDetail.exifInfo!.latitude!, assetDetail.exifInfo!.longitude!),
iconImage: "assetImage",
iconSize: 0.2,
),
);
},
),
),
)
: Container();
}
_buildLocationText() {
return (assetDetail.exifInfo!.city != null && assetDetail.exifInfo!.state != null)
? Text(
"${assetDetail.exifInfo!.city}, ${assetDetail.exifInfo!.state}",
style: TextStyle(fontSize: 12, color: Colors.grey[200], fontWeight: FontWeight.bold),
)
: Container();
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8),
child: ListView(
@@ -53,9 +106,11 @@ class ExifBottomSheet extends ConsumerWidget {
"LOCATION",
style: TextStyle(fontSize: 11, color: Colors.grey[400]),
),
_buildMap(),
_buildLocationText(),
Text(
"${assetDetail.exifInfo!.latitude!.toStringAsFixed(4)}, ${assetDetail.exifInfo!.longitude!.toStringAsFixed(4)}",
style: TextStyle(fontSize: 11, color: Colors.grey[400]),
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
)
],
),
@@ -89,8 +144,10 @@ class ExifBottomSheet extends ConsumerWidget {
"${assetDetail.exifInfo?.imageName!}${p.extension(assetDetail.originalPath)}",
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
"${assetDetail.exifInfo?.exifImageHeight!} x ${assetDetail.exifInfo?.exifImageWidth!} ${assetDetail.exifInfo?.fileSizeInByte!}B "),
subtitle: assetDetail.exifInfo?.exifImageHeight != null
? Text(
"${assetDetail.exifInfo?.exifImageHeight} x ${assetDetail.exifInfo?.exifImageWidth} ${assetDetail.exifInfo?.fileSizeInByte!}B ")
: Container(),
),
assetDetail.exifInfo?.make != null
? ListTile(

View File

@@ -29,9 +29,9 @@ class TopControlAppBar extends StatelessWidget with PreferredSizeWidget {
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
print("backup");
print("download");
},
icon: const Icon(Icons.backup_outlined),
icon: const Icon(Icons.cloud_download_rounded),
),
IconButton(
iconSize: iconSize,

View File

@@ -11,6 +11,7 @@ import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
import 'package:immich_mobile/modules/home/ui/monthly_title_text.dart';
import 'package:immich_mobile/modules/home/ui/profile_drawer.dart';
import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
import 'package:sliver_tools/sliver_tools.dart';
@@ -28,6 +29,7 @@ class HomePage extends HookConsumerWidget {
useEffect(() {
ref.read(websocketProvider.notifier).connect();
ref.read(assetProvider.notifier).getAllAsset();
ref.read(serverInfoProvider.notifier).getMapboxInfo();
return null;
}, []);

View File

@@ -19,6 +19,9 @@ class ImmichExif {
final double? exposureTime;
final double? latitude;
final double? longitude;
final String? city;
final String? state;
final String? country;
ImmichExif({
this.id,
@@ -39,6 +42,9 @@ class ImmichExif {
this.exposureTime,
this.latitude,
this.longitude,
this.city,
this.state,
this.country,
});
ImmichExif copyWith({
@@ -60,6 +66,9 @@ class ImmichExif {
double? exposureTime,
double? latitude,
double? longitude,
String? city,
String? state,
String? country,
}) {
return ImmichExif(
id: id ?? this.id,
@@ -80,6 +89,9 @@ class ImmichExif {
exposureTime: exposureTime ?? this.exposureTime,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
city: city ?? this.city,
state: state ?? this.state,
country: country ?? this.country,
);
}
@@ -103,6 +115,9 @@ class ImmichExif {
'exposureTime': exposureTime,
'latitude': latitude,
'longitude': longitude,
'city': city,
'state': state,
'country': country,
};
}
@@ -126,6 +141,9 @@ class ImmichExif {
exposureTime: map['exposureTime']?.toDouble(),
latitude: map['latitude']?.toDouble(),
longitude: map['longitude']?.toDouble(),
city: map['city'],
state: map['state'],
country: map['country'],
);
}
@@ -135,7 +153,7 @@ class ImmichExif {
@override
String toString() {
return 'ImmichExif(id: $id, assetId: $assetId, make: $make, model: $model, imageName: $imageName, exifImageWidth: $exifImageWidth, exifImageHeight: $exifImageHeight, fileSizeInByte: $fileSizeInByte, orientation: $orientation, dateTimeOriginal: $dateTimeOriginal, modifyDate: $modifyDate, lensModel: $lensModel, fNumber: $fNumber, focalLength: $focalLength, iso: $iso, exposureTime: $exposureTime, latitude: $latitude, longitude: $longitude)';
return 'ImmichExif(id: $id, assetId: $assetId, make: $make, model: $model, imageName: $imageName, exifImageWidth: $exifImageWidth, exifImageHeight: $exifImageHeight, fileSizeInByte: $fileSizeInByte, orientation: $orientation, dateTimeOriginal: $dateTimeOriginal, modifyDate: $modifyDate, lensModel: $lensModel, fNumber: $fNumber, focalLength: $focalLength, iso: $iso, exposureTime: $exposureTime, latitude: $latitude, longitude: $longitude, city: $city, state: $state, country: $country)';
}
@override
@@ -160,7 +178,10 @@ class ImmichExif {
other.iso == iso &&
other.exposureTime == exposureTime &&
other.latitude == latitude &&
other.longitude == longitude;
other.longitude == longitude &&
other.city == city &&
other.state == state &&
other.country == country;
}
@override
@@ -182,6 +203,9 @@ class ImmichExif {
iso.hashCode ^
exposureTime.hashCode ^
latitude.hashCode ^
longitude.hashCode;
longitude.hashCode ^
city.hashCode ^
state.hashCode ^
country.hashCode;
}
}

View File

@@ -0,0 +1,51 @@
import 'dart:convert';
class MapboxInfo {
final bool isEnable;
final String mapboxSecret;
MapboxInfo({
required this.isEnable,
required this.mapboxSecret,
});
MapboxInfo copyWith({
bool? isEnable,
String? mapboxSecret,
}) {
return MapboxInfo(
isEnable: isEnable ?? this.isEnable,
mapboxSecret: mapboxSecret ?? this.mapboxSecret,
);
}
Map<String, dynamic> toMap() {
return {
'isEnable': isEnable,
'mapboxSecret': mapboxSecret,
};
}
factory MapboxInfo.fromMap(Map<String, dynamic> map) {
return MapboxInfo(
isEnable: map['isEnable'] ?? false,
mapboxSecret: map['mapboxSecret'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory MapboxInfo.fromJson(String source) => MapboxInfo.fromMap(json.decode(source));
@override
String toString() => 'MapboxInfo(isEnable: $isEnable, mapboxSecret: $mapboxSecret)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is MapboxInfo && other.isEnable == isEnable && other.mapboxSecret == mapboxSecret;
}
@override
int get hashCode => isEnable.hashCode ^ mapboxSecret.hashCode;
}

View File

@@ -0,0 +1,71 @@
import 'dart:convert';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/mapbox_info.model.dart';
import 'package:immich_mobile/shared/services/server_info.service.dart';
class ServerInfoState {
final MapboxInfo mapboxInfo;
ServerInfoState({
required this.mapboxInfo,
});
ServerInfoState copyWith({
MapboxInfo? mapboxInfo,
}) {
return ServerInfoState(
mapboxInfo: mapboxInfo ?? this.mapboxInfo,
);
}
Map<String, dynamic> toMap() {
return {
'mapboxInfo': mapboxInfo.toMap(),
};
}
factory ServerInfoState.fromMap(Map<String, dynamic> map) {
return ServerInfoState(
mapboxInfo: MapboxInfo.fromMap(map['mapboxInfo']),
);
}
String toJson() => json.encode(toMap());
factory ServerInfoState.fromJson(String source) => ServerInfoState.fromMap(json.decode(source));
@override
String toString() => 'ServerInfoState(mapboxInfo: $mapboxInfo)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ServerInfoState && other.mapboxInfo == mapboxInfo;
}
@override
int get hashCode => mapboxInfo.hashCode;
}
class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
ServerInfoNotifier()
: super(
ServerInfoState(
mapboxInfo: MapboxInfo(isEnable: false, mapboxSecret: ""),
),
);
final ServerInfoService _serverInfoService = ServerInfoService();
getMapboxInfo() async {
MapboxInfo mapboxInfoRes = await _serverInfoService.getMapboxInfo();
print(mapboxInfoRes);
state = state.copyWith(mapboxInfo: mapboxInfoRes);
}
}
final serverInfoProvider = StateNotifierProvider<ServerInfoNotifier, ServerInfoState>((ref) {
return ServerInfoNotifier();
});

View File

@@ -1,6 +1,5 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:immich_mobile/shared/models/mapbox_info.model.dart';
import 'package:immich_mobile/shared/services/network.service.dart';
import 'package:immich_mobile/shared/models/server_info.model.dart';
@@ -12,4 +11,10 @@ class ServerInfoService {
return ServerInfo.fromJson(response.toString());
}
Future<MapboxInfo> getMapboxInfo() async {
Response response = await _networkService.getRequest(url: 'server-info/mapbox');
return MapboxInfo.fromJson(response.toString());
}
}