app_update_dialog.dart 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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.dart';
  6. import 'package:photos/ente_theme_data.dart';
  7. import 'package:photos/services/update_service.dart';
  8. class AppUpdateDialog extends StatefulWidget {
  9. final LatestVersionInfo latestVersionInfo;
  10. const AppUpdateDialog(this.latestVersionInfo, {Key key}) : super(key: key);
  11. @override
  12. State<AppUpdateDialog> createState() => _AppUpdateDialogState();
  13. }
  14. class _AppUpdateDialogState extends State<AppUpdateDialog> {
  15. @override
  16. Widget build(BuildContext context) {
  17. final List<Widget> changelog = [];
  18. for (final log in widget.latestVersionInfo.changelog) {
  19. changelog.add(
  20. Padding(
  21. padding: const EdgeInsets.fromLTRB(8, 4, 0, 4),
  22. child: Text("- " + log, style: Theme.of(context).textTheme.caption),
  23. ),
  24. );
  25. }
  26. final content = Column(
  27. crossAxisAlignment: CrossAxisAlignment.start,
  28. mainAxisSize: MainAxisSize.min,
  29. children: [
  30. Text(
  31. widget.latestVersionInfo.name,
  32. style: const TextStyle(
  33. fontSize: 20,
  34. fontWeight: FontWeight.bold,
  35. ),
  36. ),
  37. const Padding(padding: EdgeInsets.all(8)),
  38. const Text(
  39. "Changelog",
  40. style: TextStyle(
  41. fontSize: 18,
  42. ),
  43. ),
  44. const Padding(padding: EdgeInsets.all(4)),
  45. Column(
  46. crossAxisAlignment: CrossAxisAlignment.start,
  47. children: changelog,
  48. ),
  49. const Padding(padding: EdgeInsets.all(8)),
  50. SizedBox(
  51. width: double.infinity,
  52. height: 64,
  53. child: OutlinedButton(
  54. style: Theme.of(context).outlinedButtonTheme.style.copyWith(
  55. textStyle: MaterialStateProperty.resolveWith<TextStyle>(
  56. (Set<MaterialState> states) {
  57. return Theme.of(context).textTheme.subtitle1;
  58. },
  59. ),
  60. ),
  61. onPressed: () async {
  62. Navigator.pop(context);
  63. showDialog(
  64. context: context,
  65. builder: (BuildContext context) {
  66. return ApkDownloaderDialog(widget.latestVersionInfo);
  67. },
  68. barrierDismissible: false,
  69. );
  70. },
  71. child: const Text(
  72. "Update",
  73. ),
  74. ),
  75. ),
  76. ],
  77. );
  78. final shouldForceUpdate =
  79. UpdateService.instance.shouldForceUpdate(widget.latestVersionInfo);
  80. return WillPopScope(
  81. onWillPop: () async => !shouldForceUpdate,
  82. child: AlertDialog(
  83. title: Text(
  84. shouldForceUpdate ? "Critical update available" : "Update available",
  85. ),
  86. content: content,
  87. ),
  88. );
  89. }
  90. }
  91. class ApkDownloaderDialog extends StatefulWidget {
  92. final LatestVersionInfo versionInfo;
  93. const ApkDownloaderDialog(this.versionInfo, {Key key}) : super(key: key);
  94. @override
  95. State<ApkDownloaderDialog> createState() => _ApkDownloaderDialogState();
  96. }
  97. class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
  98. String _saveUrl;
  99. double _downloadProgress;
  100. @override
  101. void initState() {
  102. super.initState();
  103. _saveUrl = Configuration.instance.getTempDirectory() +
  104. "ente-" +
  105. widget.versionInfo.name +
  106. ".apk";
  107. _downloadApk();
  108. }
  109. @override
  110. Widget build(BuildContext context) {
  111. return WillPopScope(
  112. onWillPop: () async => false,
  113. child: AlertDialog(
  114. title: const Text(
  115. "Downloading...",
  116. style: TextStyle(
  117. fontSize: 16,
  118. ),
  119. textAlign: TextAlign.center,
  120. ),
  121. content: LinearProgressIndicator(
  122. value: _downloadProgress,
  123. valueColor: AlwaysStoppedAnimation<Color>(
  124. Theme.of(context).colorScheme.greenAlternative,
  125. ),
  126. ),
  127. ),
  128. );
  129. }
  130. Future<void> _downloadApk() async {
  131. try {
  132. await Network.instance.getDio().download(
  133. widget.versionInfo.url,
  134. _saveUrl,
  135. onReceiveProgress: (count, _) {
  136. setState(() {
  137. _downloadProgress = count / widget.versionInfo.size;
  138. });
  139. },
  140. );
  141. Navigator.of(context, rootNavigator: true).pop('dialog');
  142. OpenFile.open(_saveUrl);
  143. } catch (e) {
  144. Logger("ApkDownloader").severe(e);
  145. final AlertDialog alert = AlertDialog(
  146. title: const Text("Sorry"),
  147. content: const Text("The download could not be completed"),
  148. actions: [
  149. TextButton(
  150. child: const Text(
  151. "Ignore",
  152. style: TextStyle(
  153. color: Colors.white,
  154. ),
  155. ),
  156. onPressed: () {
  157. Navigator.of(context, rootNavigator: true).pop('dialog');
  158. Navigator.of(context, rootNavigator: true).pop('dialog');
  159. },
  160. ),
  161. TextButton(
  162. child: Text(
  163. "Retry",
  164. style: TextStyle(
  165. color: Theme.of(context).colorScheme.greenAlternative,
  166. ),
  167. ),
  168. onPressed: () {
  169. Navigator.of(context, rootNavigator: true).pop('dialog');
  170. Navigator.of(context, rootNavigator: true).pop('dialog');
  171. showDialog(
  172. context: context,
  173. builder: (BuildContext context) {
  174. return ApkDownloaderDialog(widget.versionInfo);
  175. },
  176. barrierDismissible: false,
  177. );
  178. },
  179. ),
  180. ],
  181. );
  182. showDialog(
  183. context: context,
  184. builder: (BuildContext context) {
  185. return alert;
  186. },
  187. barrierColor: Colors.black87,
  188. );
  189. return;
  190. }
  191. }
  192. }