This commit is contained in:
Brandon Wees 2025-10-17 16:12:47 +00:00 committed by GitHub
commit 73c167c155
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 112 additions and 56 deletions

View file

@ -474,6 +474,7 @@
"app_bar_signout_dialog_title": "Sign out", "app_bar_signout_dialog_title": "Sign out",
"app_download_links": "App Download Links", "app_download_links": "App Download Links",
"app_settings": "App Settings", "app_settings": "App Settings",
"app_update_available": "An app update is available",
"appears_in": "Appears in", "appears_in": "Appears in",
"apply_count": "Apply ({count, number})", "apply_count": "Apply ({count, number})",
"archive": "Archive", "archive": "Archive",
@ -705,7 +706,6 @@
"comments_and_likes": "Comments & likes", "comments_and_likes": "Comments & likes",
"comments_are_disabled": "Comments are disabled", "comments_are_disabled": "Comments are disabled",
"common_create_new_album": "Create new album", "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", "completed": "Completed",
"confirm": "Confirm", "confirm": "Confirm",
"confirm_admin_password": "Confirm Admin Password", "confirm_admin_password": "Confirm Admin Password",
@ -1555,13 +1555,9 @@
"privacy": "Privacy", "privacy": "Privacy",
"profile": "Profile", "profile": "Profile",
"profile_drawer_app_logs": "Logs", "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_client_server_up_to_date": "Client and Server are up-to-date",
"profile_drawer_github": "GitHub", "profile_drawer_github": "GitHub",
"profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", "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_image_of_user": "Profile image of {user}",
"profile_picture_set": "Profile picture set.", "profile_picture_set": "Profile picture set.",
"public_album": "Public album", "public_album": "Public album",
@ -1790,6 +1786,7 @@
"server_online": "Server Online", "server_online": "Server Online",
"server_privacy": "Server Privacy", "server_privacy": "Server Privacy",
"server_stats": "Server Stats", "server_stats": "Server Stats",
"server_update_available": "A server update is available",
"server_version": "Server Version", "server_version": "Server Version",
"set": "Set", "set": "Set",
"set_as_album_cover": "Set as album cover", "set_as_album_cover": "Set as album cover",
@ -2031,6 +2028,7 @@
"troubleshoot": "Troubleshoot", "troubleshoot": "Troubleshoot",
"type": "Type", "type": "Type",
"unable_to_change_pin_code": "Unable to change PIN code", "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", "unable_to_setup_pin_code": "Unable to setup PIN code",
"unarchive": "Unarchive", "unarchive": "Unarchive",
"unarchive_action_prompt": "{count} removed from Archive", "unarchive_action_prompt": "{count} removed from Archive",

View file

@ -49,3 +49,7 @@ const double kUploadStatusFailed = -1.0;
const double kUploadStatusCanceled = -2.0; const double kUploadStatusCanceled = -2.0;
const int kMinMonthsToEnableScrubberSnap = 12; 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";

View file

