mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-13 18:58:53 +01:00
Check for updates automatically
This commit is contained in:
parent
e17532eea1
commit
9dc1f76fd3
5 changed files with 217 additions and 19 deletions
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue