浏览代码

feat(mobile): remove announcement overlay and show in app bar dialog (#5104)

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
shenlong 1 年之前
父节点
当前提交
4daf2478aa

+ 1 - 0
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",

+ 0 - 4
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<ImmichApp>
   @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<ImmichApp>
             ),
           ),
           const ImmichLoadingOverlay(),
-          const VersionAnnouncementOverlay(),
         ],
       ),
     );

+ 18 - 5
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;
   }
 }

+ 0 - 1
mobile/lib/shared/models/store.dart

@@ -153,7 +153,6 @@ enum StoreKey<T> {
   backupRequireWifi<bool>(6, type: bool),
   backupRequireCharging<bool>(7, type: bool),
   backupTriggerDelay<int>(8, type: int),
-  githubReleaseInfo<String>(9, type: String),
   serverUrl<String>(10, type: String),
   accessToken<String>(11, type: String),
   serverEndpoint<String>(12, type: String),

+ 0 - 3
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<AppStateEnum> {
 
     _ref.read(websocketProvider.notifier).connect();
 
-    _ref.read(releaseInfoProvider.notifier).checkGithubReleaseInfo();
-
     _ref
         .read(notificationPermissionProvider.notifier)
         .getNotificationPermission();

+ 0 - 57
mobile/lib/shared/providers/release_info.provider.dart

@@ -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<String> {
-  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<ReleaseInfoNotifier, String>(
-  (ref) => ReleaseInfoNotifier(),
-);

+ 47 - 2
mobile/lib/shared/providers/server_info.provider.dart

@@ -18,6 +18,11 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
               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<ServerInfo> {
               diskUsagePercentage: 0,
             ),
             isVersionMismatch: false,
+            isNewReleaseAvailable: false,
             versionMismatchErrorMessage: "",
           ),
         );
@@ -55,6 +61,10 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
       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<ServerInfo> {
         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<ServerInfo> {
       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<ServerInfo> {
     );
   }
 
+  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<ServerInfo> {
 
 final serverInfoProvider =
     StateNotifierProvider<ServerInfoNotifier, ServerInfo>((ref) {
-  return ServerInfoNotifier(ref.watch(serverInfoServiceProvider));
+  return ServerInfoNotifier(ref.read(serverInfoServiceProvider));
 });

+ 32 - 0
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<WebsocketState> {
         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<WebsocketState> {
     addPendingChange(PendingAction.assetDelete, data);
     _debounce(handlePendingChanges);
   }
+
+  _handleReleaseUpdates(dynamic data) {
+    // Json guard
+    if (data is! Map) {
+      return;
+    }
+
+    final json = data.cast<String, dynamic>();
+    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 =

+ 56 - 1
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,
+                        ),
+                      ),
+                    ),
+                  ),
+                ],
+              ),
             ],
           ),
         ),

+ 3 - 1
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(

+ 0 - 160
mobile/lib/shared/views/version_announcement_overlay.dart

@@ -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<bool>(
-      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>[
-                                    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<bool> loaderShowingNotifier = ValueNotifier(false);
-  ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
-
-  void show() {
-    loaderShowingNotifier.value = true;
-  }
-
-  void hide() {
-    loaderShowingNotifier.value = false;
-  }
-}

+ 2 - 2
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

+ 0 - 3
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