app_update_dialog.dart 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import 'package:flutter/material.dart';
  2. import 'package:logging/logging.dart';
  3. // import 'package:open_file/open_file.dart';
  4. import 'package:photos/core/configuration.dart';
  5. import 'package:photos/core/network/network.dart';
  6. import 'package:photos/ente_theme_data.dart';
  7. import 'package:photos/services/update_service.dart';
  8. import 'package:photos/theme/ente_theme.dart';
  9. import 'package:url_launcher/url_launcher_string.dart';
  10. class AppUpdateDialog extends StatefulWidget {
  11. final LatestVersionInfo? latestVersionInfo;
  12. const AppUpdateDialog(this.latestVersionInfo, {Key? key}) : super(key: key);
  13. @override
  14. State<AppUpdateDialog> createState() => _AppUpdateDialogState();
  15. }
  16. class _AppUpdateDialogState extends State<AppUpdateDialog> {
  17. @override
  18. Widget build(BuildContext context) {
  19. final List<Widget> changelog = [];
  20. final enteTextTheme = getEnteTextTheme(context);
  21. final enteColor = getEnteColorScheme(context);
  22. for (final log in widget.latestVersionInfo!.changelog) {
  23. changelog.add(
  24. Padding(
  25. padding: const EdgeInsets.fromLTRB(0, 4, 0, 4),
  26. child: Row(
  27. mainAxisAlignment: MainAxisAlignment.start,
  28. crossAxisAlignment: CrossAxisAlignment.start,
  29. children: [
  30. Text(
  31. "- ",
  32. style: enteTextTheme.small.copyWith(color: enteColor.textMuted),
  33. ),
  34. Flexible(
  35. child: Text(
  36. log,
  37. softWrap: true,
  38. style:
  39. enteTextTheme.small.copyWith(color: enteColor.textMuted),
  40. ),
  41. )
  42. ],
  43. ),
  44. ),
  45. );
  46. }
  47. final content = Column(
  48. crossAxisAlignment: CrossAxisAlignment.start,
  49. mainAxisSize: MainAxisSize.min,
  50. children: [
  51. Text(
  52. "A new version of ente is available.",
  53. style: enteTextTheme.body.copyWith(color: enteColor.textMuted),
  54. ),
  55. const Padding(padding: EdgeInsets.all(8)),
  56. Column(
  57. crossAxisAlignment: CrossAxisAlignment.start,
  58. children: changelog,
  59. ),
  60. const Padding(padding: EdgeInsets.all(8)),
  61. SizedBox(
  62. width: double.infinity,
  63. height: 56,
  64. child: OutlinedButton(
  65. style: Theme.of(context).outlinedButtonTheme.style!.copyWith(
  66. textStyle: MaterialStateProperty.resolveWith<TextStyle>(
  67. (Set<MaterialState> states) {
  68. return enteTextTheme.bodyBold;
  69. },
  70. ),
  71. ),
  72. onPressed: () async {
  73. Navigator.pop(context);
  74. showDialog(
  75. context: context,
  76. builder: (BuildContext context) {
  77. return ApkDownloaderDialog(widget.latestVersionInfo);
  78. },
  79. barrierDismissible: false,
  80. );
  81. },
  82. child: const Text(
  83. "Update",
  84. ),
  85. ),
  86. ),
  87. const Padding(padding: EdgeInsets.all(8)),
  88. Center(
  89. child: InkWell(
  90. child: Text(
  91. "Install manually",
  92. style: Theme.of(context)
  93. .textTheme
  94. .caption!
  95. .copyWith(decoration: TextDecoration.underline),
  96. ),
  97. onTap: () => launchUrlString(
  98. widget.latestVersionInfo!.url,
  99. mode: LaunchMode.externalApplication,
  100. ),
  101. ),
  102. )
  103. ],
  104. );
  105. final shouldForceUpdate =
  106. UpdateService.instance.shouldForceUpdate(widget.latestVersionInfo!);
  107. return WillPopScope(
  108. onWillPop: () async => !shouldForceUpdate,
  109. child: AlertDialog(
  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. ? "Critical update available"
  124. : "Update available",
  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: const Text(
  158. "Downloading...",
  159. style: 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: const Text("Sorry"),
  190. content: const Text("The download could not be completed"),
  191. actions: [
  192. TextButton(
  193. child: const Text(
  194. "Ignore",
  195. style: 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. "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. showDialog(
  226. context: context,
  227. builder: (BuildContext context) {
  228. return alert;
  229. },
  230. barrierColor: Colors.black87,
  231. );
  232. return;
  233. }
  234. }
  235. }