@ -9,9 +9,11 @@ class ServerInfo {
final ServerFeatures serverFeatures; final ServerFeatures serverFeatures;
final ServerConfig serverConfig; final ServerConfig serverConfig;
final ServerDiskInfo serverDiskInfo; final ServerDiskInfo serverDiskInfo;
final bool isVersionMismatch; final bool isClientOutOfDate;
final bool isServerOutOfDate;
final bool isNewReleaseAvailable; final bool isNewReleaseAvailable;
final String versionMismatchErrorMessage; final String versionMismatchErrorMessage;
final bool errorGettingVersions;
const ServerInfo({ const ServerInfo({
required this.serverVersion, required this.serverVersion,
@ -19,9 +21,11 @@ class ServerInfo {
required this.serverFeatures, required this.serverFeatures,
required this.serverConfig, required this.serverConfig,
required this.serverDiskInfo, required this.serverDiskInfo,
required this.isVersionMismatch, required this.isClientOutOfDate,
required this.isServerOutOfDate,
required this.isNewReleaseAvailable, required this.isNewReleaseAvailable,
required this.versionMismatchErrorMessage, required this.versionMismatchErrorMessage,
required this.errorGettingVersions,
}); });
ServerInfo copyWith({ ServerInfo copyWith({
@ -30,9 +34,11 @@ class ServerInfo {
ServerFeatures? serverFeatures, ServerFeatures? serverFeatures,
ServerConfig? serverConfig, ServerConfig? serverConfig,
ServerDiskInfo? serverDiskInfo, ServerDiskInfo? serverDiskInfo,
bool? isVersionMismatch, bool? isClientOutOfDate,
bool? isServerOutOfDate,
bool? isNewReleaseAvailable, bool? isNewReleaseAvailable,
String? versionMismatchErrorMessage, String? versionMismatchErrorMessage,
bool? errorGettingVersions,
}) { }) {
return ServerInfo( return ServerInfo(
serverVersion: serverVersion ?? this.serverVersion, serverVersion: serverVersion ?? this.serverVersion,
@ -40,15 +46,17 @@ class ServerInfo {
serverFeatures: serverFeatures ?? this.serverFeatures, serverFeatures: serverFeatures ?? this.serverFeatures,
serverConfig: serverConfig ?? this.serverConfig, serverConfig: serverConfig ?? this.serverConfig,
serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo, serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo,
isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch, isClientOutOfDate: isClientOutOfDate ?? this.isClientOutOfDate,
isServerOutOfDate: isServerOutOfDate ?? this.isServerOutOfDate,
isNewReleaseAvailable: isNewReleaseAvailable ?? this.isNewReleaseAvailable, isNewReleaseAvailable: isNewReleaseAvailable ?? this.isNewReleaseAvailable,
versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage, versionMismatchErrorMessage: versionMismatchErrorMessage ?? this.versionMismatchErrorMessage,
errorGettingVersions: errorGettingVersions ?? this.errorGettingVersions,
); );
} }
@override @override
String toString() { 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, errorGettingVersions: $errorGettingVersions)';
} }
@override @override
@ -61,9 +69,11 @@ class ServerInfo {
other.serverFeatures == serverFeatures && other.serverFeatures == serverFeatures &&
other.serverConfig == serverConfig && other.serverConfig == serverConfig &&
other.serverDiskInfo == serverDiskInfo && other.serverDiskInfo == serverDiskInfo &&
other.isVersionMismatch == isVersionMismatch && other.isClientOutOfDate == isClientOutOfDate &&
other.isServerOutOfDate == isServerOutOfDate &&
other.isNewReleaseAvailable == isNewReleaseAvailable && other.isNewReleaseAvailable == isNewReleaseAvailable &&
other.versionMismatchErrorMessage == versionMismatchErrorMessage; other.versionMismatchErrorMessage == versionMismatchErrorMessage &&
other.errorGettingVersions == errorGettingVersions;
} }
@override @override
@ -73,8 +83,10 @@ class ServerInfo {
serverFeatures.hashCode ^ serverFeatures.hashCode ^
serverConfig.hashCode ^ serverConfig.hashCode ^
serverDiskInfo.hashCode ^ serverDiskInfo.hashCode ^
isVersionMismatch.hashCode ^ isClientOutOfDate.hashCode ^
isServerOutOfDate.hashCode ^
isNewReleaseAvailable.hashCode ^ isNewReleaseAvailable.hashCode ^
versionMismatchErrorMessage.hashCode; versionMismatchErrorMessage.hashCode ^
errorGettingVersions.hashCode;
} }
} }

View file

