diff --git a/mobile/lib/providers/server_info.provider.dart b/mobile/lib/providers/server_info.provider.dart index 7a424c332d..9619ba86a1 100644 --- a/mobile/lib/providers/server_info.provider.dart +++ b/mobile/lib/providers/server_info.provider.dart @@ -67,7 +67,7 @@ class ServerInfoNotifier extends StateNotifier { return; } - if (clientVersion < serverVersion) { + if (clientVersion < serverVersion && clientVersion.differenceType(serverVersion) != SemVerType.patch) { state = state.copyWith(versionStatus: VersionStatus.clientOutOfDate); return; } diff --git a/mobile/lib/utils/semver.dart b/mobile/lib/utils/semver.dart index 0eb6726b65..aebfd2fe4c 100644 --- a/mobile/lib/utils/semver.dart +++ b/mobile/lib/utils/semver.dart @@ -1,3 +1,5 @@ +enum SemVerType { major, minor, patch } + class SemVer { final int major; final int minor; @@ -15,8 +17,20 @@ class SemVer { } factory SemVer.fromString(String version) { + if (version.toLowerCase().startsWith("v")) { + version = version.substring(1); + } + final parts = version.split("-")[0].split('.'); - return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2])); + if (parts.length != 3) { + throw FormatException('Invalid semantic version string: $version'); + } + + try { + return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2])); + } catch (e) { + throw FormatException('Invalid semantic version string: $version'); + } } bool operator >(SemVer other) { @@ -54,6 +68,20 @@ class SemVer { return other is SemVer && other.major == major && other.minor == minor && other.patch == patch; } + SemVerType? differenceType(SemVer other) { + if (major != other.major) { + return SemVerType.major; + } + if (minor != other.minor) { + return SemVerType.minor; + } + if (patch != other.patch) { + return SemVerType.patch; + } + + return null; + } + @override int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode; } diff --git a/mobile/test/utils/semver_test.dart b/mobile/test/utils/semver_test.dart new file mode 100644 index 0000000000..8f1958a879 --- /dev/null +++ b/mobile/test/utils/semver_test.dart @@ -0,0 +1,92 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/utils/semver.dart'; + +void main() { + group('SemVer', () { + test('Parses valid semantic version strings correctly', () { + final version = SemVer.fromString('1.2.3'); + expect(version.major, 1); + expect(version.minor, 2); + expect(version.patch, 3); + }); + + test('Throws FormatException for invalid version strings', () { + expect(() => SemVer.fromString('1.2'), throwsFormatException); + expect(() => SemVer.fromString('a.b.c'), throwsFormatException); + expect(() => SemVer.fromString('1.2.3.4'), throwsFormatException); + }); + + test('Compares equal versons correctly', () { + final v1 = SemVer.fromString('1.2.3'); + final v2 = SemVer.fromString('1.2.3'); + expect(v1 == v2, isTrue); + expect(v1 > v2, isFalse); + expect(v1 < v2, isFalse); + }); + + test('Compares major version correctly', () { + final v1 = SemVer.fromString('2.0.0'); + final v2 = SemVer.fromString('1.9.9'); + expect(v1 == v2, isFalse); + expect(v1 > v2, isTrue); + expect(v1 < v2, isFalse); + }); + + test('Compares minor version correctly', () { + final v1 = SemVer.fromString('1.3.0'); + final v2 = SemVer.fromString('1.2.9'); + expect(v1 == v2, isFalse); + expect(v1 > v2, isTrue); + expect(v1 < v2, isFalse); + }); + + test('Compares patch version correctly', () { + final v1 = SemVer.fromString('1.2.4'); + final v2 = SemVer.fromString('1.2.3'); + expect(v1 == v2, isFalse); + expect(v1 > v2, isTrue); + expect(v1 < v2, isFalse); + }); + + test('Gives correct major difference type', () { + final v1 = SemVer.fromString('2.0.0'); + final v2 = SemVer.fromString('1.9.9'); + expect(v1.differenceType(v2), SemVerType.major); + }); + + test('Gives correct minor difference type', () { + final v1 = SemVer.fromString('1.3.0'); + final v2 = SemVer.fromString('1.2.9'); + expect(v1.differenceType(v2), SemVerType.minor); + }); + + test('Gives correct patch difference type', () { + final v1 = SemVer.fromString('1.2.4'); + final v2 = SemVer.fromString('1.2.3'); + expect(v1.differenceType(v2), SemVerType.patch); + }); + + test('Gives null difference type for equal versions', () { + final v1 = SemVer.fromString('1.2.3'); + final v2 = SemVer.fromString('1.2.3'); + expect(v1.differenceType(v2), isNull); + }); + + test('toString returns correct format', () { + final version = SemVer.fromString('1.2.3'); + expect(version.toString(), '1.2.3'); + }); + + test('Parses versions with leading v correctly', () { + final version1 = SemVer.fromString('v1.2.3'); + expect(version1.major, 1); + expect(version1.minor, 2); + expect(version1.patch, 3); + + final version2 = SemVer.fromString('V1.2.3'); + expect(version2.major, 1); + expect(version2.minor, 2); + expect(version2.patch, 3); + }); + }); +}