Add update all subscriptions

This commit is contained in:
problematicconsumer
2024-01-04 10:56:26 +03:30
parent 88108e0da5
commit 685d05c4d3
7 changed files with 122 additions and 43 deletions

View File

@@ -73,8 +73,11 @@
"update": { "update": {
"buttonTxt": "Update", "buttonTxt": "Update",
"tooltip": "Update Profile", "tooltip": "Update Profile",
"updateSubscriptions": "Update Subscriptions",
"failureMsg": "Failed to update profile", "failureMsg": "Failed to update profile",
"successMsg": "Profile updated successfully" "successMsg": "Profile updated successfully",
"namedFailureMsg": "Failed to update \"${name}\"",
"namedSuccessMsg": "\"${name}\" updated successfully"
}, },
"share": { "share": {
"buttonText": "Share", "buttonText": "Share",

View File

@@ -74,7 +74,10 @@
"buttonTxt": "بروزرسانی", "buttonTxt": "بروزرسانی",
"tooltip": "بروزرسانی پروفایل", "tooltip": "بروزرسانی پروفایل",
"failureMsg": "در بروزرسانی پروفایل خطایی رخ داد", "failureMsg": "در بروزرسانی پروفایل خطایی رخ داد",
"successMsg": "پروفایل با موفقیت بروزرسانی شد" "successMsg": "پروفایل با موفقیت بروزرسانی شد",
"namedFailureMsg": "در بروزرسانی \"${name}\" خطایی رخ داد",
"namedSuccessMsg": "\"${name}\" با موفقیت به روز شد",
"updateSubscriptions": "بروزرسانی اشتراک‌ها"
}, },
"share": { "share": {
"buttonText": "اشتراک گذاری", "buttonText": "اشتراک گذاری",

View File

@@ -74,7 +74,10 @@
"buttonTxt": "Обновить", "buttonTxt": "Обновить",
"tooltip": "Обновить профиль", "tooltip": "Обновить профиль",
"failureMsg": "Не удалось обновить профиль", "failureMsg": "Не удалось обновить профиль",
"successMsg": "Профиль успешно обновлён" "successMsg": "Профиль успешно обновлён",
"namedFailureMsg": "Не удалось обновить \"${name}\".",
"namedSuccessMsg": "\"${name}\" успешно обновлено",
"updateSubscriptions": "Обновить подписки"
}, },
"share": { "share": {
"buttonText": "Поделиться", "buttonText": "Поделиться",

View File

@@ -74,7 +74,10 @@
"buttonTxt": "Güncelle", "buttonTxt": "Güncelle",
"tooltip": "Profili Güncelle", "tooltip": "Profili Güncelle",
"failureMsg": "Profil güncellenemedi", "failureMsg": "Profil güncellenemedi",
"successMsg": "Profil başarıyla güncellendi" "successMsg": "Profil başarıyla güncellendi",
"namedFailureMsg": "\"${name}\" güncellenemedi",
"namedSuccessMsg": "\"${name}\" başarıyla güncellendi",
"updateSubscriptions": "Abonelikleri Güncelle"
}, },
"share": { "share": {
"buttonText": "Paylaş", "buttonText": "Paylaş",

View File

@@ -74,7 +74,10 @@
"buttonTxt": "更新", "buttonTxt": "更新",
"tooltip": "更新配置文件", "tooltip": "更新配置文件",
"failureMsg": "更新配置文件失败", "failureMsg": "更新配置文件失败",
"successMsg": "配置文件更新成功" "successMsg": "配置文件更新成功",
"namedFailureMsg": "无法更新\"${name}\"",
"namedSuccessMsg": "\"${name}\" 更新成功",
"updateSubscriptions": "更新订阅"
}, },
"share": { "share": {
"buttonText": "分享", "buttonText": "分享",

View File

@@ -10,6 +10,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'profiles_update_notifier.g.dart'; part 'profiles_update_notifier.g.dart';
typedef ProfileUpdateStatus = ({String name, bool success});
@Riverpod(keepAlive: true) @Riverpod(keepAlive: true)
class ForegroundProfilesUpdateNotifier class ForegroundProfilesUpdateNotifier
extends _$ForegroundProfilesUpdateNotifier with AppLogger { extends _$ForegroundProfilesUpdateNotifier with AppLogger {
@@ -17,9 +19,9 @@ class ForegroundProfilesUpdateNotifier
static const interval = Duration(minutes: 15); static const interval = Duration(minutes: 15);
@override @override
Future<void> build() async { Stream<ProfileUpdateStatus?> build() {
var cycleCount = 0; var cycleCount = 0;
final scheduler = NeatPeriodicTaskScheduler( _scheduler = NeatPeriodicTaskScheduler(
name: 'profiles update worker', name: 'profiles update worker',
interval: interval, interval: interval,
timeout: const Duration(minutes: 5), timeout: const Duration(minutes: 5),
@@ -30,30 +32,51 @@ class ForegroundProfilesUpdateNotifier
); );
ref.onDispose(() async { ref.onDispose(() async {
await scheduler.stop(); await _scheduler?.stop();
_scheduler = null;
}); });
if (ref.watch(introCompletedProvider)) { if (ref.watch(introCompletedProvider)) {
loggy.debug("intro done, starting"); loggy.debug("intro done, starting");
return scheduler.start(); _scheduler?.start();
} else { } else {
loggy.debug("intro in process, skipping"); loggy.debug("intro in process, skipping");
} }
return const Stream.empty();
}
NeatPeriodicTaskScheduler? _scheduler;
bool _forceNextRun = false;
Future<void> trigger() async {
loggy.debug("triggering update");
_forceNextRun = true;
await _scheduler?.trigger();
} }
@visibleForTesting @visibleForTesting
Future<void> updateProfiles() async { Future<void> updateProfiles() async {
var force = false;
if (_forceNextRun) {
force = true;
_forceNextRun = false;
}
try { try {
final previousRun = DateTime.tryParse( final previousRun = DateTime.tryParse(
ref.read(sharedPreferencesProvider).requireValue.getString(prefKey) ?? ref.read(sharedPreferencesProvider).requireValue.getString(prefKey) ??
"", "",
); );
if (previousRun != null && previousRun.add(interval) > DateTime.now()) { if (!force &&
previousRun != null &&
previousRun.add(interval) > DateTime.now()) {
loggy.debug("too soon! previous run: [$previousRun]"); loggy.debug("too soon! previous run: [$previousRun]");
return; return;
} }
loggy.debug("running, previous run: [$previousRun]"); loggy.debug(
"${force ? "[FORCED] " : ""}running, previous run: [$previousRun]",
);
final remoteProfiles = await ref final remoteProfiles = await ref
.read(profileRepositoryProvider) .read(profileRepositoryProvider)
@@ -69,20 +92,25 @@ class ForegroundProfilesUpdateNotifier
await for (final profile in Stream.fromIterable(remoteProfiles)) { await for (final profile in Stream.fromIterable(remoteProfiles)) {
final updateInterval = profile.options?.updateInterval; final updateInterval = profile.options?.updateInterval;
if (updateInterval != null && if (force ||
updateInterval <= DateTime.now().difference(profile.lastUpdate)) { updateInterval != null &&
updateInterval <=
DateTime.now().difference(profile.lastUpdate)) {
await ref await ref
.read(profileRepositoryProvider) .read(profileRepositoryProvider)
.requireValue .requireValue
.updateSubscription(profile) .updateSubscription(profile)
.mapLeft( .mapLeft(
(l) => loggy.debug("error updating profile [${profile.id}]", l), (l) {
) loggy.debug("error updating profile [${profile.id}]", l);
.map( state = AsyncData((name: profile.name, success: false));
(_) => },
loggy.debug("profile [${profile.id}] updated successfully"), ).map(
) (_) {
.run(); loggy.debug("profile [${profile.id}] updated successfully");
state = AsyncData((name: profile.name, success: true));
},
).run();
} else { } else {
loggy.debug( loggy.debug(
"skipping profile [${profile.id}] update. last successful update: [${profile.lastUpdate}] - interval: [${profile.options?.updateInterval}]", "skipping profile [${profile.id}] update. last successful update: [${profile.lastUpdate}] - interval: [${profile.options?.updateInterval}]",

View File

@@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/localization/translations.dart';
import 'package:hiddify/core/model/failures.dart'; import 'package:hiddify/core/model/failures.dart';
import 'package:hiddify/core/notification/in_app_notification_controller.dart';
import 'package:hiddify/core/router/router.dart'; import 'package:hiddify/core/router/router.dart';
import 'package:hiddify/features/profile/model/profile_sort_enum.dart'; import 'package:hiddify/features/profile/model/profile_sort_enum.dart';
import 'package:hiddify/features/profile/notifier/profiles_update_notifier.dart';
import 'package:hiddify/features/profile/overview/profiles_overview_notifier.dart'; import 'package:hiddify/features/profile/overview/profiles_overview_notifier.dart';
import 'package:hiddify/features/profile/widget/profile_tile.dart'; import 'package:hiddify/features/profile/widget/profile_tile.dart';
import 'package:hiddify/utils/placeholders.dart'; import 'package:hiddify/utils/placeholders.dart';
@@ -22,6 +24,25 @@ class ProfilesOverviewModal extends HookConsumerWidget {
final t = ref.watch(translationsProvider); final t = ref.watch(translationsProvider);
final asyncProfiles = ref.watch(profilesOverviewNotifierProvider); final asyncProfiles = ref.watch(profilesOverviewNotifierProvider);
ref.listen(
foregroundProfilesUpdateNotifierProvider,
(_, next) {
if (next case AsyncData(:final value?)) {
final t = ref.read(translationsProvider);
final notification = ref.read(inAppNotificationControllerProvider);
if (value.success) {
notification.showSuccessToast(
t.profile.update.namedSuccessMsg(name: value.name),
);
} else {
notification.showErrorToast(
t.profile.update.namedFailureMsg(name: value.name),
);
}
}
},
);
return Stack( return Stack(
children: [ children: [
CustomScrollView( CustomScrollView(
@@ -47,29 +68,44 @@ class ProfilesOverviewModal extends HookConsumerWidget {
Positioned.fill( Positioned.fill(
child: Align( child: Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: ButtonBar( child: Padding(
alignment: MainAxisAlignment.center, padding: const EdgeInsets.symmetric(vertical: 8),
children: [ child: Wrap(
FilledButton.icon( alignment: WrapAlignment.center,
onPressed: () { spacing: 8,
const AddProfileRoute().push(context); children: [
}, FilledButton.icon(
icon: const Icon(Icons.add), onPressed: () {
label: Text(t.profile.add.shortBtnTxt), const AddProfileRoute().push(context);
), },
FilledButton.icon( icon: const Icon(Icons.add),
onPressed: () { label: Text(t.profile.add.shortBtnTxt),
showDialog( ),
context: context, FilledButton.icon(
builder: (context) { onPressed: () {
return const ProfilesSortModal(); showDialog(
}, context: context,
); builder: (context) {
}, return const ProfilesSortModal();
icon: const Icon(Icons.sort), },
label: Text(t.general.sort), );
), },
], icon: const Icon(Icons.sort),
label: Text(t.general.sort),
),
FilledButton.icon(
onPressed: () async {
await ref
.read(
foregroundProfilesUpdateNotifierProvider.notifier,
)
.trigger();
},
icon: const Icon(Icons.update),
label: Text(t.profile.update.updateSubscriptions),
),
],
),
), ),
), ),
), ),