@ -24,9 +24,11 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
), ),
serverDiskInfo: ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0), serverDiskInfo: ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0),
isVersionMismatch: false, isClientOutOfDate: false,
isServerOutOfDate: false,
isNewReleaseAvailable: false, isNewReleaseAvailable: false,
versionMismatchErrorMessage: "", versionMismatchErrorMessage: "",
errorGettingVersions: false,
), ),
); );
@ -43,15 +45,16 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
try { try {
final serverVersion = await _serverInfoService.getServerVersion(); final serverVersion = await _serverInfoService.getServerVersion();
// using isClientOutOfDate since that will show to users reguardless of if they are an admin
if (serverVersion == null) { if (serverVersion == null) {
state = state.copyWith(isVersionMismatch: true, versionMismatchErrorMessage: "common_server_error".tr()); state = state.copyWith(errorGettingVersions: true, versionMismatchErrorMessage: "unable_to_check_version".tr());
return; return;
} }
await _checkServerVersionMismatch(serverVersion); await _checkServerVersionMismatch(serverVersion);
} catch (e, stackTrace) { } catch (e, stackTrace) {
_log.severe("Failed to get server version", e, stackTrace); _log.severe("Failed to get server version", e, stackTrace);
state = state.copyWith(isVersionMismatch: true); state = state.copyWith(errorGettingVersions: true, versionMismatchErrorMessage: "unable_to_check_version".tr());
return; return;
} }
} }
@ -63,39 +66,17 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
Map<String, int> appVersion = _getDetailVersion(packageInfo.version); Map<String, int> appVersion = _getDetailVersion(packageInfo.version);
if (appVersion["major"]! > serverVersion.major) { if (appVersion["major"]! > serverVersion.major || appVersion["minor"]! > serverVersion.minor) {
state = state.copyWith( state = state.copyWith(isServerOutOfDate: true, versionMismatchErrorMessage: "server_update_available".tr());
isVersionMismatch: true,
versionMismatchErrorMessage: "profile_drawer_server_out_of_date_major".tr(),
);
return; return;
} }
if (appVersion["major"]! < serverVersion.major) { if (appVersion["major"]! < serverVersion.major || appVersion["minor"]! < serverVersion.minor) {
state = state.copyWith( state = state.copyWith(isClientOutOfDate: true, versionMismatchErrorMessage: "app_update_available".tr());
isVersionMismatch: true,
versionMismatchErrorMessage: "profile_drawer_client_out_of_date_major".tr(),
);
return; return;
} }
if (appVersion["minor"]! > serverVersion.minor) { state = state.copyWith(isClientOutOfDate: false, isServerOutOfDate: false, versionMismatchErrorMessage: "");
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: "");
} }
handleNewRelease(ServerVersion serverVersion, ServerVersion latestVersion) { handleNewRelease(ServerVersion serverVersion, ServerVersion latestVersion) {

View file

@ -1,14 +1,19 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/models/server_info/server_info.model.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/locale_provider.dart';
import 'package:immich_mobile/providers/server_info.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/utils/url_helper.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
class AppBarServerInfo extends HookConsumerWidget { class AppBarServerInfo extends HookConsumerWidget {
const AppBarServerInfo({super.key}); const AppBarServerInfo({super.key});
@ -18,16 +23,44 @@ class AppBarServerInfo extends HookConsumerWidget {
ref.watch(localeProvider); ref.watch(localeProvider);
ServerInfo serverInfoState = ref.watch(serverInfoProvider); ServerInfo serverInfoState = ref.watch(serverInfoProvider);
final user = ref.watch(currentUserProvider);
final appInfo = useState({}); final appInfo = useState({});
const titleFontSize = 12.0; const titleFontSize = 12.0;
const contentFontSize = 11.0; const contentFontSize = 11.0;
final showWarning =
serverInfoState.isClientOutOfDate ||
serverInfoState.errorGettingVersions ||
((user?.isAdmin ?? false) && serverInfoState.isServerOutOfDate);
getPackageInfo() async { getPackageInfo() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform(); PackageInfo packageInfo = await PackageInfo.fromPlatform();
appInfo.value = {"version": packageInfo.version, "buildNumber": packageInfo.buildNumber}; appInfo.value = {"version": packageInfo.version, "buildNumber": packageInfo.buildNumber};
} }
void openUpdateLink() {
if (serverInfoState.isServerOutOfDate) {
launchUrl(
Uri.parse("https://github.com/immich-app/immich/releases/latest"),
mode: LaunchMode.externalApplication,
);
return;
}
String url;
if (Platform.isIOS) {
url = kImmichAppStoreLink;
} else if (Platform.isAndroid) {
url = kImmichPlayStoreLink;
} else {
url = kImmichLatestRelease;
}
launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
}
useEffect(() { useEffect(() {
getPackageInfo(); getPackageInfo();
return null; return null;
@ -45,17 +78,45 @@ class AppBarServerInfo extends HookConsumerWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Padding( if (showWarning) ...[
padding: const EdgeInsets.all(8.0), 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.symmetric(horizontal: 12, vertical: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text( child: Text(
serverInfoState.isVersionMismatch serverInfoState.versionMismatchErrorMessage,
? serverInfoState.versionMismatchErrorMessage textAlign: (serverInfoState.isClientOutOfDate || serverInfoState.isServerOutOfDate)
: "profile_drawer_client_server_up_to_date".tr(), ? TextAlign.start
textAlign: TextAlign.center, : TextAlign.center,
style: TextStyle(fontSize: 11, color: context.primaryColor, fontWeight: FontWeight.w500), style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
if (serverInfoState.isClientOutOfDate || serverInfoState.isServerOutOfDate)
TextButton(
onPressed: openUpdateLink,
style: TextButton.styleFrom(
padding: const EdgeInsets.all(4),
minimumSize: const Size(0, 0),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: Text("action_common_update".tr(context: context)),
),
],
),
), ),
), ),
const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)), const Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(thickness: 1)),
],
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View file

@ -47,7 +47,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
isLabelVisible: isLabelVisible:
serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), serverInfoState.isClientOutOfDate || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable),
offset: const Offset(-2, -12), offset: const Offset(-2, -12),
child: user == null child: user == null
? const Icon(Icons.face_outlined, size: widgetSize) ? const Icon(Icons.face_outlined, size: widgetSize)

View file

@ -149,7 +149,7 @@ class _ProfileIndicator extends ConsumerWidget {
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
isLabelVisible: isLabelVisible:
serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable), serverInfoState.isClientOutOfDate || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable),
offset: const Offset(-2, -12), offset: const Offset(-2, -12),
child: user == null child: user == null
? const Icon(Icons.face_outlined, size: widgetSize) ? const Icon(Icons.face_outlined, size: widgetSize)