app_update_dialog.dart 5.7 KB

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