1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

feat(mobile): remove announcement overlay and show in app bar dialog (#5104)

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2023-11-17 21:46:18 +00:00 committed by GitHub
parent 63a745c7ad
commit 4daf2478aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 159 additions and 239 deletions

View File

@ -292,6 +292,7 @@
"server_info_box_app_version": "App Version",
"server_info_box_server_url": "Server URL",
"server_info_box_server_version": "Server Version",
"server_info_box_latest_release":"Latest Version",
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
"setting_image_viewer_original_title": "Load original image",

View File

@ -26,11 +26,9 @@ import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
import 'package:immich_mobile/shared/services/local_notification.service.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
import 'package:immich_mobile/utils/migration.dart';
@ -196,7 +194,6 @@ class ImmichAppState extends ConsumerState<ImmichApp>
@override
Widget build(BuildContext context) {
var router = ref.watch(appRouterProvider);
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
return MaterialApp(
localizationsDelegates: context.localizationDelegates,
@ -217,7 +214,6 @@ class ImmichAppState extends ConsumerState<ImmichApp>
),
),
const ImmichLoadingOverlay(),
const VersionAnnouncementOverlay(),
],
),
);

View File

@ -5,43 +5,52 @@ import 'package:immich_mobile/shared/models/server_info/server_version.model.dar
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;
ServerInfo({
required this.serverVersion,
required this.latestVersion,
required this.serverFeatures,
required this.serverConfig,
required this.isVersionMismatch,
required this.serverDiskInfo,
required this.isVersionMismatch,
required this.isNewReleaseAvailable,
required this.versionMismatchErrorMessage,
});
ServerInfo copyWith({
ServerVersion? serverVersion,
ServerVersion? latestVersion,
ServerFeatures? serverFeatures,
ServerConfig? serverConfig,
ServerDiskInfo? serverDiskInfo,
bool? isVersionMismatch,
bool? isNewReleaseAvailable,
String? versionMismatchErrorMessage,
}) {
return ServerInfo(
serverVersion: serverVersion ?? this.serverVersion,
latestVersion: latestVersion ?? this.latestVersion,
serverFeatures: serverFeatures ?? this.serverFeatures,
serverConfig: serverConfig ?? this.serverConfig,
serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo,
isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch,
isNewReleaseAvailable:
isNewReleaseAvailable ?? this.isNewReleaseAvailable,
versionMismatchErrorMessage:
versionMismatchErrorMessage ?? this.versionMismatchErrorMessage,
serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo,
);
}
@override
String toString() {
return 'ServerInfo(serverVersion: $serverVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, isVersionMismatch: $isVersionMismatch, versionMismatchErrorMessage: $versionMismatchErrorMessage, serverDiskInfo: $serverDiskInfo)';
return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, isVersionMismatch: $isVersionMismatch, isNewReleaseAvailable: $isNewReleaseAvailable, versionMismatchErrorMessage: $versionMismatchErrorMessage)';
}
@override
@ -50,20 +59,24 @@ class ServerInfo {
return other is ServerInfo &&
other.serverVersion == serverVersion &&
other.latestVersion == latestVersion &&
other.serverFeatures == serverFeatures &&
other.serverConfig == serverConfig &&
other.serverDiskInfo == serverDiskInfo &&
other.isVersionMismatch == isVersionMismatch &&
other.isNewReleaseAvailable == isNewReleaseAvailable &&
other.versionMismatchErrorMessage == versionMismatchErrorMessage;
}
@override
int get hashCode {
return serverVersion.hashCode ^
latestVersion.hashCode ^
serverFeatures.hashCode ^
serverConfig.hashCode ^
serverDiskInfo.hashCode ^
isVersionMismatch.hashCode ^
versionMismatchErrorMessage.hashCode ^
serverDiskInfo.hashCode;
isNewReleaseAvailable.hashCode ^
versionMismatchErrorMessage.hashCode;
}
}

View File

@ -153,7 +153,6 @@ enum StoreKey<T> {
backupRequireWifi<bool>(6, type: bool),
backupRequireCharging<bool>(7, type: bool),
backupTriggerDelay<int>(8, type: int),
githubReleaseInfo<String>(9, type: String),
serverUrl<String>(10, type: String),
accessToken<String>(11, type: String),
serverEndpoint<String>(12, type: String),

View File

@ -11,7 +11,6 @@ import 'package:immich_mobile/modules/memories/providers/memory.provider.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/providers/tab.provider.dart';
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
@ -70,8 +69,6 @@ class AppStateNotiifer extends StateNotifier<AppStateEnum> {
_ref.read(websocketProvider.notifier).connect();
_ref.read(releaseInfoProvider.notifier).checkGithubReleaseInfo();
_ref
.read(notificationPermissionProvider.notifier)
.getNotificationPermission();

View File

@ -1,57 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
import 'package:logging/logging.dart';
class ReleaseInfoNotifier extends StateNotifier<String> {
ReleaseInfoNotifier() : super("");
final log = Logger('ReleaseInfoNotifier');
void checkGithubReleaseInfo() async {
final Client client = Client();
try {
final String? localReleaseVersion =
Store.tryGet(StoreKey.githubReleaseInfo);
final res = await client.get(
Uri.parse(
"https://api.github.com/repos/immich-app/immich/releases/latest",
),
headers: {"Accept": "application/vnd.github.v3+json"},
);
if (res.statusCode == 200) {
final data = jsonDecode(res.body);
String latestTagVersion = data["tag_name"];
state = latestTagVersion;
if (localReleaseVersion == null && latestTagVersion.isNotEmpty) {
VersionAnnouncementOverlayController.appLoader.show();
return;
}
if (latestTagVersion.isNotEmpty &&
localReleaseVersion != latestTagVersion) {
VersionAnnouncementOverlayController.appLoader.show();
return;
}
}
} catch (e) {
debugPrint("Error gettting latest release version");
state = "";
}
}
void acknowledgeNewVersion() {
Store.put(StoreKey.githubReleaseInfo, state);
VersionAnnouncementOverlayController.appLoader.hide();
}
}
final releaseInfoProvider = StateNotifierProvider<ReleaseInfoNotifier, String>(
(ref) => ReleaseInfoNotifier(),
);

View File

@ -18,6 +18,11 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
minor: 0,
patch: 0,
),
latestVersion: const ServerVersion(
major: 0,
minor: 0,
patch: 0,
),
serverFeatures: const ServerFeatures(
map: true,
trash: true,
@ -32,6 +37,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
diskUsagePercentage: 0,
),
isVersionMismatch: false,
isNewReleaseAvailable: false,
versionMismatchErrorMessage: "",
),
);
@ -55,6 +61,10 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
return;
}
await _checkServerVersionMismatch(serverVersion);
}
_checkServerVersionMismatch(ServerVersion serverVersion) async {
state = state.copyWith(serverVersion: serverVersion);
var packageInfo = await PackageInfo.fromPlatform();
@ -67,7 +77,15 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
versionMismatchErrorMessage:
"Server is out of date. Please update to the latest major version.",
);
return;
}
if (appVersion["major"]! < serverVersion.major) {
state = state.copyWith(
isVersionMismatch: true,
versionMismatchErrorMessage:
"Mobile App is out of date. Please update to the latest major version.",
);
return;
}
@ -75,9 +93,17 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
state = state.copyWith(
isVersionMismatch: true,
versionMismatchErrorMessage:
"Server is out of date. Consider updating to the latest minor version.",
"Server is out of date. Please update to the latest minor version.",
);
return;
}
if (appVersion["minor"]! < serverVersion.minor) {
state = state.copyWith(
isVersionMismatch: true,
versionMismatchErrorMessage:
"Mobile App is out of date. Please update to the latest minor version.",
);
return;
}
@ -87,6 +113,25 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
);
}
handleNewRelease(
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,
);
}
getServerFeatures() async {
final serverFeatures = await _serverInfoService.getServerFeatures();
if (serverFeatures == null) {
@ -120,5 +165,5 @@ class ServerInfoNotifier extends StateNotifier<ServerInfo> {
final serverInfoProvider =
StateNotifierProvider<ServerInfoNotifier, ServerInfo>((ref) {
return ServerInfoNotifier(ref.watch(serverInfoServiceProvider));
return ServerInfoNotifier(ref.read(serverInfoServiceProvider));
});

View File

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/server_info/server_version.model.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
@ -130,6 +131,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
socket.on('on_asset_trash', _handleServerUpdates);
socket.on('on_asset_restore', _handleServerUpdates);
socket.on('on_asset_update', _handleServerUpdates);
socket.on('on_new_release', _handleReleaseUpdates);
} catch (e) {
debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
}
@ -204,6 +206,36 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
addPendingChange(PendingAction.assetDelete, data);
_debounce(handlePendingChanges);
}
_handleReleaseUpdates(dynamic data) {
// Json guard
if (data is! Map) {
return;
}
final json = data.cast<String, dynamic>();
final serverVersionJson =
json.containsKey('serverVersion') ? json['serverVersion'] : null;
final releaseVersionJson =
json.containsKey('releaseVersion') ? json['releaseVersion'] : null;
if (serverVersionJson == null || releaseVersionJson == null) {
return;
}
final serverVersionDto =
ServerVersionResponseDto.fromJson(serverVersionJson);
final releaseVersionDto =
ServerVersionResponseDto.fromJson(releaseVersionJson);
if (serverVersionDto == null || releaseVersionDto == null) {
return;
}
final serverVersion = ServerVersion.fromDto(serverVersionDto);
final releaseVersion = ServerVersion.fromDto(releaseVersionDto);
_ref
.read(serverInfoProvider.notifier)
.handleNewRelease(serverVersion, releaseVersion);
}
}
final websocketProvider =

View File

@ -137,7 +137,7 @@ class AppBarServerInfo extends HookConsumerWidget {
child: Text(
serverInfoState.serverVersion.major > 0
? "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}"
: "?",
: "--",
style: TextStyle(
fontSize: 11,
color: context.textTheme.labelSmall?.color
@ -207,6 +207,61 @@ class AppBarServerInfo extends HookConsumerWidget {
),
],
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Divider(
color: Color.fromARGB(101, 201, 201, 201),
thickness: 1,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Row(
children: [
if (serverInfoState.isNewReleaseAvailable)
const Padding(
padding: EdgeInsets.only(right: 5.0),
child: Icon(
Icons.info,
color: Color.fromARGB(255, 243, 188, 106),
size: 12,
),
),
Text(
"server_info_box_latest_release".tr(),
style: TextStyle(
fontSize: 11,
color: context.textTheme.labelSmall?.color,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
Expanded(
flex: 0,
child: Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Text(
serverInfoState.latestVersion.major > 0
? "${serverInfoState.latestVersion.major}.${serverInfoState.latestVersion.minor}.${serverInfoState.latestVersion.patch}"
: "--",
style: TextStyle(
fontSize: 11,
color: context.textTheme.labelSmall?.color
?.withOpacity(0.5),
fontWeight: FontWeight.bold,
),
),
),
),
],
),
],
),
),

View File

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

View File

@ -1,160 +0,0 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
import 'package:immich_mobile/shared/providers/admin_provider.dart';
import 'package:url_launcher/url_launcher.dart';
class VersionAnnouncementOverlay extends HookConsumerWidget {
const VersionAnnouncementOverlay({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final bool isAdmin = ref.watch(isAdminProvider);
if (!isAdmin) {
return const SizedBox.shrink(); // Don't show anything for non-admins
}
void goToReleaseNote() async {
final Uri url =
Uri.parse('https://github.com/immich-app/immich/releases/latest');
await launchUrl(url);
}
void onAcknowledgeTapped() {
ref.watch(releaseInfoProvider.notifier).acknowledgeNewVersion();
}
return ValueListenableBuilder<bool>(
valueListenable:
VersionAnnouncementOverlayController.appLoader.loaderShowingNotifier,
builder: (context, shouldShow, child) {
if (shouldShow) {
return Scaffold(
backgroundColor: Colors.black38,
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 307),
child: Wrap(
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"version_announcement_overlay_title",
style: TextStyle(
fontSize: 16,
fontFamily: 'WorkSans',
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
).tr(),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 14,
fontFamily: 'WorkSans',
color: Colors.black87,
height: 1.2,
),
children: <TextSpan>[
TextSpan(
text:
'version_announcement_overlay_text_1'
.tr(),
),
const TextSpan(
text: ' Immich ',
style: TextStyle(
fontFamily: "SnowBurstOne",
color: Colors.indigo,
fontWeight: FontWeight.bold,
),
),
TextSpan(
text:
"version_announcement_overlay_text_2"
.tr(),
),
TextSpan(
text:
"version_announcement_overlay_release_notes"
.tr(),
style: const TextStyle(
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = goToReleaseNote,
),
TextSpan(
text:
"version_announcement_overlay_text_3"
.tr(),
),
],
),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
visualDensity: VisualDensity.standard,
backgroundColor: Colors.indigo,
foregroundColor: Colors.grey[50],
elevation: 2,
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 25,
),
),
onPressed: onAcknowledgeTapped,
child: const Text(
"version_announcement_overlay_ack",
style: TextStyle(
fontSize: 14,
),
).tr(),
),
),
],
),
),
),
],
),
),
),
);
} else {
return const SizedBox();
}
},
);
}
}
class VersionAnnouncementOverlayController {
static final VersionAnnouncementOverlayController appLoader =
VersionAnnouncementOverlayController();
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void show() {
loaderShowingNotifier.value = true;
}
void hide() {
loaderShowingNotifier.value = false;
}
}

View File

@ -346,7 +346,7 @@ packages:
source: hosted
version: "0.0.2"
executor_lib:
dependency: "direct main"
dependency: transitive
description:
name: executor_lib
sha256: "544889daa5726462657dab6410b75f2f8e3a77479d85b307a25c346e243bc38e"
@ -1562,7 +1562,7 @@ packages:
source: hosted
version: "1.0.0"
vector_tile_renderer:
dependency: "direct main"
dependency: transitive
description:
name: vector_tile_renderer
sha256: de212da0f5e48107d3b763a940a428eb1f49d8a4664d41ac0b654f77209a2d0b

View File

@ -32,9 +32,6 @@ dependencies:
git:
url: https://github.com/shenlong-tanwen/flutter-vector-map-tiles.git
ref: immich_above_4
# Adding the following as direct dependency since flutter cannot detect them as transitive dep
vector_tile_renderer: ^4.0.0
executor_lib: 1.1.1
flutter_udid: ^2.0.0
package_info_plus: ^4.1.0
url_launcher: ^6.1.3