import 'dart:io'; import 'package:ente_auth/core/constants.dart'; import 'package:ente_auth/core/network.dart'; import 'package:ente_auth/services/notification_service.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher_string.dart'; class UpdateService { UpdateService._privateConstructor(); static final UpdateService instance = UpdateService._privateConstructor(); static const kUpdateAvailableShownTimeKey = "update_available_shown_time_key"; static const String flavor = String.fromEnvironment('app.flavor'); LatestVersionInfo? _latestVersion; final _logger = Logger("UpdateService"); late PackageInfo _packageInfo; late SharedPreferences _prefs; Future init() async { _packageInfo = await PackageInfo.fromPlatform(); _prefs = await SharedPreferences.getInstance(); } Future shouldUpdate() async { if (!isIndependent()) { return false; } try { _latestVersion = await _getLatestVersionInfo(); final currentVersionCode = int.parse(_packageInfo.buildNumber); return currentVersionCode < _latestVersion!.code!; } catch (e) { _logger.severe(e); return false; } } bool shouldForceUpdate(LatestVersionInfo? info) { if (!isIndependent()) { return false; } try { final currentVersionCode = int.parse(_packageInfo.buildNumber); return currentVersionCode < info!.lastSupportedVersionCode; } catch (e) { _logger.severe(e); return false; } } LatestVersionInfo? getLatestVersionInfo() { return _latestVersion; } Future showUpdateNotification() async { if (!isIndependent()) { return; } final shouldUpdate = await this.shouldUpdate(); final lastNotificationShownTime = _prefs.getInt(kUpdateAvailableShownTimeKey) ?? 0; final now = DateTime.now().microsecondsSinceEpoch; final hasBeen3DaysSinceLastNotification = (now - lastNotificationShownTime) > (3 * microSecondsInDay); if (shouldUpdate && hasBeen3DaysSinceLastNotification && _latestVersion!.shouldNotify!) { NotificationService.instance.showNotification( "Update available", "Click to install our best version yet", ); await _prefs.setInt(kUpdateAvailableShownTimeKey, now); } else { _logger.info("Debouncing notification"); } } Future _getLatestVersionInfo() async { final response = await Network.instance .getDio() .get("https://ente.io/release-info/auth-independent.json"); return LatestVersionInfo.fromMap(response.data["latestVersion"]); } // getRateDetails returns details about the place Tuple2 getRateDetails() { // Note: in auth, currently we don't have a way to identify if the // app was installed from play store, f-droid or github based on pkg name if (Platform.isAndroid) { if(flavor == "playstore") { return const Tuple2( "Play Store", "market://details??id=io.ente.auth", ); } return const Tuple2( "AlternativeTo", "https://alternativeto.net/software/ente-authenticator/about/", ); } return const Tuple2( "App Store", "https://apps.apple.com/in/app/ente-photos/id6444121398", ); } Future launchReviewUrl() async { final String url = getRateDetails().item2; try { await launchUrlString(url, mode: LaunchMode.externalApplication); } catch (e) { _logger.severe("Failed top open launch url $url", e); // Fall back if we fail to open play-store market app on android if (Platform.isAndroid && url.startsWith("market://")) { launchUrlString( "https://play.google.com/store/apps/details?id=io" ".ente.auth", mode: LaunchMode.externalApplication, ).ignore(); } } } bool isIndependent() { return flavor == "independent" || _packageInfo.packageName.endsWith("independent"); } } class LatestVersionInfo { final String? name; final int? code; final List changelog; final bool? shouldForceUpdate; final int lastSupportedVersionCode; final String? url; final int? size; final bool? shouldNotify; LatestVersionInfo( this.name, this.code, this.changelog, this.shouldForceUpdate, this.lastSupportedVersionCode, this.url, this.size, this.shouldNotify, ); factory LatestVersionInfo.fromMap(Map map) { return LatestVersionInfo( map['name'], map['code'], List.from(map['changelog']), map['shouldForceUpdate'], map['lastSupportedVersionCode'] ?? 1, map['url'], map['size'], map['shouldNotify'], ); } }