mirror of https://github.com/immich-app/immich.git
feat: improved update messaging on app bar server info (#22938)
* feat: improved update messaging on app bar server info * chore: message improvements * chore: failed to fetch version error message * feat: open latest release when tapping "Update" on server out of date message * fix: text alignment states * chore: code review updates * Apply suggestion from @alextran1502 Co-authored-by: Alex <alex.tran1502@gmail.com> * Apply suggestion from @alextran1502 Co-authored-by: Alex <alex.tran1502@gmail.com> * chore: lots of rework of the version checking code to be cleaner Added a semver utility class to simplify comparisons, broke the update notification logic into own widget, reworked view construction and colors. * fix: show warnign without having to tap on app bar icon * chore: colors --------- Co-authored-by: Alex <alex.tran1502@gmail.com>pull/23110/head
parent
6f31f27218
commit
23a34bee6f
@ -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_);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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()),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue