-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: integrate github api OTA 4linux
- Loading branch information
1 parent
d740c34
commit 5f239db
Showing
7 changed files
with
377 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import 'dart:convert'; | ||
import 'dart:io'; | ||
|
||
import 'package:process_run/shell.dart'; | ||
import 'package:version/version.dart'; | ||
|
||
import '../configs/constants.dart'; | ||
|
||
class OtaManager { | ||
static final shell = Shell(); | ||
|
||
// [tagname, releaseUrl, downloadUrl] | ||
static Future<List<String>> checkLatestOta() async { | ||
List<String> result = []; | ||
try { | ||
if (Platform.isLinux) { | ||
ProcessResult pr = (await shell.run('${Constants.githubApiRequest} ${Constants.githubApiReleases}'))[0]; | ||
if (pr.exitCode != 0) { | ||
return result; | ||
} | ||
Map<dynamic, dynamic> json = jsonDecode(pr.stdout.toString()); | ||
|
||
// fetch tagname & url | ||
if (!json.containsKey(Constants.githubApiFieldTagname)) { | ||
return result; | ||
} | ||
result.add(json[Constants.githubApiFieldTagname]); | ||
if (!json.containsKey(Constants.githubApiFieldHtmlUrl)) { | ||
return result; | ||
} | ||
result.add(json[Constants.githubApiFieldHtmlUrl]); | ||
|
||
// fetch download url | ||
if (!json.containsKey(Constants.githubApiFieldAssets)) { | ||
return result; | ||
} | ||
for (Map<dynamic, dynamic> asset in json[Constants.githubApiFieldAssets]) { | ||
if (asset.containsKey(Constants.githubApiFieldBrowserDownloadUrl) && asset[Constants.githubApiFieldBrowserDownloadUrl].toString().endsWith('.deb')) { | ||
result.add(asset[Constants.githubApiFieldBrowserDownloadUrl]); | ||
break; | ||
} | ||
} | ||
return result; | ||
} else { | ||
// ToDo Windows integration; | ||
return result; | ||
} | ||
} catch (e) { | ||
return result; | ||
} | ||
} | ||
|
||
static bool compareUpdateRequired(String tagname) { | ||
Version currentVersion = Version.parse(Constants.applicationVersion.split('-')[0]); | ||
Version latestVersion = Version.parse(tagname); | ||
return latestVersion > currentVersion; | ||
} | ||
|
||
static Future<bool> downloadOta(String tagname, String downloadUrl) async { | ||
bool result = true; | ||
try { | ||
if (Platform.isLinux) { | ||
List<ProcessResult> prs = (await shell.run(''' | ||
rm -rf ${Constants.packagesLinuxDownloadPath}/* | ||
mkdir -p ${Constants.packagesLinuxDownloadPath} | ||
curl -L -A "User-Agent Mozilla" $downloadUrl -o ${Constants.packagesLinuxDownloadPath}/$tagname.deb | ||
''')); | ||
for (ProcessResult pr in prs) { | ||
result = pr.exitCode == 0 && result; | ||
} | ||
return result; | ||
} else { | ||
// ToDo Windows integration; | ||
return false; | ||
} | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
|
||
static Future<bool> installOta(String tagname) async { | ||
bool result = true; | ||
try { | ||
if (Platform.isLinux) { | ||
ProcessResult pr = (await shell.run('pkexec bash -c "ss=0; apt install -y --allow-downgrades -f ${Constants.packagesLinuxDownloadPath}/$tagname.deb || ((ss++)); rm -rf ${Constants.packagesLinuxDownloadPath}/* || ((ss++)); exit \$ss"'))[0]; | ||
result = pr.exitCode == 0; | ||
return result; | ||
} else { | ||
// ToDo Windows integration; | ||
return false; | ||
} | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
import 'dart:async'; | ||
import 'dart:io'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:google_fonts/google_fonts.dart'; | ||
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||
import 'package:url_launcher/url_launcher.dart'; | ||
import '../classes/ota_manager.dart'; | ||
import '../configs/constants.dart'; | ||
|
||
enum OtaState { | ||
hidden, | ||
awaiting, | ||
downloading, | ||
installing, | ||
downloadFailed, | ||
installationFailed, | ||
installationSucceeded, | ||
} | ||
|
||
class MenuOta extends StatefulWidget { | ||
const MenuOta({super.key, this.paddingH = 0, this.paddingV = 0, this.backgroundColor = Colors.transparent}); | ||
|
||
final double paddingH; | ||
final double paddingV; | ||
final Color backgroundColor; | ||
|
||
@override | ||
State<MenuOta> createState() => MenuOtaState(); | ||
} | ||
|
||
class MenuOtaState extends State<MenuOta> { | ||
// assume running latest version by default | ||
OtaState _otaState = OtaState.hidden; | ||
// [tagname, releaseUrl, downloadUrl] | ||
List<String> _targetVersion = []; | ||
late Map<OtaState, String> otaStateTitles; | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
OtaManager.checkLatestOta().then((latestOta) => _handleOtaState(latestOta)); | ||
} | ||
|
||
void _handleOtaState(List<String> latestOta) { | ||
if (latestOta.length < 3) { | ||
return; | ||
} | ||
if (!OtaManager.compareUpdateRequired(latestOta[0])) { | ||
return; | ||
} | ||
if (_otaState == OtaState.hidden) { | ||
setState(() { | ||
_targetVersion = latestOta; | ||
_otaState = OtaState.awaiting; | ||
}); | ||
} | ||
} | ||
|
||
void _getOta() async { | ||
setState(() { | ||
_otaState = OtaState.downloading; | ||
}); | ||
bool downloaded = await OtaManager.downloadOta(_targetVersion[0], _targetVersion[2]); | ||
if (!downloaded) { | ||
setState(() { | ||
_otaState = OtaState.downloadFailed; | ||
}); | ||
return; | ||
} | ||
setState(() { | ||
_otaState = OtaState.installing; | ||
}); | ||
bool installed = await OtaManager.installOta(_targetVersion[0]); | ||
setState(() { | ||
if (installed) { | ||
_otaState = OtaState.installationSucceeded; | ||
} else { | ||
_otaState = OtaState.installationFailed; | ||
} | ||
}); | ||
} | ||
|
||
Widget _getProgressBar(var state, BuildContext context) { | ||
switch (state) { | ||
case OtaState.installing: | ||
case OtaState.downloading: | ||
return const LinearProgressIndicator(backgroundColor: Colors.transparent); | ||
case OtaState.installationFailed: | ||
case OtaState.downloadFailed: | ||
return LinearProgressIndicator(backgroundColor: Colors.transparent, color: Theme.of(context).colorScheme.error, value: 1,); | ||
case OtaState.installationSucceeded: | ||
return const LinearProgressIndicator(backgroundColor: Colors.transparent, color: Colors.green, value: 1,); | ||
default: | ||
return const LinearProgressIndicator(backgroundColor: Colors.transparent, color: Colors.transparent,); | ||
} | ||
} | ||
|
||
Widget _getIcon(var state, BuildContext context) { | ||
switch (state) { | ||
case OtaState.installationFailed: | ||
case OtaState.downloadFailed: | ||
return Icon(Icons.error_outline_rounded, color: Theme.of(context).colorScheme.error,); | ||
case OtaState.installationSucceeded: | ||
return const Icon(Icons.check_circle_outline_outlined, color: Colors.green,); | ||
default: | ||
return const Icon(Icons.browser_updated_rounded); | ||
} | ||
} | ||
|
||
Future<void> _showDownloadModal() { | ||
return showDialog<void>( | ||
context: context, | ||
builder: (BuildContext context) { | ||
return AlertDialog( | ||
title: Text(S.of(context)!.otaCardTitle), | ||
content: Column( | ||
mainAxisSize: MainAxisSize.min, | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Row( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
Text( | ||
"${S.of(context)!.otaAlertVersionCurrent}:\n${S.of(context)!.otaAlertVersionAvailable}:", | ||
style: Theme.of(context).textTheme.bodyMedium, | ||
), | ||
Text( | ||
" ${Constants.applicationVersion}\n ${_targetVersion[0]}", | ||
style: GoogleFonts.sourceCodePro().copyWith(color: Theme.of(context).textTheme.bodyMedium!.color!), | ||
), | ||
], | ||
), | ||
Text( | ||
"\n${S.of(context)!.otaAlertP1}", | ||
style: Theme.of(context).textTheme.bodyMedium, | ||
textAlign: TextAlign.justify, | ||
), | ||
Text( | ||
S.of(context)!.otaAlertP2, | ||
style: Theme.of(context).textTheme.bodyMedium, | ||
textAlign: TextAlign.justify, | ||
), | ||
], | ||
), | ||
actions: <Widget>[ | ||
TextButton.icon( | ||
style: TextButton.styleFrom( | ||
textStyle: Theme.of(context).textTheme.labelLarge, | ||
), | ||
icon: const Icon(Icons.link_rounded), | ||
label: Text(S.of(context)!.otaAlertButtonRelease), | ||
onPressed: () { | ||
launchUrl(Uri.parse(_targetVersion[1])); | ||
Navigator.of(context).pop(); | ||
}, | ||
), | ||
TextButton.icon( | ||
style: TextButton.styleFrom( | ||
textStyle: Theme.of(context).textTheme.labelLarge, | ||
), | ||
icon: const Icon(Icons.download_rounded), | ||
label: Text(S.of(context)!.otaAlertButtonInstall), | ||
onPressed: () { | ||
_getOta(); | ||
Navigator.of(context).pop(); | ||
}, | ||
), | ||
], | ||
); | ||
}, | ||
); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
otaStateTitles = { | ||
OtaState.hidden : "", | ||
OtaState.awaiting : S.of(context)!.otaCardSubtitleAwaiting, | ||
OtaState.downloading : S.of(context)!.otaCardSubtitleDownloading, | ||
OtaState.installing : S.of(context)!.otaCardSubtitleInstalling, | ||
OtaState.downloadFailed : S.of(context)!.otaCardSubtitleDownloadFailed, | ||
OtaState.installationFailed : S.of(context)!.otaCardSubtitleInstallationFailed, | ||
OtaState.installationSucceeded : S.of(context)!.otaCardSubtitleInstallationSucceeded, | ||
}; | ||
|
||
return AnimatedSwitcher( | ||
duration: const Duration(milliseconds: Constants.animationMs), | ||
child: _otaState != OtaState.hidden ? Card( | ||
key: const Key("otaAvailableTrue"), | ||
clipBehavior: Clip.antiAlias, | ||
color: Colors.amber.withOpacity(0.4), | ||
elevation: 0, | ||
margin: EdgeInsets.symmetric(vertical: widget.paddingV, horizontal: widget.paddingH), | ||
child: InkWell( | ||
onTap: () async { | ||
if (_otaState == OtaState.installing || _otaState == OtaState.downloading) { | ||
return; | ||
} | ||
if (_otaState == OtaState.installationSucceeded) { | ||
exit(0); | ||
} | ||
_showDownloadModal(); | ||
}, | ||
child: Column( | ||
children: [ | ||
Row( | ||
mainAxisAlignment: MainAxisAlignment.start, | ||
crossAxisAlignment: CrossAxisAlignment.center, | ||
children: [ | ||
Padding( | ||
padding: const EdgeInsets.only(left: 15, right: 15), | ||
child: _getIcon(_otaState, context), | ||
), | ||
Padding( | ||
padding: const EdgeInsets.symmetric(vertical: 15), | ||
child: Column( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Text( | ||
S.of(context)!.otaCardTitle, | ||
style: Theme.of(context).textTheme.titleMedium), | ||
const SizedBox(height: 5,), | ||
Text(otaStateTitles[_otaState].toString(), textAlign: TextAlign.justify,), | ||
], | ||
), | ||
), | ||
], | ||
), | ||
Align(alignment: Alignment.bottomCenter, child: _getProgressBar(_otaState, context),), | ||
], | ||
), | ||
), | ||
) : const SizedBox( | ||
key: Key("otaAvailableFalse"), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.