diff --git a/i18n/en.json b/i18n/en.json index 51cd4181c5..8669a048b8 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -474,6 +474,7 @@ "app_bar_signout_dialog_title": "Sign out", "app_download_links": "App Download Links", "app_settings": "App Settings", + "app_update_available": "App update is available", "appears_in": "Appears in", "apply_count": "Apply ({count, number})", "archive": "Archive", @@ -706,7 +707,6 @@ "comments_and_likes": "Comments & likes", "comments_are_disabled": "Comments are disabled", "common_create_new_album": "Create new album", - "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.", "completed": "Completed", "confirm": "Confirm", "confirm_admin_password": "Confirm Admin Password", @@ -1555,13 +1555,9 @@ "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_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_image_of_user": "Profile image of {user}", "profile_picture_set": "Profile picture set.", "public_album": "Public album", @@ -1790,6 +1786,7 @@ "server_online": "Server Online", "server_privacy": "Server Privacy", "server_stats": "Server Stats", + "server_update_available": "Server update is available", "server_version": "Server Version", "set": "Set", "set_as_album_cover": "Set as album cover", @@ -2031,6 +2028,7 @@ "troubleshoot": "Troubleshoot", "type": "Type", "unable_to_change_pin_code": "Unable to change PIN code", + "unable_to_check_version": "Unable to check app or server version", "unable_to_setup_pin_code": "Unable to setup PIN code", "unarchive": "Unarchive", "unarchive_action_prompt": "{count} removed from Archive", diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index 7429616f14..10f4e88f0f 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -49,3 +49,7 @@ const double kUploadStatusFailed = -1.0; const double kUploadStatusCanceled = -2.0; const int kMinMonthsToEnableScrubberSnap = 12; + +const String kImmichAppStoreLink = "https://apps.apple.com/app/immich/id6449244941"; +const String kImmichPlayStoreLink = "https://play.google.com/store/apps/details?id=app.alextran.immich"; +const String kImmichLatestRelease = "https://github.com/immich-app/immich/releases/latest"; diff --git a/mobile/lib/models/server_info/server_info.model.dart b/mobile/lib/models/server_info/server_info.model.dart index 0fa80d45d8..a034960ddb 100644 --- a/mobile/lib/models/server_info/server_info.model.dart +++ b/mobile/lib/models/server_info/server_info.model.dart @@ -1,17 +1,30 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/models/server_info/server_config.model.dart'; import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; import 'package:immich_mobile/models/server_info/server_features.model.dart'; import 'package:immich_mobile/models/server_info/server_version.model.dart'; +enum VersionStatus { + upToDate, + clientOutOfDate, + serverOutOfDate, + error; + + String get message => switch (this) { + VersionStatus.upToDate => "", + VersionStatus.clientOutOfDate => "app_update_available".tr(), + VersionStatus.serverOutOfDate => "server_update_available".tr(), + VersionStatus.error => "unable_to_check_version".tr(), + }; +} + 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; + final VersionStatus versionStatus; const ServerInfo({ required this.serverVersion, @@ -19,9 +32,7 @@ class ServerInfo { required this.serverFeatures, required this.serverConfig, required this.serverDiskInfo, - required this.isVersionMismatch, - required this.isNewReleaseAvailable, - required this.versionMismatchErrorMessage, + required this.versionStatus, }); ServerInfo copyWith({ @@ -30,9 +41,7 @@ class ServerInfo { ServerFeatures? serverFeatures, ServerConfig? serverConfig, ServerDiskInfo? serverDiskInfo, - bool? isVersionMismatch, - bool? isNewReleaseAvailable, - String? versionMismatchErrorMessage, + VersionStatus? versionStatus, }) { return ServerInfo( serverVersion: serverVersion ?? this.serverVersion, @@ -40,15 +49,13 @@ class ServerInfo { serverFeatures: serverFeatures ?? this.serverFeatures, serverConfig: serverConfig ?? this.serverConfig, serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo, - isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch, - isNewReleaseAvailable: isNewReleaseAvailable ?? this.isNewReleaseAvailable, - versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage, + versionStatus: versionStatus ?? this.versionStatus, ); } @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, versionStatus: $versionStatus)'; } @override @@ -61,9 +68,7 @@ class ServerInfo { other.serverFeatures == serverFeatures && other.serverConfig == serverConfig && other.serverDiskInfo == serverDiskInfo && - other.isVersionMismatch == isVersionMismatch && - other.isNewReleaseAvailable == isNewReleaseAvailable && - other.versionMismatchErrorMessage == versionMismatchErrorMessage; + other.versionStatus == versionStatus; } @override @@ -73,8 +78,6 @@ class ServerInfo { serverFeatures.hashCode ^ serverConfig.hashCode ^ serverDiskInfo.hashCode ^ - isVersionMismatch.hashCode ^ - isNewReleaseAvailable.hashCode ^ - versionMismatchErrorMessage.hashCode; + versionStatus.hashCode; } } diff --git a/mobile/lib/models/server_info/server_version.model.dart b/mobile/lib/models/server_info/server_version.model.dart index 2cb41b0415..3aea98a80d 100644 --- a/mobile/lib/models/server_info/server_version.model.dart +++ b/mobile/lib/models/server_info/server_version.model.dart @@ -1,32 +1,13 @@ +import 'package:immich_mobile/utils/semver.dart'; import 'package:openapi/api.dart'; -class ServerVersion { - final int major; - final int minor; - final int patch; - - const ServerVersion({required this.major, required this.minor, required this.patch}); - - ServerVersion copyWith({int? major, int? minor, int? patch}) { - return ServerVersion(major: major ?? this.major, minor: minor ?? this.minor, patch: patch ?? this.patch); - } +class ServerVersion extends SemVer { + const ServerVersion({required super.major, required super.minor, required super.patch}); @override String toString() { return 'ServerVersion(major: $major, minor: $minor, patch: $patch)'; } - ServerVersion.fromDto(ServerVersionResponseDto dto) : major = dto.major, minor = dto.minor, patch = dto.patch_; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is ServerVersion && other.major == major && other.minor == minor && other.patch == patch; - } - - @override - int get hashCode { - return major.hashCode ^ minor.hashCode ^ patch.hashCode; - } + ServerVersion.fromDto(ServerVersionResponseDto dto) : super(major: dto.major, minor: dto.minor, patch: dto.patch_); } diff --git a/mobile/lib/providers/server_info.provider.dart b/mobile/lib/providers/server_info.provider.dart index 25b1002b7a..7a424c332d 100644 --- a/mobile/lib/providers/server_info.provider.dart +++ b/mobile/lib/providers/server_info.provider.dart @@ -1,11 +1,12 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/models/server_info/server_config.model.dart'; import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; import 'package:immich_mobile/models/server_info/server_features.model.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/models/server_info/server_version.model.dart'; import 'package:immich_mobile/services/server_info.service.dart'; +import 'package:immich_mobile/utils/semver.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -24,9 +25,7 @@ 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, - isNewReleaseAvailable: false, - versionMismatchErrorMessage: "", + versionStatus: VersionStatus.upToDate, ), ); @@ -43,73 +42,42 @@ 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(versionStatus: VersionStatus.error); return; } await _checkServerVersionMismatch(serverVersion); } catch (e, stackTrace) { _log.severe("Failed to get server version", e, stackTrace); - state = state.copyWith(isVersionMismatch: true); + state = state.copyWith(versionStatus: VersionStatus.error); return; } } - _checkServerVersionMismatch(ServerVersion serverVersion) async { - state = state.copyWith(serverVersion: serverVersion); + _checkServerVersionMismatch(ServerVersion serverVersion, {ServerVersion? latestVersion}) async { + state = state.copyWith(serverVersion: serverVersion, latestVersion: latestVersion); var packageInfo = await PackageInfo.fromPlatform(); + SemVer clientVersion = SemVer.fromString(packageInfo.version); - Map appVersion = _getDetailVersion(packageInfo.version); - - if (appVersion["major"]! > serverVersion.major) { - state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_server_out_of_date_major".tr(), - ); + if (serverVersion < clientVersion || (latestVersion != null && serverVersion < latestVersion)) { + state = state.copyWith(versionStatus: VersionStatus.serverOutOfDate); return; } - if (appVersion["major"]! < serverVersion.major) { - state = state.copyWith( - isVersionMismatch: true, - versionMismatchErrorMessage: "profile_drawer_client_out_of_date_major".tr(), - ); + if (clientVersion < serverVersion) { + state = state.copyWith(versionStatus: VersionStatus.clientOutOfDate); 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(versionStatus: VersionStatus.upToDate); } - handleNewRelease(ServerVersion serverVersion, ServerVersion latestVersion) { + handleReleaseInfo(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); + _checkServerVersionMismatch(serverVersion, latestVersion: latestVersion); } getServerFeatures() async { @@ -127,18 +95,15 @@ class ServerInfoNotifier extends StateNotifier { } state = state.copyWith(serverConfig: serverConfig); } - - Map _getDetailVersion(String version) { - List detail = version.split("."); - - var major = detail[0]; - var minor = detail[1]; - var patch = detail[2]; - - return {"major": int.parse(major), "minor": int.parse(minor), "patch": int.parse(patch.replaceAll("-DEBUG", ""))}; - } } final serverInfoProvider = StateNotifierProvider((ref) { return ServerInfoNotifier(ref.read(serverInfoServiceProvider)); }); + +final versionWarningPresentProvider = Provider.family((ref, user) { + final serverInfo = ref.watch(serverInfoProvider); + return serverInfo.versionStatus == VersionStatus.clientOutOfDate || + serverInfo.versionStatus == VersionStatus.error || + ((user?.isAdmin ?? false) && serverInfo.versionStatus == VersionStatus.serverOutOfDate); +}); diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index 136c6049a7..6a1083bfcc 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -15,10 +15,10 @@ import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/utils/debounce.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; import 'package:socket_io_client/socket_io_client.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; enum PendingAction { assetDelete, assetUploaded, assetHidden, assetTrash } @@ -307,7 +307,7 @@ class WebsocketNotifier extends StateNotifier { final serverVersion = ServerVersion.fromDto(serverVersionDto); final releaseVersion = ServerVersion.fromDto(releaseVersionDto); - _ref.read(serverInfoProvider.notifier).handleNewRelease(serverVersion, releaseVersion); + _ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion); } void _handleSyncAssetUploadReady(dynamic data) { diff --git a/mobile/lib/utils/semver.dart b/mobile/lib/utils/semver.dart new file mode 100644 index 0000000000..5df63618e4 --- /dev/null +++ b/mobile/lib/utils/semver.dart @@ -0,0 +1,59 @@ +class SemVer { + final int major; + final int minor; + final int patch; + + const SemVer({required this.major, required this.minor, required this.patch}); + + @override + String toString() { + return '$major.$minor.$patch'; + } + + SemVer copyWith({int? major, int? minor, int? patch}) { + return SemVer(major: major ?? this.major, minor: minor ?? this.minor, patch: patch ?? this.patch); + } + + factory SemVer.fromString(String version) { + final parts = version.split('.'); + return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2])); + } + + bool operator >(SemVer other) { + if (major != other.major) { + return major > other.major; + } + if (minor != other.minor) { + return minor > other.minor; + } + return patch > other.patch; + } + + bool operator <(SemVer other) { + if (major != other.major) { + return major < other.major; + } + if (minor != other.minor) { + return minor < other.minor; + } + return patch < other.patch; + } + + bool operator >=(SemVer other) { + return this > other || this == other; + } + + bool operator <=(SemVer other) { + return this < other || this == other; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SemVer && other.major == major && other.minor == minor && other.patch == patch; + } + + @override + int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode; +} 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..a83a3beee3 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 @@ -7,7 +7,9 @@ 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:immich_mobile/widgets/common/app_bar_dialog/server_update_notification.dart'; import 'package:package_info_plus/package_info_plus.dart'; class AppBarServerInfo extends HookConsumerWidget { @@ -17,6 +19,8 @@ class AppBarServerInfo extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.watch(localeProvider); ServerInfo serverInfoState = ref.watch(serverInfoProvider); + final user = ref.watch(currentUserProvider); + final bool showVersionWarning = ref.watch(versionWarningPresentProvider(user)); final appInfo = useState({}); const titleFontSize = 12.0; @@ -45,17 +49,10 @@ 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), - ), - ), - const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)), + if (showVersionWarning) ...[ + const Padding(padding: EdgeInsets.symmetric(horizontal: 8.0), child: ServerUpdateNotification()), + const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)), + ], Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -182,7 +179,7 @@ class AppBarServerInfo extends HookConsumerWidget { padding: const EdgeInsets.only(left: 10.0), child: Row( children: [ - if (serverInfoState.isNewReleaseAvailable) + if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate) const Padding( padding: EdgeInsets.only(right: 5.0), child: Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: 12), diff --git a/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart b/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart new file mode 100644 index 0000000000..6068ee022e --- /dev/null +++ b/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart @@ -0,0 +1,83 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/server_info/server_info.model.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class ServerUpdateNotification extends HookConsumerWidget { + const ServerUpdateNotification({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final serverInfoState = ref.watch(serverInfoProvider); + + Color errorColor = const Color.fromARGB(85, 253, 97, 83); + Color infoColor = context.isDarkTheme ? context.primaryColor.withAlpha(55) : context.primaryColor.withAlpha(25); + void openUpdateLink() { + String url; + if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate) { + url = kImmichLatestRelease; + } else { + if (Platform.isIOS) { + url = kImmichAppStoreLink; + } else if (Platform.isAndroid) { + url = kImmichPlayStoreLink; + } else { + // Fallback to latest release for other/unknown platforms + url = kImmichLatestRelease; + } + } + + launchUrlString(url, mode: LaunchMode.externalApplication); + } + + return SizedBox( + width: double.infinity, + child: Container( + decoration: BoxDecoration( + color: serverInfoState.versionStatus == VersionStatus.error ? errorColor : infoColor, + borderRadius: const BorderRadius.all(Radius.circular(8)), + border: Border.all( + color: serverInfoState.versionStatus == VersionStatus.error + ? errorColor + : context.primaryColor.withAlpha(50), + width: 0.75, + ), + ), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + serverInfoState.versionStatus.message, + textAlign: TextAlign.start, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: context.textTheme.labelLarge, + ), + if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate || + serverInfoState.versionStatus == VersionStatus.clientOutOfDate) ...[ + const Spacer(), + TextButton( + onPressed: openUpdateLink, + style: TextButton.styleFrom( + padding: const EdgeInsets.all(4), + minimumSize: const Size(0, 0), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: serverInfoState.versionStatus == VersionStatus.clientOutOfDate + ? Text("action_common_update".tr(context: context)) + : Text("view".tr()), + ), + ], + ], + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index 28b5c535d2..2bac100807 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -6,7 +6,6 @@ import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; -import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -28,8 +27,8 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { Widget build(BuildContext context, WidgetRef ref) { final BackUpState backupState = ref.watch(backupProvider); final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup; - final ServerInfo serverInfoState = ref.watch(serverInfoProvider); final user = ref.watch(currentUserProvider); + final bool versionWarningPresent = ref.watch(versionWarningPresentProvider(user)); final isDarkTheme = context.isDarkTheme; const widgetSize = 30.0; final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); @@ -46,8 +45,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { ), backgroundColor: Colors.transparent, alignment: Alignment.bottomRight, - isLabelVisible: - serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), + isLabelVisible: versionWarningPresent, 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..f68d5c9fda 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -118,8 +118,10 @@ class _ProfileIndicator extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final ServerInfo serverInfoState = ref.watch(serverInfoProvider); final user = ref.watch(currentUserProvider); + final bool versionWarningPresent = ref.watch(versionWarningPresentProvider(user)); + final serverInfoState = ref.watch(serverInfoProvider); + const widgetSize = 30.0; void toggleReadonlyMode() { @@ -143,13 +145,21 @@ class _ProfileIndicator extends ConsumerWidget { borderRadius: const BorderRadius.all(Radius.circular(12)), child: Badge( label: Container( - decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(widgetSize / 2)), - child: const Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: widgetSize / 2), + decoration: BoxDecoration( + color: context.isDarkTheme ? Colors.black : Colors.white, + borderRadius: BorderRadius.circular(widgetSize / 2), + ), + child: Icon( + Icons.info, + color: serverInfoState.versionStatus == VersionStatus.error + ? context.colorScheme.error + : context.primaryColor, + size: widgetSize / 2, + ), ), backgroundColor: Colors.transparent, alignment: Alignment.bottomRight, - isLabelVisible: - serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), + isLabelVisible: versionWarningPresent, offset: const Offset(-2, -12), child: user == null ? const Icon(Icons.face_outlined, size: widgetSize)