diff --git a/i18n/en.json b/i18n/en.json index e1702611a7..95b6246433 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1547,13 +1547,11 @@ "privacy": "Privacy", "profile": "Profile", "profile_drawer_app_logs": "Logs", - "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.", - "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.", + "profile_drawer_client_out_of_date": "Mobile App update available.", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", - "profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.", - "profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.", + "profile_drawer_server_out_of_date": "Server is out of date. Please update to the latest version.", "profile_image_of_user": "Profile image of {user}", "profile_picture_set": "Profile picture set.", "public_album": "Public album", diff --git a/mobile/lib/models/server_info/server_info.model.dart b/mobile/lib/models/server_info/server_info.model.dart index 0fa80d45d8..6201c74424 100644 --- a/mobile/lib/models/server_info/server_info.model.dart +++ b/mobile/lib/models/server_info/server_info.model.dart @@ -9,7 +9,8 @@ class ServerInfo { final ServerFeatures serverFeatures; final ServerConfig serverConfig; final ServerDiskInfo serverDiskInfo; - final bool isVersionMismatch; + final bool isClientOutOfDate; + final bool isServerOutOfDate; final bool isNewReleaseAvailable; final String versionMismatchErrorMessage; @@ -19,7 +20,8 @@ class ServerInfo { required this.serverFeatures, required this.serverConfig, required this.serverDiskInfo, - required this.isVersionMismatch, + required this.isClientOutOfDate, + required this.isServerOutOfDate, required this.isNewReleaseAvailable, required this.versionMismatchErrorMessage, }); @@ -30,7 +32,8 @@ class ServerInfo { ServerFeatures? serverFeatures, ServerConfig? serverConfig, ServerDiskInfo? serverDiskInfo, - bool? isVersionMismatch, + bool? isClientOutOfDate, + bool? isServerOutOfDate, bool? isNewReleaseAvailable, String? versionMismatchErrorMessage, }) { @@ -40,7 +43,8 @@ class ServerInfo { serverFeatures: serverFeatures ?? this.serverFeatures, serverConfig: serverConfig ?? this.serverConfig, serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo, - isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch, + isClientOutOfDate: isClientOutOfDate ?? this.isClientOutOfDate, + isServerOutOfDate: isServerOutOfDate ?? this.isServerOutOfDate, isNewReleaseAvailable: isNewReleaseAvailable ?? this.isNewReleaseAvailable, versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage, ); @@ -48,7 +52,7 @@ class ServerInfo { @override String toString() { - return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, isVersionMismatch: $isVersionMismatch, isNewReleaseAvailable: $isNewReleaseAvailable, versionMismatchErrorMessage: $versionMismatchErrorMessage)'; + return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, isClientOutOfDate: $isClientOutOfDate, isServerOutOfDate: $isServerOutOfDate, isNewReleaseAvailable: $isNewReleaseAvailable, versionMismatchErrorMessage: $versionMismatchErrorMessage)'; } @override @@ -61,7 +65,8 @@ class ServerInfo { other.serverFeatures == serverFeatures && other.serverConfig == serverConfig && other.serverDiskInfo == serverDiskInfo && - other.isVersionMismatch == isVersionMismatch && + other.isClientOutOfDate == isClientOutOfDate && + other.isServerOutOfDate == isServerOutOfDate && other.isNewReleaseAvailable == isNewReleaseAvailable && other.versionMismatchErrorMessage == versionMismatchErrorMessage; } @@ -73,7 +78,8 @@ class ServerInfo { serverFeatures.hashCode ^ serverConfig.hashCode ^ serverDiskInfo.hashCode ^ - isVersionMismatch.hashCode ^ + isClientOutOfDate.hashCode ^ + isServerOutOfDate.hashCode ^ isNewReleaseAvailable.hashCode ^ versionMismatchErrorMessage.hashCode; } diff --git a/mobile/lib/providers/server_info.provider.dart b/mobile/lib/providers/server_info.provider.dart index 25b1002b7a..9a359f9e0d 100644 --- a/mobile/lib/providers/server_info.provider.dart +++ b/mobile/lib/providers/server_info.provider.dart @@ -24,7 +24,8 @@ class ServerInfoNotifier extends StateNotifier { mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', ), serverDiskInfo: ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0), - isVersionMismatch: false, + isClientOutOfDate: false, + isServerOutOfDate: false, isNewReleaseAvailable: false, versionMismatchErrorMessage: "", ), @@ -43,15 +44,16 @@ class ServerInfoNotifier extends StateNotifier { try { final serverVersion = await _serverInfoService.getServerVersion(); + // using isClientOutOfDate since that will show to users reguardless of if they are an admin if (serverVersion == null) { - state = state.copyWith(isVersionMismatch: true, versionMismatchErrorMessage: "common_server_error".tr()); + state = state.copyWith(isClientOutOfDate: true, versionMismatchErrorMessage: "common_server_error".tr()); return; } await _checkServerVersionMismatch(serverVersion); } catch (e, stackTrace) { _log.severe("Failed to get server version", e, stackTrace); - state = state.copyWith(isVersionMismatch: true); + state = state.copyWith(isClientOutOfDate: true); return; } } @@ -63,39 +65,23 @@ class ServerInfoNotifier extends StateNotifier { Map appVersion = _getDetailVersion(packageInfo.version); - if (appVersion["major"]! > serverVersion.major) { + if (appVersion["major"]! > serverVersion.major || appVersion["minor"]! > serverVersion.minor) { state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_major".tr(), + isServerOutOfDate: true, + versionMismatchErrorMessage: "profile_drawer_server_out_of_date".tr(), ); return; } - if (appVersion["major"]! < serverVersion.major) { + if (appVersion["major"]! < serverVersion.major || appVersion["minor"]! < serverVersion.minor) { state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_major".tr(), + isClientOutOfDate: true, + versionMismatchErrorMessage: "profile_drawer_client_out_of_date".tr(), ); return; } - if (appVersion["minor"]! > serverVersion.minor) { - state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_minor".tr(), - ); - return; - } - - if (appVersion["minor"]! < serverVersion.minor) { - state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_minor".tr(), - ); - return; - } - - state = state.copyWith(isVersionMismatch: false, versionMismatchErrorMessage: ""); + state = state.copyWith(isClientOutOfDate: false, isServerOutOfDate: false, versionMismatchErrorMessage: ""); } handleNewRelease(ServerVersion serverVersion, ServerVersion latestVersion) { diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart index 4aacfb3322..929e0454fa 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; @@ -7,8 +9,10 @@ import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; class AppBarServerInfo extends HookConsumerWidget { const AppBarServerInfo({super.key}); @@ -18,16 +22,35 @@ class AppBarServerInfo extends HookConsumerWidget { ref.watch(localeProvider); ServerInfo serverInfoState = ref.watch(serverInfoProvider); + final user = ref.watch(currentUserProvider); + final appInfo = useState({}); const titleFontSize = 12.0; const contentFontSize = 11.0; + final showWarning = + serverInfoState.isClientOutOfDate || + ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable) && false; + getPackageInfo() async { PackageInfo packageInfo = await PackageInfo.fromPlatform(); appInfo.value = {"version": packageInfo.version, "buildNumber": packageInfo.buildNumber}; } + void openUpdateLink() { + if (Platform.isIOS) { + launchUrl(Uri.parse("https://apps.apple.com/app/id1613945652"), mode: LaunchMode.externalApplication); + } else if (Platform.isAndroid) { + launchUrl( + Uri.parse("https://play.google.com/store/apps/details?id=app.alextran.immich"), + mode: LaunchMode.externalApplication, + ); + } else { + launchUrl(Uri.parse("https://immich.app/download"), mode: LaunchMode.externalApplication); + } + } + useEffect(() { getPackageInfo(); return null; @@ -45,17 +68,38 @@ class AppBarServerInfo extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - serverInfoState.isVersionMismatch - ? serverInfoState.versionMismatchErrorMessage - : "profile_drawer_client_server_up_to_date".tr(), - textAlign: TextAlign.center, - style: TextStyle(fontSize: 11, color: context.primaryColor, fontWeight: FontWeight.w500), + if (showWarning) ...[ + SizedBox( + width: double.infinity, + child: Container( + decoration: const BoxDecoration( + color: Color.fromARGB(80, 243, 188, 106), + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8, + children: [ + Text( + serverInfoState.versionMismatchErrorMessage, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w500), + ), + if (serverInfoState.isClientOutOfDate) + IconButton( + onPressed: openUpdateLink, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + style: IconButton.styleFrom(tapTargetSize: MaterialTapTargetSize.shrinkWrap), + icon: const Icon(Icons.open_in_new, size: 16), + ), + ], + ), + ), ), - ), - const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)), + const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)), + ], Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index 28b5c535d2..09fdfddbca 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -47,7 +47,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { backgroundColor: Colors.transparent, alignment: Alignment.bottomRight, isLabelVisible: - serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), + serverInfoState.isClientOutOfDate || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), offset: const Offset(-2, -12), child: user == null ? const Icon(Icons.face_outlined, size: widgetSize) diff --git a/mobile/lib/widgets/common/immich_sliver_app_bar.dart b/mobile/lib/widgets/common/immich_sliver_app_bar.dart index 90c213599c..61382cd7ba 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -149,7 +149,7 @@ class _ProfileIndicator extends ConsumerWidget { backgroundColor: Colors.transparent, alignment: Alignment.bottomRight, isLabelVisible: - serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), + serverInfoState.isClientOutOfDate || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), offset: const Offset(-2, -12), child: user == null ? const Icon(Icons.face_outlined, size: widgetSize)