From 4daf2478aa3246b8188f9503bdc3c08baf79dadd Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Fri, 17 Nov 2023 21:46:18 +0000 Subject: [PATCH] feat(mobile): remove announcement overlay and show in app bar dialog (#5104) Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- mobile/assets/i18n/en-US.json | 1 + mobile/lib/main.dart | 4 - .../models/server_info/server_info.model.dart | 23 ++- mobile/lib/shared/models/store.dart | 1 - .../shared/providers/app_state.provider.dart | 3 - .../providers/release_info.provider.dart | 57 ------- .../providers/server_info.provider.dart | 49 +++++- .../shared/providers/websocket.provider.dart | 32 ++++ .../app_bar_dialog/app_bar_server_info.dart | 57 ++++++- mobile/lib/shared/ui/immich_app_bar.dart | 4 +- .../views/version_announcement_overlay.dart | 160 ------------------ mobile/pubspec.lock | 4 +- mobile/pubspec.yaml | 3 - 13 files changed, 159 insertions(+), 239 deletions(-) delete mode 100644 mobile/lib/shared/providers/release_info.provider.dart delete mode 100644 mobile/lib/shared/views/version_announcement_overlay.dart diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 7e3015c9fb..2862279794 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -292,6 +292,7 @@ "server_info_box_app_version": "App Version", "server_info_box_server_url": "Server URL", "server_info_box_server_version": "Server Version", + "server_info_box_latest_release":"Latest Version", "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", "setting_image_viewer_original_title": "Load original image", diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 13eda9d6ea..224fe3ef44 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -26,11 +26,9 @@ import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/app_state.provider.dart'; import 'package:immich_mobile/shared/providers/db.provider.dart'; -import 'package:immich_mobile/shared/providers/release_info.provider.dart'; import 'package:immich_mobile/shared/services/immich_logger.service.dart'; import 'package:immich_mobile/shared/services/local_notification.service.dart'; import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; -import 'package:immich_mobile/shared/views/version_announcement_overlay.dart'; import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; import 'package:immich_mobile/utils/immich_app_theme.dart'; import 'package:immich_mobile/utils/migration.dart'; @@ -196,7 +194,6 @@ class ImmichAppState extends ConsumerState @override Widget build(BuildContext context) { var router = ref.watch(appRouterProvider); - ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo(); return MaterialApp( localizationsDelegates: context.localizationDelegates, @@ -217,7 +214,6 @@ class ImmichAppState extends ConsumerState ), ), const ImmichLoadingOverlay(), - const VersionAnnouncementOverlay(), ], ), ); diff --git a/mobile/lib/shared/models/server_info/server_info.model.dart b/mobile/lib/shared/models/server_info/server_info.model.dart index db81507685..b423f598c3 100644 --- a/mobile/lib/shared/models/server_info/server_info.model.dart +++ b/mobile/lib/shared/models/server_info/server_info.model.dart @@ -5,43 +5,52 @@ import 'package:immich_mobile/shared/models/server_info/server_version.model.dar class ServerInfo { final ServerVersion serverVersion; + final ServerVersion latestVersion; final ServerFeatures serverFeatures; final ServerConfig serverConfig; final ServerDiskInfo serverDiskInfo; final bool isVersionMismatch; + final bool isNewReleaseAvailable; final String versionMismatchErrorMessage; ServerInfo({ required this.serverVersion, + required this.latestVersion, required this.serverFeatures, required this.serverConfig, - required this.isVersionMismatch, required this.serverDiskInfo, + required this.isVersionMismatch, + required this.isNewReleaseAvailable, required this.versionMismatchErrorMessage, }); ServerInfo copyWith({ ServerVersion? serverVersion, + ServerVersion? latestVersion, ServerFeatures? serverFeatures, ServerConfig? serverConfig, ServerDiskInfo? serverDiskInfo, bool? isVersionMismatch, + bool? isNewReleaseAvailable, String? versionMismatchErrorMessage, }) { return ServerInfo( serverVersion: serverVersion ?? this.serverVersion, + latestVersion: latestVersion ?? this.latestVersion, serverFeatures: serverFeatures ?? this.serverFeatures, serverConfig: serverConfig ?? this.serverConfig, + serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo, isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch, + isNewReleaseAvailable: + isNewReleaseAvailable ?? this.isNewReleaseAvailable, versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage, - serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo, ); } @override String toString() { - return 'ServerInfo(serverVersion: $serverVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, isVersionMismatch: $isVersionMismatch, versionMismatchErrorMessage: $versionMismatchErrorMessage, serverDiskInfo: $serverDiskInfo)'; + return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, isVersionMismatch: $isVersionMismatch, isNewReleaseAvailable: $isNewReleaseAvailable, versionMismatchErrorMessage: $versionMismatchErrorMessage)'; } @override @@ -50,20 +59,24 @@ class ServerInfo { return other is ServerInfo && other.serverVersion == serverVersion && + other.latestVersion == latestVersion && other.serverFeatures == serverFeatures && other.serverConfig == serverConfig && other.serverDiskInfo == serverDiskInfo && other.isVersionMismatch == isVersionMismatch && + other.isNewReleaseAvailable == isNewReleaseAvailable && other.versionMismatchErrorMessage == versionMismatchErrorMessage; } @override int get hashCode { return serverVersion.hashCode ^ + latestVersion.hashCode ^ serverFeatures.hashCode ^ serverConfig.hashCode ^ + serverDiskInfo.hashCode ^ isVersionMismatch.hashCode ^ - versionMismatchErrorMessage.hashCode ^ - serverDiskInfo.hashCode; + isNewReleaseAvailable.hashCode ^ + versionMismatchErrorMessage.hashCode; } } diff --git a/mobile/lib/shared/models/store.dart b/mobile/lib/shared/models/store.dart index 669dbb048d..8a186af9a7 100644 --- a/mobile/lib/shared/models/store.dart +++ b/mobile/lib/shared/models/store.dart @@ -153,7 +153,6 @@ enum StoreKey { backupRequireWifi(6, type: bool), backupRequireCharging(7, type: bool), backupTriggerDelay(8, type: int), - githubReleaseInfo(9, type: String), serverUrl(10, type: String), accessToken(11, type: String), serverEndpoint(12, type: String), diff --git a/mobile/lib/shared/providers/app_state.provider.dart b/mobile/lib/shared/providers/app_state.provider.dart index dce2c054c3..0ded4d79d2 100644 --- a/mobile/lib/shared/providers/app_state.provider.dart +++ b/mobile/lib/shared/providers/app_state.provider.dart @@ -11,7 +11,6 @@ import 'package:immich_mobile/modules/memories/providers/memory.provider.dart'; import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; -import 'package:immich_mobile/shared/providers/release_info.provider.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/providers/tab.provider.dart'; import 'package:immich_mobile/shared/providers/websocket.provider.dart'; @@ -70,8 +69,6 @@ class AppStateNotiifer extends StateNotifier { _ref.read(websocketProvider.notifier).connect(); - _ref.read(releaseInfoProvider.notifier).checkGithubReleaseInfo(); - _ref .read(notificationPermissionProvider.notifier) .getNotificationPermission(); diff --git a/mobile/lib/shared/providers/release_info.provider.dart b/mobile/lib/shared/providers/release_info.provider.dart deleted file mode 100644 index ded511c009..0000000000 --- a/mobile/lib/shared/providers/release_info.provider.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:http/http.dart'; -import 'package:immich_mobile/shared/models/store.dart'; -import 'package:immich_mobile/shared/views/version_announcement_overlay.dart'; -import 'package:logging/logging.dart'; - -class ReleaseInfoNotifier extends StateNotifier { - ReleaseInfoNotifier() : super(""); - final log = Logger('ReleaseInfoNotifier'); - void checkGithubReleaseInfo() async { - final Client client = Client(); - - try { - final String? localReleaseVersion = - Store.tryGet(StoreKey.githubReleaseInfo); - final res = await client.get( - Uri.parse( - "https://api.github.com/repos/immich-app/immich/releases/latest", - ), - headers: {"Accept": "application/vnd.github.v3+json"}, - ); - - if (res.statusCode == 200) { - final data = jsonDecode(res.body); - String latestTagVersion = data["tag_name"]; - state = latestTagVersion; - - if (localReleaseVersion == null && latestTagVersion.isNotEmpty) { - VersionAnnouncementOverlayController.appLoader.show(); - return; - } - - if (latestTagVersion.isNotEmpty && - localReleaseVersion != latestTagVersion) { - VersionAnnouncementOverlayController.appLoader.show(); - return; - } - } - } catch (e) { - debugPrint("Error gettting latest release version"); - - state = ""; - } - } - - void acknowledgeNewVersion() { - Store.put(StoreKey.githubReleaseInfo, state); - VersionAnnouncementOverlayController.appLoader.hide(); - } -} - -final releaseInfoProvider = StateNotifierProvider( - (ref) => ReleaseInfoNotifier(), -); diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart index 3c66fe76d5..a012379ef1 100644 --- a/mobile/lib/shared/providers/server_info.provider.dart +++ b/mobile/lib/shared/providers/server_info.provider.dart @@ -18,6 +18,11 @@ class ServerInfoNotifier extends StateNotifier { minor: 0, patch: 0, ), + latestVersion: const ServerVersion( + major: 0, + minor: 0, + patch: 0, + ), serverFeatures: const ServerFeatures( map: true, trash: true, @@ -32,6 +37,7 @@ class ServerInfoNotifier extends StateNotifier { diskUsagePercentage: 0, ), isVersionMismatch: false, + isNewReleaseAvailable: false, versionMismatchErrorMessage: "", ), ); @@ -55,6 +61,10 @@ class ServerInfoNotifier extends StateNotifier { return; } + await _checkServerVersionMismatch(serverVersion); + } + + _checkServerVersionMismatch(ServerVersion serverVersion) async { state = state.copyWith(serverVersion: serverVersion); var packageInfo = await PackageInfo.fromPlatform(); @@ -67,7 +77,15 @@ class ServerInfoNotifier extends StateNotifier { versionMismatchErrorMessage: "Server is out of date. Please update to the latest major version.", ); + return; + } + if (appVersion["major"]! < serverVersion.major) { + state = state.copyWith( + isVersionMismatch: true, + versionMismatchErrorMessage: + "Mobile App is out of date. Please update to the latest major version.", + ); return; } @@ -75,9 +93,17 @@ class ServerInfoNotifier extends StateNotifier { state = state.copyWith( isVersionMismatch: true, versionMismatchErrorMessage: - "Server is out of date. Consider updating to the latest minor version.", + "Server is out of date. Please update to the latest minor version.", ); + return; + } + if (appVersion["minor"]! < serverVersion.minor) { + state = state.copyWith( + isVersionMismatch: true, + versionMismatchErrorMessage: + "Mobile App is out of date. Please update to the latest minor version.", + ); return; } @@ -87,6 +113,25 @@ class ServerInfoNotifier extends StateNotifier { ); } + handleNewRelease( + ServerVersion serverVersion, + ServerVersion latestVersion, + ) { + // Update local server version + _checkServerVersionMismatch(serverVersion); + + final majorEqual = latestVersion.major == serverVersion.major; + final minorEqual = majorEqual && latestVersion.minor == serverVersion.minor; + final newVersionAvailable = latestVersion.major > serverVersion.major || + (majorEqual && latestVersion.minor > serverVersion.minor) || + (minorEqual && latestVersion.patch > serverVersion.patch); + + state = state.copyWith( + latestVersion: latestVersion, + isNewReleaseAvailable: newVersionAvailable, + ); + } + getServerFeatures() async { final serverFeatures = await _serverInfoService.getServerFeatures(); if (serverFeatures == null) { @@ -120,5 +165,5 @@ class ServerInfoNotifier extends StateNotifier { final serverInfoProvider = StateNotifierProvider((ref) { - return ServerInfoNotifier(ref.watch(serverInfoServiceProvider)); + return ServerInfoNotifier(ref.read(serverInfoServiceProvider)); }); diff --git a/mobile/lib/shared/providers/websocket.provider.dart b/mobile/lib/shared/providers/websocket.provider.dart index f49bc66868..018f7ea7a5 100644 --- a/mobile/lib/shared/providers/websocket.provider.dart +++ b/mobile/lib/shared/providers/websocket.provider.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/shared/models/asset.dart'; +import 'package:immich_mobile/shared/models/server_info/server_version.model.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart'; @@ -130,6 +131,7 @@ class WebsocketNotifier extends StateNotifier { socket.on('on_asset_trash', _handleServerUpdates); socket.on('on_asset_restore', _handleServerUpdates); socket.on('on_asset_update', _handleServerUpdates); + socket.on('on_new_release', _handleReleaseUpdates); } catch (e) { debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}"); } @@ -204,6 +206,36 @@ class WebsocketNotifier extends StateNotifier { addPendingChange(PendingAction.assetDelete, data); _debounce(handlePendingChanges); } + + _handleReleaseUpdates(dynamic data) { + // Json guard + if (data is! Map) { + return; + } + + final json = data.cast(); + final serverVersionJson = + json.containsKey('serverVersion') ? json['serverVersion'] : null; + final releaseVersionJson = + json.containsKey('releaseVersion') ? json['releaseVersion'] : null; + if (serverVersionJson == null || releaseVersionJson == null) { + return; + } + + final serverVersionDto = + ServerVersionResponseDto.fromJson(serverVersionJson); + final releaseVersionDto = + ServerVersionResponseDto.fromJson(releaseVersionJson); + if (serverVersionDto == null || releaseVersionDto == null) { + return; + } + + final serverVersion = ServerVersion.fromDto(serverVersionDto); + final releaseVersion = ServerVersion.fromDto(releaseVersionDto); + _ref + .read(serverInfoProvider.notifier) + .handleNewRelease(serverVersion, releaseVersion); + } } final websocketProvider = diff --git a/mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart b/mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart index fef9e1daab..ec8fb09116 100644 --- a/mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart +++ b/mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart @@ -137,7 +137,7 @@ class AppBarServerInfo extends HookConsumerWidget { child: Text( serverInfoState.serverVersion.major > 0 ? "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}" - : "?", + : "--", style: TextStyle( fontSize: 11, color: context.textTheme.labelSmall?.color @@ -207,6 +207,61 @@ class AppBarServerInfo extends HookConsumerWidget { ), ], ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: Divider( + color: Color.fromARGB(101, 201, 201, 201), + thickness: 1, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Row( + children: [ + if (serverInfoState.isNewReleaseAvailable) + const Padding( + padding: EdgeInsets.only(right: 5.0), + child: Icon( + Icons.info, + color: Color.fromARGB(255, 243, 188, 106), + size: 12, + ), + ), + Text( + "server_info_box_latest_release".tr(), + style: TextStyle( + fontSize: 11, + color: context.textTheme.labelSmall?.color, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + Expanded( + flex: 0, + child: Padding( + padding: const EdgeInsets.only(right: 10.0), + child: Text( + serverInfoState.latestVersion.major > 0 + ? "${serverInfoState.latestVersion.major}.${serverInfoState.latestVersion.minor}.${serverInfoState.latestVersion.patch}" + : "--", + style: TextStyle( + fontSize: 11, + color: context.textTheme.labelSmall?.color + ?.withOpacity(0.5), + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), ], ), ), diff --git a/mobile/lib/shared/ui/immich_app_bar.dart b/mobile/lib/shared/ui/immich_app_bar.dart index 144adc7876..49f5453a7b 100644 --- a/mobile/lib/shared/ui/immich_app_bar.dart +++ b/mobile/lib/shared/ui/immich_app_bar.dart @@ -50,7 +50,9 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { ), backgroundColor: Colors.transparent, alignment: Alignment.bottomRight, - isLabelVisible: serverInfoState.isVersionMismatch, + isLabelVisible: serverInfoState.isVersionMismatch || + ((user?.isAdmin ?? false) && + serverInfoState.isNewReleaseAvailable), offset: const Offset(2, 2), child: user == null ? const Icon( diff --git a/mobile/lib/shared/views/version_announcement_overlay.dart b/mobile/lib/shared/views/version_announcement_overlay.dart deleted file mode 100644 index 7a01ef304d..0000000000 --- a/mobile/lib/shared/views/version_announcement_overlay.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/shared/providers/release_info.provider.dart'; -import 'package:immich_mobile/shared/providers/admin_provider.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class VersionAnnouncementOverlay extends HookConsumerWidget { - const VersionAnnouncementOverlay({ - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final bool isAdmin = ref.watch(isAdminProvider); - - if (!isAdmin) { - return const SizedBox.shrink(); // Don't show anything for non-admins - } - - void goToReleaseNote() async { - final Uri url = - Uri.parse('https://github.com/immich-app/immich/releases/latest'); - await launchUrl(url); - } - - void onAcknowledgeTapped() { - ref.watch(releaseInfoProvider.notifier).acknowledgeNewVersion(); - } - - return ValueListenableBuilder( - valueListenable: - VersionAnnouncementOverlayController.appLoader.loaderShowingNotifier, - builder: (context, shouldShow, child) { - if (shouldShow) { - return Scaffold( - backgroundColor: Colors.black38, - body: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 307), - child: Wrap( - children: [ - Card( - child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "version_announcement_overlay_title", - style: TextStyle( - fontSize: 16, - fontFamily: 'WorkSans', - fontWeight: FontWeight.bold, - color: Colors.indigo, - ), - ).tr(), - Padding( - padding: const EdgeInsets.only(top: 16.0), - child: RichText( - text: TextSpan( - style: const TextStyle( - fontSize: 14, - fontFamily: 'WorkSans', - color: Colors.black87, - height: 1.2, - ), - children: [ - TextSpan( - text: - 'version_announcement_overlay_text_1' - .tr(), - ), - const TextSpan( - text: ' Immich ', - style: TextStyle( - fontFamily: "SnowBurstOne", - color: Colors.indigo, - fontWeight: FontWeight.bold, - ), - ), - TextSpan( - text: - "version_announcement_overlay_text_2" - .tr(), - ), - TextSpan( - text: - "version_announcement_overlay_release_notes" - .tr(), - style: const TextStyle( - decoration: TextDecoration.underline, - ), - recognizer: TapGestureRecognizer() - ..onTap = goToReleaseNote, - ), - TextSpan( - text: - "version_announcement_overlay_text_3" - .tr(), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.only(top: 16.0), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: const StadiumBorder(), - visualDensity: VisualDensity.standard, - backgroundColor: Colors.indigo, - foregroundColor: Colors.grey[50], - elevation: 2, - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 25, - ), - ), - onPressed: onAcknowledgeTapped, - child: const Text( - "version_announcement_overlay_ack", - style: TextStyle( - fontSize: 14, - ), - ).tr(), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ), - ); - } else { - return const SizedBox(); - } - }, - ); - } -} - -class VersionAnnouncementOverlayController { - static final VersionAnnouncementOverlayController appLoader = - VersionAnnouncementOverlayController(); - ValueNotifier loaderShowingNotifier = ValueNotifier(false); - ValueNotifier loaderTextNotifier = ValueNotifier('error message'); - - void show() { - loaderShowingNotifier.value = true; - } - - void hide() { - loaderShowingNotifier.value = false; - } -} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index eca0d1246d..a573f6bf71 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -346,7 +346,7 @@ packages: source: hosted version: "0.0.2" executor_lib: - dependency: "direct main" + dependency: transitive description: name: executor_lib sha256: "544889daa5726462657dab6410b75f2f8e3a77479d85b307a25c346e243bc38e" @@ -1562,7 +1562,7 @@ packages: source: hosted version: "1.0.0" vector_tile_renderer: - dependency: "direct main" + dependency: transitive description: name: vector_tile_renderer sha256: de212da0f5e48107d3b763a940a428eb1f49d8a4664d41ac0b654f77209a2d0b diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 88c54c6085..b6664ec2ba 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -32,9 +32,6 @@ dependencies: git: url: https://github.com/shenlong-tanwen/flutter-vector-map-tiles.git ref: immich_above_4 - # Adding the following as direct dependency since flutter cannot detect them as transitive dep - vector_tile_renderer: ^4.0.0 - executor_lib: 1.1.1 flutter_udid: ^2.0.0 package_info_plus: ^4.1.0 url_launcher: ^6.1.3