Check for updates automatically

This commit is contained in:
Ming Ming 2022-06-01 13:55:54 +08:00
parent 5e1d7be6a3
commit 083a8b5f6e
9 changed files with 199 additions and 9 deletions

View file

@ -427,6 +427,9 @@ class PrefController {
.map(PrefHomeCollectionsNavButton.fromJson)
.toList()) ??
_homeCollectionsNavBarButtonsDefault);
@npSubjectAccessor
late final _isAutoUpdateCheckAvailableController =
BehaviorSubject.seeded(pref.isAutoUpdateCheckAvailableOr(false));
}
extension PrefControllerExtension on PrefController {

View file

@ -266,6 +266,15 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
homeCollectionsNavBarButtons.distinct().skip(1);
List<PrefHomeCollectionsNavButton> get homeCollectionsNavBarButtonsValue =>
_homeCollectionsNavBarButtonsController.value;
// _isAutoUpdateCheckAvailableController
ValueStream<bool> get isAutoUpdateCheckAvailable =>
_isAutoUpdateCheckAvailableController.stream;
Stream<bool> get isAutoUpdateCheckAvailableNew =>
isAutoUpdateCheckAvailable.skip(1);
Stream<bool> get isAutoUpdateCheckAvailableChange =>
isAutoUpdateCheckAvailable.distinct().skip(1);
bool get isAutoUpdateCheckAvailableValue =>
_isAutoUpdateCheckAvailableController.value;
}
extension $SecurePrefControllerNpSubjectAccessor on SecurePrefController {

View file

@ -120,6 +120,9 @@ enum PrefKey implements PrefKeyInterface {
homeCollectionsNavBarButtons,
lastDonationDialogTime,
shouldRemindDonationLater,
lastAutoUpdateCheckTime,
isAutoUpdateCheckAvailable,
isEnableAutoUpdateCheck,
;
@override
@ -224,6 +227,12 @@ enum PrefKey implements PrefKeyInterface {
return "lastDonationDialogTime";
case PrefKey.shouldRemindDonationLater:
return "shouldRemindDonationLater";
case PrefKey.lastAutoUpdateCheckTime:
return "lastAutoUpdateCheckTime";
case PrefKey.isAutoUpdateCheckAvailable:
return "isAutoUpdateCheckAvailable";
case PrefKey.isEnableAutoUpdateCheck:
return "isEnableAutoUpdateCheck";
}
}
}

View file

@ -183,6 +183,33 @@ extension PrefExtension on Pref {
PrefKey.shouldRemindDonationLater,
value,
(key, value) => provider.setBool(key, value));
int? getLastAutoUpdateCheckTime() =>
provider.getInt(PrefKey.lastAutoUpdateCheckTime);
int getLastAutoUpdateCheckTimeOr(int def) =>
getLastAutoUpdateCheckTime() ?? def;
Future<bool> setLastAutoUpdateCheckTime(int value) => _set<int>(
PrefKey.lastAutoUpdateCheckTime,
value,
(key, value) => provider.setInt(key, 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));
}
extension AccountPrefExtension on AccountPref {

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/entity/pref.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/object_extension.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

@ -5,6 +5,7 @@ import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/controller/account_controller.dart';
import 'package:nc_photos/controller/account_pref_controller.dart';
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/stream_util.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/widget/account_picker_dialog.dart';
@ -53,15 +54,48 @@ class HomeSliverAppBar extends StatelessWidget {
}
},
),
_ProfileIconView(
account: account,
isProcessing: isShowProgressIcon,
onTap: () {
showDialog(
context: context,
builder: (_) => const AccountPickerDialog(),
);
},
Stack(
fit: StackFit.passthrough,
children: [
_ProfileIconView(
account: account,
isProcessing: isShowProgressIcon,
onTap: () {
showDialog(
context: context,
builder: (_) => const AccountPickerDialog(),
);
},
),
ValueStreamBuilder(
stream: context.read<PrefController>().isAutoUpdateCheckAvailable,
builder: (context, snapshot) {
final isAutoUpdateCheckAvailable = snapshot.requireData;
if (isAutoUpdateCheckAvailable) {
return Positioned.directional(
textDirection: Directionality.of(context),
end: 0,
top: 0,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.red,
),
child: const Icon(
Icons.upload,
color: Colors.white,
size: 12,
),
),
);
} else {
return const SizedBox.shrink();
}
},
),
],
),
const SizedBox(width: 8),
],

View file

@ -6,6 +6,7 @@ import 'package:logging/logging.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/debug_util.dart';
import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/help_utils.dart' as help_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/language_util.dart' as language_util;
@ -13,6 +14,7 @@ import 'package:nc_photos/mobile/platform.dart'
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
import 'package:nc_photos/platform/features.dart' as features;
import 'package:nc_photos/platform/notification.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/stream_util.dart';
import 'package:nc_photos/url_launcher_util.dart';
import 'package:nc_photos/widget/list_tile_center_leading.dart';
@ -48,6 +50,12 @@ class Settings extends StatefulWidget {
@npLog
class _SettingsState extends State<Settings> {
@override
void initState() {
super.initState();
_isEnableAutoUpdateCheck = Pref().isEnableAutoUpdateCheckOr();
}
@override
Widget build(BuildContext context) {
final translator = L10n.global().translator;
@ -148,11 +156,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: () {
@ -237,6 +268,37 @@ 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);
});
}
}
}
}
late bool _isEnableAutoUpdateCheck;
var _devSettingsUnlockCount = 3;
var _isShowDevSettings = false;

View file

@ -9,10 +9,12 @@ import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc_util.dart';
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/db/entity_converter.dart';
import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/activity.dart';
import 'package:nc_photos/mobile/android/permission_util.dart';
import 'package:nc_photos/protected_page_handler.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/use_case/compat/v55.dart';

View file

@ -28,6 +28,9 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
_initFirstRun(),
_migrateApp(emit),
]);
if (Pref().isEnableAutoUpdateCheckOr()) {
unawaited(const AutoUpdateChecker()());
}
emit(state.copyWith(isDone: true));
}
@ -50,6 +53,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _migrateApp(Emitter<_State> emit) async {
if (_shouldUpgrade()) {
unawaited(Pref().setIsAutoUpdateCheckAvailable(false));
await _handleUpgrade(emit);
}
}