import 'dart:io'; import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:freezer/api/cache.dart'; import 'package:freezer/api/download.dart'; import 'package:freezer/ui/elements.dart'; import 'package:freezer/translations.i18n.dart'; import 'package:freezer/ui/error.dart'; import 'package:http/http.dart' as http; import 'package:open_file/open_file.dart'; import 'package:package_info/package_info.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; import 'dart:convert'; import 'package:version/version.dart'; class UpdaterScreen extends StatefulWidget { @override _UpdaterScreenState createState() => _UpdaterScreenState(); } class _UpdaterScreenState extends State { bool _loading = true; bool _error = false; late FreezerVersions _versions; String? _current; String? _arch; double _progress = 0.0; bool _buttonEnabled = true; Future _load() async { //Load current version PackageInfo info = await PackageInfo.fromPlatform(); setState(() => _current = info.version); //Get architecture _arch = await DownloadManager.platform.invokeMethod("arch"); if (_arch == 'armv8l') _arch = 'arm32'; //Load from website try { FreezerVersions versions = await FreezerVersions.fetch(); setState(() { _versions = versions; _loading = false; }); } catch (e, st) { print(e.toString() + st.toString()); _error = true; _loading = false; } } FreezerDownload? get _versionDownload { return _versions.versions![0].downloads!.firstWhereOrNull( (d) => d.version!.toLowerCase().contains(_arch!.toLowerCase())); } Future _download() async { String url = _versionDownload!.directUrl!; //Start request http.Client client = new http.Client(); http.StreamedResponse res = await client.send(http.Request('GET', Uri.parse(url))); int? size = res.contentLength; //Open file String path = p.join((await getExternalStorageDirectory())!.path, 'update.apk'); File file = File(path); IOSink fileSink = file.openWrite(); //Update progress Future.doWhile(() async { int received = await file.length(); setState(() => _progress = received / size!); return received != size; }); //Pipe await res.stream.pipe(fileSink); fileSink.close(); OpenFile.open(path); setState(() => _buttonEnabled = true); } @override void initState() { _load(); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: FreezerAppBar('Updates'.i18n), body: ListView( children: [ if (_error) ErrorScreen(), if (_loading) Padding( padding: EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [CircularProgressIndicator()], ), ), if (!_error && !_loading && Version.parse(_versions.latest) <= Version.parse(_current)) Center( child: Padding( padding: EdgeInsets.all(8.0), child: Text('You are running latest version!'.i18n, textAlign: TextAlign.center, style: TextStyle(fontSize: 26.0)), )), if (!_error && !_loading && Version.parse(_versions.latest) > Version.parse(_current)) Column( children: [ Padding( padding: EdgeInsets.all(8.0), child: Text( 'New update available!'.i18n + ' ' + _versions.latest!, textAlign: TextAlign.center, style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold), ), ), Text( 'Current version: ' + _current!, style: TextStyle(fontSize: 14.0, fontStyle: FontStyle.italic), ), Container(height: 8.0), FreezerDivider(), Container(height: 8.0), Text( 'Changelog', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold), ), Padding( padding: EdgeInsets.fromLTRB(16, 4, 16, 8), child: Text( _versions.versions![0].changelog!, style: TextStyle(fontSize: 16.0), ), ), FreezerDivider(), Container(height: 8.0), //Available download if (_versionDownload != null) Column(children: [ ElevatedButton( child: Text('Download'.i18n + ' (${_versionDownload!.version})'), onPressed: _buttonEnabled ? () { setState(() => _buttonEnabled = false); _download(); } : null), Padding( padding: EdgeInsets.all(8.0), child: LinearProgressIndicator(value: _progress), ) ]), //Unsupported arch if (_versionDownload == null) Text( 'Unsupported platform!'.i18n + ' $_arch', textAlign: TextAlign.center, style: TextStyle(fontSize: 16.0), ) ], ) ], )); } } class FreezerVersions { String? latest; List? versions; FreezerVersions({this.latest, this.versions}); factory FreezerVersions.fromJson(Map data) => FreezerVersions( latest: data['android']['latest'], versions: data['android']['versions'] .map((v) => FreezerVersion.fromJson(v)) .toList()); //Fetch from website API static Future fetch() async { http.Response response = await http.get(Uri.parse('https://freezer.life/api/versions')); // http.Response response = await http.get('https://cum.freezerapp.workers.dev/api/versions'); return FreezerVersions.fromJson(jsonDecode(response.body)); } static Future checkUpdate() async { //Check only each 24h int updateDelay = 86400000; if ((DateTime.now().millisecondsSinceEpoch - (cache.lastUpdateCheck ?? 0)) < updateDelay) return; cache.lastUpdateCheck = DateTime.now().millisecondsSinceEpoch; await cache.save(); FreezerVersions versions = await FreezerVersions.fetch(); //Load current version PackageInfo info = await PackageInfo.fromPlatform(); if (Version.parse(versions.latest) <= Version.parse(info.version)) return; //Get architecture String? _arch = await DownloadManager.platform.invokeMethod("arch"); if (_arch == 'armv8l') _arch = 'arm32'; //Check compatible architecture if (versions.versions![0].downloads!.firstWhereOrNull( (d) => d.version!.toLowerCase().contains(_arch!.toLowerCase())) == null) return; //Show notification FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('drawable/ic_logo'); final InitializationSettings initializationSettings = InitializationSettings(android: androidInitializationSettings); await flutterLocalNotificationsPlugin.initialize(initializationSettings); AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( 'freezerupdates', 'Freezer Updates'.i18n, 'Freezer Updates'.i18n); NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show(0, 'New update available!'.i18n, 'Update to latest version in the settings.'.i18n, notificationDetails); } } class FreezerVersion { String? version; String? changelog; List? downloads; FreezerVersion({this.version, this.changelog, this.downloads}); factory FreezerVersion.fromJson(Map data) => FreezerVersion( version: data['version'], changelog: data['changelog'], downloads: data['downloads'] .map((d) => FreezerDownload.fromJson(d)) .toList()); } class FreezerDownload { String? version; String? directUrl; FreezerDownload({this.version, this.directUrl}); factory FreezerDownload.fromJson(Map data) => FreezerDownload( version: data['version'], directUrl: data['links'].first['url']); }