Add update all subscriptions
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -74,7 +74,10 @@
|
|||||||
"buttonTxt": "بروزرسانی",
|
"buttonTxt": "بروزرسانی",
|
||||||
"tooltip": "بروزرسانی پروفایل",
|
"tooltip": "بروزرسانی پروفایل",
|
||||||
"failureMsg": "در بروزرسانی پروفایل خطایی رخ داد",
|
"failureMsg": "در بروزرسانی پروفایل خطایی رخ داد",
|
||||||
"successMsg": "پروفایل با موفقیت بروزرسانی شد"
|
"successMsg": "پروفایل با موفقیت بروزرسانی شد",
|
||||||
|
"namedFailureMsg": "در بروزرسانی \"${name}\" خطایی رخ داد",
|
||||||
|
"namedSuccessMsg": "\"${name}\" با موفقیت به روز شد",
|
||||||
|
"updateSubscriptions": "بروزرسانی اشتراکها"
|
||||||
},
|
},
|
||||||
"share": {
|
"share": {
|
||||||
"buttonText": "اشتراک گذاری",
|
"buttonText": "اشتراک گذاری",
|
||||||
|
|||||||
@@ -74,7 +74,10 @@
|
|||||||
"buttonTxt": "Обновить",
|
"buttonTxt": "Обновить",
|
||||||
"tooltip": "Обновить профиль",
|
"tooltip": "Обновить профиль",
|
||||||
"failureMsg": "Не удалось обновить профиль",
|
"failureMsg": "Не удалось обновить профиль",
|
||||||
"successMsg": "Профиль успешно обновлён"
|
"successMsg": "Профиль успешно обновлён",
|
||||||
|
"namedFailureMsg": "Не удалось обновить \"${name}\".",
|
||||||
|
"namedSuccessMsg": "\"${name}\" успешно обновлено",
|
||||||
|
"updateSubscriptions": "Обновить подписки"
|
||||||
},
|
},
|
||||||
"share": {
|
"share": {
|
||||||
"buttonText": "Поделиться",
|
"buttonText": "Поделиться",
|
||||||
|
|||||||
@@ -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ş",
|
||||||
|
|||||||
@@ -74,7 +74,10 @@
|
|||||||
"buttonTxt": "更新",
|
"buttonTxt": "更新",
|
||||||
"tooltip": "更新配置文件",
|
"tooltip": "更新配置文件",
|
||||||
"failureMsg": "更新配置文件失败",
|
"failureMsg": "更新配置文件失败",
|
||||||
"successMsg": "配置文件更新成功"
|
"successMsg": "配置文件更新成功",
|
||||||
|
"namedFailureMsg": "无法更新\"${name}\"",
|
||||||
|
"namedSuccessMsg": "\"${name}\" 更新成功",
|
||||||
|
"updateSubscriptions": "更新订阅"
|
||||||
},
|
},
|
||||||
"share": {
|
"share": {
|
||||||
"buttonText": "分享",
|
"buttonText": "分享",
|
||||||
|
|||||||
@@ -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}]",
|
||||||
|
|||||||
@@ -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,8 +68,11 @@ 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),
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@@ -69,10 +93,22 @@ class ProfilesOverviewModal extends HookConsumerWidget {
|
|||||||
icon: const Icon(Icons.sort),
|
icon: const Icon(Icons.sort),
|
||||||
label: Text(t.general.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),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user