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

@@ -44,7 +44,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.immich_mobile"
minSdkVersion flutter.minSdkVersion
minSdkVersion 20
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@@ -1,39 +1,23 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.immich_mobile">
<application
android:label="Immich"
android:name="${applicationName}"
android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.immich_mobile">
<application android:label="Immich" android:name="${applicationName}" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher">
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2"/>
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

View File

@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -34,8 +34,19 @@ target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
# post_install do |installer|
# installer.pods_project.targets.each do |target|
# flutter_additional_ios_build_settings(target)
# end
# end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
config.build_settings['ENABLE_BITCODE'] = 'YES'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
end
end
end

View File

@@ -8,6 +8,15 @@ PODS:
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- Mapbox-iOS-SDK (6.4.1):
- MapboxMobileEvents (~> 0.10.12)
- mapbox_gl (0.0.1):
- Flutter
- Mapbox-iOS-SDK (~> 6.4.0)
- MapboxAnnotationExtension (~> 0.0.1-beta.1)
- MapboxAnnotationExtension (0.0.1-beta.2):
- Mapbox-iOS-SDK (~> 6.0)
- MapboxMobileEvents (0.10.14)
- path_provider_ios (0.0.1):
- Flutter
- photo_manager (1.0.0):
@@ -26,6 +35,7 @@ DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- mapbox_gl (from `.symlinks/plugins/mapbox_gl/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
@@ -35,6 +45,9 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- FMDB
- Mapbox-iOS-SDK
- MapboxAnnotationExtension
- MapboxMobileEvents
- Toast
EXTERNAL SOURCES:
@@ -44,6 +57,8 @@ EXTERNAL SOURCES:
:path: Flutter
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
mapbox_gl:
:path: ".symlinks/plugins/mapbox_gl/ios"
path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios"
photo_manager:
@@ -60,6 +75,10 @@ SPEC CHECKSUMS:
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
Mapbox-iOS-SDK: f870f83cbdc7aa4a74afcee143aafb0dae390c82
mapbox_gl: 33c5ab6306cbfa72289bb3606d2cd2e8baee9ff0
MapboxAnnotationExtension: 4eee6c26349ef6d909f1a23a7eae2d0f7ca5fa7d
MapboxMobileEvents: 5a172cc9bbf8ac0e45ba86095cbee685ede248cc
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
photo_manager: 84fa94fbeb82e607333ea9a13c43b58e0903a463
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
@@ -67,6 +86,6 @@ SPEC CHECKSUMS:
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
PODFILE CHECKSUM: a44d1ba6d6faf8c61ee449ab69176b941340b431
COCOAPODS: 1.10.1

View File

@@ -341,6 +341,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
NEW_SETTING = "";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -419,6 +420,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
NEW_SETTING = "";
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -468,6 +470,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
NEW_SETTING = "";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;

View File

@@ -43,20 +43,27 @@
</array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true />
<true />
<key>NSPhotoLibraryUsageDescription</key>
<string>We need to manage backup your photos album</string>
<string>We need to manage backup your photos album</string>
<key>NSAppTransportSecurity</key>
<dict>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<true />
</dict>
<key>io.flutter.embedded_views_preview</key>
<true />
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true />
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
</dict>
</plist>

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());
}
}

View File

@@ -513,6 +513,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
mapbox_gl:
dependency: "direct main"
description:
name: mapbox_gl
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.0"
mapbox_gl_dart:
dependency: transitive
description:
name: mapbox_gl_dart
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.1"
mapbox_gl_platform_interface:
dependency: transitive
description:
name: mapbox_gl_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.0"
mapbox_gl_web:
dependency: transitive
description:
name: mapbox_gl_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.0"
matcher:
dependency: transitive
description:

View File

@@ -34,7 +34,8 @@ dependencies:
badges: ^2.0.2
photo_view: ^0.13.0
socket_io_client: ^2.0.0-beta.4-nullsafety.0
mapbox_gl: ^0.15.0
dev_dependencies:
flutter_test:
sdk: flutter