mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
Check for updates automatically
This commit is contained in:
parent
5e1d7be6a3
commit
083a8b5f6e
9 changed files with 199 additions and 9 deletions
|
@ -427,6 +427,9 @@ class PrefController {
|
||||||
.map(PrefHomeCollectionsNavButton.fromJson)
|
.map(PrefHomeCollectionsNavButton.fromJson)
|
||||||
.toList()) ??
|
.toList()) ??
|
||||||
_homeCollectionsNavBarButtonsDefault);
|
_homeCollectionsNavBarButtonsDefault);
|
||||||
|
@npSubjectAccessor
|
||||||
|
late final _isAutoUpdateCheckAvailableController =
|
||||||
|
BehaviorSubject.seeded(pref.isAutoUpdateCheckAvailableOr(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PrefControllerExtension on PrefController {
|
extension PrefControllerExtension on PrefController {
|
||||||
|
|
|
@ -266,6 +266,15 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
|
||||||
homeCollectionsNavBarButtons.distinct().skip(1);
|
homeCollectionsNavBarButtons.distinct().skip(1);
|
||||||
List<PrefHomeCollectionsNavButton> get homeCollectionsNavBarButtonsValue =>
|
List<PrefHomeCollectionsNavButton> get homeCollectionsNavBarButtonsValue =>
|
||||||
_homeCollectionsNavBarButtonsController.value;
|
_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 {
|
extension $SecurePrefControllerNpSubjectAccessor on SecurePrefController {
|
||||||
|
|
|
@ -120,6 +120,9 @@ enum PrefKey implements PrefKeyInterface {
|
||||||
homeCollectionsNavBarButtons,
|
homeCollectionsNavBarButtons,
|
||||||
lastDonationDialogTime,
|
lastDonationDialogTime,
|
||||||
shouldRemindDonationLater,
|
shouldRemindDonationLater,
|
||||||
|
lastAutoUpdateCheckTime,
|
||||||
|
isAutoUpdateCheckAvailable,
|
||||||
|
isEnableAutoUpdateCheck,
|
||||||
;
|
;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -224,6 +227,12 @@ enum PrefKey implements PrefKeyInterface {
|
||||||
return "lastDonationDialogTime";
|
return "lastDonationDialogTime";
|
||||||
case PrefKey.shouldRemindDonationLater:
|
case PrefKey.shouldRemindDonationLater:
|
||||||
return "shouldRemindDonationLater";
|
return "shouldRemindDonationLater";
|
||||||
|
case PrefKey.lastAutoUpdateCheckTime:
|
||||||
|
return "lastAutoUpdateCheckTime";
|
||||||
|
case PrefKey.isAutoUpdateCheckAvailable:
|
||||||
|
return "isAutoUpdateCheckAvailable";
|
||||||
|
case PrefKey.isEnableAutoUpdateCheck:
|
||||||
|
return "isEnableAutoUpdateCheck";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,6 +183,33 @@ extension PrefExtension on Pref {
|
||||||
PrefKey.shouldRemindDonationLater,
|
PrefKey.shouldRemindDonationLater,
|
||||||
value,
|
value,
|
||||||
(key, value) => provider.setBool(key, 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 {
|
extension AccountPrefExtension on AccountPref {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/object_extension.dart';
|
||||||
|
|
||||||
enum UpdateCheckerResult {
|
enum UpdateCheckerResult {
|
||||||
updateAvailable,
|
updateAvailable,
|
||||||
|
@ -51,3 +54,40 @@ class UpdateChecker {
|
||||||
|
|
||||||
static final _log = Logger("update_checker.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");
|
||||||
|
}
|
||||||
|
|
|
@ -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/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/controller/account_controller.dart';
|
import 'package:nc_photos/controller/account_controller.dart';
|
||||||
import 'package:nc_photos/controller/account_pref_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/stream_util.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/widget/account_picker_dialog.dart';
|
import 'package:nc_photos/widget/account_picker_dialog.dart';
|
||||||
|
@ -53,15 +54,48 @@ class HomeSliverAppBar extends StatelessWidget {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_ProfileIconView(
|
Stack(
|
||||||
account: account,
|
fit: StackFit.passthrough,
|
||||||
isProcessing: isShowProgressIcon,
|
children: [
|
||||||
onTap: () {
|
_ProfileIconView(
|
||||||
showDialog(
|
account: account,
|
||||||
context: context,
|
isProcessing: isShowProgressIcon,
|
||||||
builder: (_) => const AccountPickerDialog(),
|
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),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/controller/pref_controller.dart';
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
import 'package:nc_photos/debug_util.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/help_utils.dart' as help_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart' as language_util;
|
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;
|
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/features.dart' as features;
|
||||||
import 'package:nc_photos/platform/notification.dart';
|
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/stream_util.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
import 'package:nc_photos/widget/list_tile_center_leading.dart';
|
import 'package:nc_photos/widget/list_tile_center_leading.dart';
|
||||||
|
@ -48,6 +50,12 @@ class Settings extends StatefulWidget {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class _SettingsState extends State<Settings> {
|
class _SettingsState extends State<Settings> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_isEnableAutoUpdateCheck = Pref().isEnableAutoUpdateCheckOr();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final translator = L10n.global().translator;
|
final translator = L10n.global().translator;
|
||||||
|
@ -148,11 +156,34 @@ class _SettingsState extends State<Settings> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
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"),
|
title: const Text("Check for updates"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pushNamed(UpdateChecker.routeName);
|
Navigator.of(context).pushNamed(UpdateChecker.routeName);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: const Text("Check for updates automatically"),
|
||||||
|
value: _isEnableAutoUpdateCheck,
|
||||||
|
onChanged: _onEnableAutoUpdateCheckChanged,
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.global().settingsSourceCodeTitle),
|
title: Text(L10n.global().settingsSourceCodeTitle),
|
||||||
onTap: () {
|
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 _devSettingsUnlockCount = 3;
|
||||||
var _isShowDevSettings = false;
|
var _isShowDevSettings = false;
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,12 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc_util.dart';
|
import 'package:nc_photos/bloc_util.dart';
|
||||||
import 'package:nc_photos/controller/pref_controller.dart';
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
import 'package:nc_photos/db/entity_converter.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/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/android/activity.dart';
|
import 'package:nc_photos/mobile/android/activity.dart';
|
||||||
import 'package:nc_photos/mobile/android/permission_util.dart';
|
import 'package:nc_photos/mobile/android/permission_util.dart';
|
||||||
import 'package:nc_photos/protected_page_handler.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/v29.dart';
|
||||||
import 'package:nc_photos/use_case/compat/v46.dart';
|
import 'package:nc_photos/use_case/compat/v46.dart';
|
||||||
import 'package:nc_photos/use_case/compat/v55.dart';
|
import 'package:nc_photos/use_case/compat/v55.dart';
|
||||||
|
|
|
@ -28,6 +28,9 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
_initFirstRun(),
|
_initFirstRun(),
|
||||||
_migrateApp(emit),
|
_migrateApp(emit),
|
||||||
]);
|
]);
|
||||||
|
if (Pref().isEnableAutoUpdateCheckOr()) {
|
||||||
|
unawaited(const AutoUpdateChecker()());
|
||||||
|
}
|
||||||
emit(state.copyWith(isDone: true));
|
emit(state.copyWith(isDone: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +53,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
|
|
||||||
Future<void> _migrateApp(Emitter<_State> emit) async {
|
Future<void> _migrateApp(Emitter<_State> emit) async {
|
||||||
if (_shouldUpgrade()) {
|
if (_shouldUpgrade()) {
|
||||||
|
unawaited(Pref().setIsAutoUpdateCheckAvailable(false));
|
||||||
await _handleUpgrade(emit);
|
await _handleUpgrade(emit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue