Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mechanism to show new release announcement to web and mobile app #209

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docker/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ services:
networks:
- immich-network


immich-web:
image: immich-web-dev:1.9.0
build:
Expand Down
7 changes: 7 additions & 0 deletions mobile/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />

<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
</manifest>
8 changes: 7 additions & 1 deletion mobile/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
Expand All @@ -76,5 +76,11 @@
<false />
<key>CADisableMinimumFrameDurationOnPhone</key>
<true />


<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
</dict>
</plist>
4 changes: 4 additions & 0 deletions mobile/lib/constants/hive_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ const String savedLoginInfoKey = "immichSavedLoginInfoKey";
// Backup Info
const String hiveBackupInfoBox = "immichBackupAlbumInfoBox";
const String backupInfoKey = "immichBackupAlbumInfoKey";

// Github Release Info
const String hiveGithubReleaseInfoBox = "immichGithubReleaseInfoBox";
const String githubReleaseInfoKey = "immichGithubReleaseInfoKey";
21 changes: 18 additions & 3 deletions mobile/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
import 'package:immich_mobile/modules/backup/providers/backup.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/websocket.provider.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
import 'constants/hive_box.dart';

void main() async {
Expand All @@ -24,6 +27,7 @@ void main() async {
await Hive.openBox(userInfoBox);
await Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox);
await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
await Hive.openBox(hiveGithubReleaseInfoBox);

SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
Expand All @@ -48,10 +52,18 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
case AppLifecycleState.resumed:
debugPrint("[APP STATE] resumed");
ref.watch(appStateProvider.notifier).state = AppStateEnum.resumed;
ref.watch(backupProvider.notifier).resumeBackup();

var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated;

if (isAuthenticated) {
ref.watch(backupProvider.notifier).resumeBackup();
ref.watch(assetProvider.notifier).getAllAsset();
ref.watch(serverInfoProvider.notifier).getServerVersion();
}

ref.watch(websocketProvider.notifier).connect();
ref.watch(assetProvider.notifier).getAllAsset();
ref.watch(serverInfoProvider.notifier).getServerVersion();

ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();

break;

Expand Down Expand Up @@ -95,6 +107,8 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv

@override
Widget build(BuildContext context) {
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();

return MaterialApp(
debugShowCheckedModeBanner: false,
home: Stack(
Expand All @@ -121,6 +135,7 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
),
const ImmichLoadingOverlay(),
const VersionAnnouncementOverlay(),
],
),
);
Expand Down
57 changes: 57 additions & 0 deletions mobile/lib/shared/providers/release_info.provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';

class ReleaseInfoNotifier extends StateNotifier<String> {
ReleaseInfoNotifier() : super("");

void checkGithubReleaseInfo() async {
var dio = Dio();
var box = Hive.box(hiveGithubReleaseInfoBox);

try {
String? localReleaseVersion = box.get(githubReleaseInfoKey);

Response res = await dio.get(
"https://api.github.com/repos/alextran1502/immich/releases/latest",
options: Options(
headers: {"Accept": "application/vnd.github.v3+json"},
),
);

if (res.statusCode == 200) {
String latestTagVersion = res.data["tag_name"];
state = latestTagVersion;

debugPrint("Local release version $localReleaseVersion");
debugPrint("Remote release veresion $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() {
var box = Hive.box(hiveGithubReleaseInfoBox);

box.put(githubReleaseInfoKey, state);
VersionAnnouncementOverlayController.appLoader.hide();
}
}

final releaseInfoProvider = StateNotifierProvider<ReleaseInfoNotifier, String>((ref) => ReleaseInfoNotifier());
5 changes: 0 additions & 5 deletions mobile/lib/shared/providers/server_info.provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ class ServerInfoNotifier extends StateNotifier<ServerInfoState> {

final ServerInfoService _serverInfoService = ServerInfoService();

getMapboxInfo() async {
MapboxInfo mapboxInfoRes = await _serverInfoService.getMapboxInfo();
state = state.copyWith(mapboxInfo: mapboxInfoRes);
}

getServerVersion() async {
ServerVersion? serverVersion = await _serverInfoService.getServerVersion();

Expand Down
18 changes: 10 additions & 8 deletions mobile/lib/shared/services/server_info.service.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/shared/models/mapbox_info.model.dart';
import 'package:immich_mobile/shared/models/server_version.model.dart';
import 'package:immich_mobile/shared/services/network.service.dart';
Expand All @@ -13,15 +14,16 @@ class ServerInfoService {
return ServerInfo.fromJson(response.toString());
}

Future<MapboxInfo> getMapboxInfo() async {
Response response = await _networkService.getRequest(url: 'server-info/mapbox');

return MapboxInfo.fromJson(response.toString());
}

Future<ServerVersion?> getServerVersion() async {
Response response = await _networkService.getRequest(url: 'server-info/version');
try {
Response response =
await _networkService.getRequest(url: 'server-info/version');

return ServerVersion.fromJson(response.toString());
} catch (e) {
debugPrint("Error getting server info");
}

return ServerVersion.fromJson(response.toString());
return null;
}
}
133 changes: 133 additions & 0 deletions mobile/lib/shared/views/version_announcement_overlay.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
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:url_launcher/url_launcher.dart';

class VersionAnnouncementOverlay extends HookConsumerWidget {
const VersionAnnouncementOverlay({
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context, WidgetRef ref) {
void goToReleaseNote() async {
final Uri _url = Uri.parse('https://github.com/alextran1502/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(
"New Server Version Available 🎉",
style: TextStyle(
fontSize: 16,
fontFamily: 'WorkSans',
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
),
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>[
const TextSpan(
text: 'Hi friend, there is a new release of',
),
const TextSpan(
text: ' Immich ',
style: TextStyle(
fontFamily: "SnowBurstOne",
color: Colors.indigo,
fontWeight: FontWeight.bold,
),
),
const TextSpan(
text: "please take your time to visit the ",
),
TextSpan(
text: "release note",
style: const TextStyle(
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()..onTap = goToReleaseNote,
),
const TextSpan(
text:
" and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
)
],
),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
visualDensity: VisualDensity.standard,
primary: Colors.indigo,
onPrimary: Colors.grey[50],
elevation: 2,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
),
onPressed: onAcknowledgeTapped,
child: const Text(
"Acknowledge",
style: TextStyle(
fontSize: 14,
),
)),
)
],
),
),
),
],
),
),
),
);
} else {
return Container();
}
},
);
}
}

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;
}
}
Loading