Check for updates automatically

This commit is contained in:
Ming Ming 2022-06-01 13:55:54 +08:00
parent e17532eea1
commit 9dc1f76fd3
5 changed files with 217 additions and 19 deletions

View file

@ -283,6 +283,31 @@ class Pref {
value,
(key, value) => provider.setBool(key, value));
int? getLastAutoUpdateCheckTime() =>
provider.getInt(PrefKey.lastAutoUpdateCheckTime);
int getLastAutoUpdateCheckTimeOr(int def) =>
getLastAutoUpdateCheckTime() ?? def;
Future<bool> setLastAutoUpdateCheckTime(int value) =>
provider.setInt(PrefKey.lastAutoUpdateCheckTime, value);
bool? isAutoUpdateCheckAvailable() =>
provider.getBool(PrefKey.isAutoUpdateCheckAvailable);
bool isAutoUpdateCheckAvailableOr([bool def = false]) =>
isAutoUpdateCheckAvailable() ?? def;
Future<bool> setIsAutoUpdateCheckAvailable(bool value) => _set<bool>(
PrefKey.isAutoUpdateCheckAvailable,
value,
(key, value) => provider.setBool(key, value));
bool? isEnableAutoUpdateCheck() =>
provider.getBool(PrefKey.isEnableAutoUpdateCheck);
bool isEnableAutoUpdateCheckOr([bool def = true]) =>
isEnableAutoUpdateCheck() ?? def;
Future<bool> setIsEnableAutoUpdateCheck(bool value) => _set<bool>(
PrefKey.isEnableAutoUpdateCheck,
value,
(key, value) => provider.setBool(key, value));
Future<bool> _set<T>(PrefKey key, T value,
Future<bool> Function(PrefKey key, T value) setFn) async {
if (await setFn(key, value)) {
@ -599,6 +624,9 @@ enum PrefKey {
hasShownSaveEditResultDialog,
lastDonationDialogTime,
shouldRemindDonationLater,
lastAutoUpdateCheckTime,
isAutoUpdateCheckAvailable,
isEnableAutoUpdateCheck,
// account pref
isEnableFaceRecognitionApp,
@ -678,6 +706,12 @@ extension on PrefKey {
return "lastDonationDialogTime";
case PrefKey.shouldRemindDonationLater:
return "shouldRemindDonationLater";
case PrefKey.lastAutoUpdateCheckTime:
return "lastAutoUpdateCheckTime";
case PrefKey.isAutoUpdateCheckAvailable:
return "isAutoUpdateCheckAvailable";
case PrefKey.isEnableAutoUpdateCheck:
return "isEnableAutoUpdateCheck";
// account pref
case PrefKey.isEnableFaceRecognitionApp:

View file

@ -1,8 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/pref.dart';
enum UpdateCheckerResult {
updateAvailable,
@ -51,3 +54,40 @@ class UpdateChecker {
static final _log = Logger("update_checker.UpdateChecker");
}
class AutoUpdateChecker {
const AutoUpdateChecker();
Future<void> call() async {
try {
if (Pref().isAutoUpdateCheckAvailableOr()) {
return;
}
final prev = Pref().getLastAutoUpdateCheckTime()?.run(
(obj) => DateTime.fromMillisecondsSinceEpoch(obj).toUtc()) ??
DateTime(0);
final now = DateTime.now().toUtc();
if (now.isAfter(prev) && now.difference(prev) > const Duration(days: 7)) {
unawaited(
Pref().setLastAutoUpdateCheckTime(now.millisecondsSinceEpoch),
);
await _check();
}
} catch (e, stackTrace) {
_log.severe("[call] Exception", e, stackTrace);
}
}
Future<void> _check() async {
_log.info("[_check] Auto update check");
final checker = UpdateChecker();
final result = await checker();
if (result == UpdateCheckerResult.updateAvailable) {
_log.info("[_check] New update available");
unawaited(Pref().setIsAutoUpdateCheckAvailable(true));
}
}
static final _log = Logger("update_checker.AutoUpdateChecker");
}

View file

@ -12,7 +12,7 @@ import 'package:nc_photos/widget/account_picker_dialog.dart';
import 'package:nc_photos/widget/settings.dart';
/// AppBar for home screens
class HomeSliverAppBar extends StatelessWidget {
class HomeSliverAppBar extends StatefulWidget {
const HomeSliverAppBar({
Key? key,
required this.account,
@ -21,16 +21,43 @@ class HomeSliverAppBar extends StatelessWidget {
this.onSelectedMenuActions,
}) : super(key: key);
@override
createState() => _HomeSliverAppBarState();
final Account account;
/// Screen specific action buttons
final List<Widget>? actions;
/// Screen specific actions under the overflow menu. The value of each item
/// much >= 0
final List<PopupMenuEntry<int>>? menuActions;
final void Function(int)? onSelectedMenuActions;
}
class _HomeSliverAppBarState extends State<HomeSliverAppBar> {
@override
initState() {
super.initState();
_prefUpdatedListener.begin();
}
@override
dispose() {
_prefUpdatedListener.end();
super.dispose();
}
@override
build(BuildContext context) {
final accountLabel = AccountPref.of(account).getAccountLabel();
final accountLabel = AccountPref.of(widget.account).getAccountLabel();
return SliverAppBar(
title: InkWell(
onTap: () {
showDialog(
context: context,
builder: (_) => AccountPickerDialog(
account: account,
account: widget.account,
),
);
},
@ -40,7 +67,7 @@ class HomeSliverAppBar extends StatelessWidget {
children: [
Stack(
children: [
if (account.scheme == "http")
if (widget.account.scheme == "http")
const Icon(
Icons.no_encryption_outlined,
color: Colors.orange,
@ -58,12 +85,12 @@ class HomeSliverAppBar extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
accountLabel ?? account.address,
accountLabel ?? widget.account.address,
style: const TextStyle(fontSize: 16),
),
if (accountLabel == null)
Text(
account.username2,
widget.account.username2,
style: TextStyle(
fontSize: 14,
color: AppTheme.getSecondaryTextColor(context),
@ -78,7 +105,7 @@ class HomeSliverAppBar extends StatelessWidget {
),
floating: true,
automaticallyImplyLeading: false,
actions: (actions ?? []) +
actions: (widget.actions ?? []) +
[
if (!Pref().isFollowSystemThemeOr(false))
Switch(
@ -95,13 +122,50 @@ class HomeSliverAppBar extends StatelessWidget {
const AssetImage("assets/ic_dark_mode_switch_24dp.png"),
),
PopupMenuButton<int>(
icon: Pref().isAutoUpdateCheckAvailableOr()
? Stack(
fit: StackFit.passthrough,
children: [
Icon(Icons.adaptive.more),
Positioned.directional(
textDirection: Directionality.of(context),
end: 0,
top: 0,
child: const Icon(
Icons.circle,
color: Colors.red,
size: 8,
),
),
],
)
: null,
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
itemBuilder: (context) =>
(menuActions ?? []) +
(widget.menuActions ?? []) +
[
PopupMenuItem(
value: _menuValueAbout,
child: Text(L10n.global().settingsMenuLabel),
child: Stack(
fit: StackFit.passthrough,
children: [
Padding(
padding: const EdgeInsetsDirectional.only(end: 8),
child: Text(L10n.global().settingsMenuLabel),
),
if (Pref().isAutoUpdateCheckAvailableOr())
Positioned.directional(
textDirection: Directionality.of(context),
end: 0,
top: 0,
child: const Icon(
Icons.circle,
color: Colors.red,
size: 8,
),
),
],
),
),
PopupMenuItem(
value: _menuValueHelp,
@ -110,11 +174,11 @@ class HomeSliverAppBar extends StatelessWidget {
],
onSelected: (option) {
if (option >= 0) {
onSelectedMenuActions?.call(option);
widget.onSelectedMenuActions?.call(option);
} else {
if (option == _menuValueAbout) {
Navigator.of(context).pushNamed(Settings.routeName,
arguments: SettingsArguments(account));
arguments: SettingsArguments(widget.account));
} else if (option == _menuValueHelp) {
launch(help_utils.mainUrl);
}
@ -131,15 +195,16 @@ class HomeSliverAppBar extends StatelessWidget {
});
}
final Account account;
void _onPrefUpdated(PrefUpdatedEvent ev) {
if (ev.key == PrefKey.isAutoUpdateCheckAvailable) {
if (mounted) {
setState(() {});
}
}
}
/// Screen specific action buttons
final List<Widget>? actions;
/// Screen specific actions under the overflow menu. The value of each item
/// much >= 0
final List<PopupMenuEntry<int>>? menuActions;
final void Function(int)? onSelectedMenuActions;
late final _prefUpdatedListener =
AppEventListener<PrefUpdatedEvent>(_onPrefUpdated);
static const _menuValueAbout = -1;
static const _menuValueHelp = -2;

View file

@ -73,6 +73,7 @@ class _SettingsState extends State<Settings> {
super.initState();
_isEnableExif = Pref().isEnableExifOr();
_shouldProcessExifWifiOnly = Pref().shouldProcessExifWifiOnlyOr();
_isEnableAutoUpdateCheck = Pref().isEnableAutoUpdateCheckOr();
_prefUpdatedListener.begin();
}
@ -246,11 +247,34 @@ class _SettingsState extends State<Settings> {
},
),
ListTile(
trailing: Pref().isAutoUpdateCheckAvailableOr()
? Stack(
fit: StackFit.passthrough,
children: [
const Icon(Icons.upload),
Positioned.directional(
textDirection: Directionality.of(context),
end: 0,
top: 0,
child: const Icon(
Icons.circle,
color: Colors.red,
size: 8,
),
),
],
)
: null,
title: const Text("Check for updates"),
onTap: () {
Navigator.of(context).pushNamed(UpdateChecker.routeName);
},
),
SwitchListTile(
title: const Text("Check for updates automatically"),
value: _isEnableAutoUpdateCheck,
onChanged: _onEnableAutoUpdateCheckChanged,
),
ListTile(
title: Text(L10n.global().settingsSourceCodeTitle),
onTap: () {
@ -453,6 +477,35 @@ class _SettingsState extends State<Settings> {
}
}
Future<void> _onEnableAutoUpdateCheckChanged(bool value) async {
_log.info("[_onEnableAutoUpdateCheckChanged] New value: $value");
final oldValue = _isEnableAutoUpdateCheck;
setState(() {
_isEnableAutoUpdateCheck = value;
});
if (!await Pref().setIsEnableAutoUpdateCheck(value)) {
_log.severe("[_onEnableAutoUpdateCheckChanged] Failed writing pref");
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal,
));
setState(() {
_isEnableAutoUpdateCheck = oldValue;
});
} else {
if (!value) {
// reset state after disabling
if (mounted) {
setState(() {
Pref()
..setIsAutoUpdateCheckAvailable(false)
..setLastAutoUpdateCheckTime(0);
});
}
}
}
}
Future<void> _setExifSupport(bool value) async {
final oldValue = _isEnableExif;
setState(() {
@ -472,6 +525,7 @@ class _SettingsState extends State<Settings> {
late bool _isEnableExif;
late bool _shouldProcessExifWifiOnly;
late bool _isEnableAutoUpdateCheck;
var _devSettingsUnlockCount = 3;
var _isShowDevSettings = false;

View file

@ -10,6 +10,7 @@ import 'package:nc_photos/mobile/android/activity.dart';
import 'package:nc_photos/platform/k.dart' as platform_k;
import 'package:nc_photos/pref.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/update_checker.dart';
import 'package:nc_photos/use_case/compat/v29.dart';
import 'package:nc_photos/use_case/compat/v46.dart';
import 'package:nc_photos/widget/changelog.dart';
@ -45,12 +46,16 @@ class _SplashState extends State<Splash> {
setState(() {
_isUpgrading = true;
});
unawaited(Pref().setIsAutoUpdateCheckAvailable(false));
await _handleUpgrade();
setState(() {
_isUpgrading = false;
});
}
unawaited(_exit());
if (Pref().isEnableAutoUpdateCheckOr()) {
unawaited(const AutoUpdateChecker()());
}
}
@override