app_update_dialog.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import 'package:flutter/material.dart';
  2. import 'package:logging/logging.dart';
  3. import 'package:photos/core/configuration.dart';
  4. import 'package:photos/core/network/network.dart';
  5. import 'package:photos/ente_theme_data.dart';
  6. import "package:photos/generated/l10n.dart";
  7. import 'package:photos/services/update_service.dart';
  8. import 'package:photos/theme/ente_theme.dart';
  9. import "package:photos/ui/components/buttons/button_widget.dart";
  10. import "package:photos/ui/components/models/button_type.dart";
  11. import 'package:url_launcher/url_launcher_string.dart';
  12. class AppUpdateDialog extends StatefulWidget {
  13. final LatestVersionInfo? latestVersionInfo;
  14. const AppUpdateDialog(this.latestVersionInfo, {Key? key}) : super(key: key);
  15. @override
  16. State<AppUpdateDialog> createState() => _AppUpdateDialogState();
  17. }
  18. class _AppUpdateDialogState extends State<AppUpdateDialog> {
  19. @override
  20. Widget build(BuildContext context) {
  21. final List<Widget> changelog = [];
  22. final enteTextTheme = getEnteTextTheme(context);
  23. final enteColor = getEnteColorScheme(context);
  24. for (final log in widget.latestVersionInfo!.changelog) {
  25. changelog.add(
  26. Padding(
  27. padding: const EdgeInsets.fromLTRB(0, 4, 0, 4),
  28. child: Row(
  29. mainAxisAlignment: MainAxisAlignment.start,
  30. crossAxisAlignment: CrossAxisAlignment.start,
  31. children: [
  32. Text(
  33. "- ",
  34. style: enteTextTheme.small.copyWith(color: enteColor.textMuted),
  35. ),
  36. Flexible(
  37. child: Text(
  38. log,
  39. softWrap: true,
  40. style:
  41. enteTextTheme.small.copyWith(color: enteColor.textMuted),
  42. ),
  43. ),
  44. ],
  45. ),
  46. ),
  47. );
  48. }
  49. final content = Column(
  50. crossAxisAlignment: CrossAxisAlignment.start,
  51. mainAxisSize: MainAxisSize.min,
  52. children: [
  53. Text(
  54. S.of(context).aNewVersionOfEnteIsAvailable,
  55. style: enteTextTheme.body.copyWith(color: enteColor.textMuted),
  56. ),
  57. const Padding(padding: EdgeInsets.all(8)),
  58. Column(
  59. crossAxisAlignment: CrossAxisAlignment.start,
  60. children: changelog,
  61. ),
  62. const Padding(padding: EdgeInsets.all(8)),
  63. ButtonWidget(
  64. buttonType: ButtonType.primary,
  65. labelText: S.of(context).update,
  66. onTap: () async {
  67. Navigator.pop(context);
  68. // ignore: unawaited_futures
  69. showDialog(
  70. context: context,
  71. builder: (BuildContext context) {
  72. return ApkDownloaderDialog(widget.latestVersionInfo);
  73. },
  74. barrierDismissible: false,
  75. );
  76. },
  77. ),
  78. const SizedBox(height: 6),
  79. ButtonWidget(
  80. buttonType: ButtonType.secondary,
  81. labelText: S.of(context).cancel,
  82. onTap: () async {
  83. Navigator.of(context).pop();
  84. },
  85. ),
  86. const Padding(padding: EdgeInsets.all(8)),
  87. Center(
  88. child: InkWell(
  89. child: Text(
  90. S.of(context).installManually,
  91. style: Theme.of(context)
  92. .textTheme
  93. .bodySmall!
  94. .copyWith(decoration: TextDecoration.underline),
  95. ),
  96. onTap: () => launchUrlString(
  97. widget.latestVersionInfo!.url,
  98. mode: LaunchMode.externalApplication,
  99. ),
  100. ),
  101. ),
  102. ],
  103. );
  104. final shouldForceUpdate =
  105. UpdateService.instance.shouldForceUpdate(widget.latestVersionInfo!);
  106. return WillPopScope(
  107. onWillPop: () async => !shouldForceUpdate,
  108. child: AlertDialog(
  109. key: const ValueKey("updateAppDialog"),
  110. title: Column(
  111. crossAxisAlignment: CrossAxisAlignment.start,
  112. children: [
  113. Icon(
  114. Icons.auto_awesome_outlined,
  115. size: 48,
  116. color: enteColor.strokeMuted,
  117. ),
  118. const SizedBox(
  119. height: 16,
  120. ),
  121. Text(
  122. shouldForceUpdate
  123. ? S.of(context).criticalUpdateAvailable
  124. : S.of(context).updateAvailable,
  125. style: enteTextTheme.h3Bold,
  126. ),
  127. ],
  128. ),
  129. content: content,
  130. ),
  131. );
  132. }
  133. }
  134. class ApkDownloaderDialog extends StatefulWidget {
  135. final LatestVersionInfo? versionInfo;
  136. const ApkDownloaderDialog(this.versionInfo, {Key? key}) : super(key: key);
  137. @override
  138. State<ApkDownloaderDialog> createState() => _ApkDownloaderDialogState();
  139. }
  140. class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
  141. String? _saveUrl;
  142. double? _downloadProgress;
  143. @override
  144. void initState() {
  145. super.initState();
  146. _saveUrl = Configuration.instance.getTempDirectory() +
  147. "ente-" +
  148. widget.versionInfo!.name +
  149. ".apk";
  150. _downloadApk();
  151. }
  152. @override
  153. Widget build(BuildContext context) {
  154. return WillPopScope(
  155. onWillPop: () async => false,
  156. child: AlertDialog(
  157. title: Text(
  158. S.of(context).downloading,
  159. style: const TextStyle(
  160. fontSize: 16,
  161. ),
  162. textAlign: TextAlign.center,
  163. ),
  164. content: LinearProgressIndicator(
  165. value: _downloadProgress,
  166. valueColor: AlwaysStoppedAnimation<Color>(
  167. Theme.of(context).colorScheme.greenAlternative,
  168. ),
  169. ),
  170. ),
  171. );
  172. }
  173. Future<void> _downloadApk() async {
  174. try {
  175. await NetworkClient.instance.getDio().download(
  176. widget.versionInfo!.url,
  177. _saveUrl,
  178. onReceiveProgress: (count, _) {
  179. setState(() {
  180. _downloadProgress = count / widget.versionInfo!.size;
  181. });
  182. },
  183. );
  184. Navigator.of(context, rootNavigator: true).pop('dialog');
  185. // OpenFile.open(_saveUrl);
  186. } catch (e) {
  187. Logger("ApkDownloader").severe(e);
  188. final AlertDialog alert = AlertDialog(
  189. title: Text(S.of(context).sorry),
  190. content: Text(S.of(context).theDownloadCouldNotBeCompleted),
  191. actions: [
  192. TextButton(
  193. child: Text(
  194. S.of(context).ignoreUpdate,
  195. style: const TextStyle(
  196. color: Colors.white,
  197. ),
  198. ),
  199. onPressed: () {
  200. Navigator.of(context, rootNavigator: true).pop('dialog');
  201. Navigator.of(context, rootNavigator: true).pop('dialog');
  202. },
  203. ),
  204. TextButton(
  205. child: Text(
  206. S.of(context).retry,
  207. style: TextStyle(
  208. color: Theme.of(context).colorScheme.greenAlternative,
  209. ),
  210. ),
  211. onPressed: () {
  212. Navigator.of(context, rootNavigator: true).pop('dialog');
  213. Navigator.of(context, rootNavigator: true).pop('dialog');
  214. showDialog(
  215. context: context,
  216. builder: (BuildContext context) {
  217. return ApkDownloaderDialog(widget.versionInfo);
  218. },
  219. barrierDismissible: false,
  220. );
  221. },
  222. ),
  223. ],
  224. );
  225. // ignore: unawaited_futures
  226. showDialog(
  227. context: context,
  228. builder: (BuildContext context) {
  229. return alert;
  230. },
  231. barrierColor: Colors.black87,
  232. );
  233. return;
  234. }
  235. }
  236. }