From 3b4d895c1dcefaeaf46b40cb62c6e21840165c82 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 30 Apr 2026 10:52:14 +0800
Subject: [PATCH] Refactor: centralize subscription auto-update scheduling in
SubscriptionUpdater (#5567)
* Initial plan
* Refactor subscription auto-update system into SubscriptionUpdater facade
Agent-Logs-Url: https://github.com/2dust/v2rayNG/sessions/63125c89-4db9-4572-8adb-10c29ea531f5
Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
* Add TODO comment to legacy fallback in SubscriptionUpdater
Agent-Logs-Url: https://github.com/2dust/v2rayNG/sessions/63125c89-4db9-4572-8adb-10c29ea531f5
Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
* Use per-subscription auto-update intervals
Migrate subscription auto-update from a global toggle/interval to per-subscription scheduling.
- Add per-subscription updateInterval (Long, default 1440 minutes) and persist in SubEdit UI; validate minimum interval (15 minutes).
- Update SubscriptionUpdater to schedule/cancel tasks per subscription, compute initial delay from subscription.lastUpdated, and skip when autoUpdate is disabled or subId is missing.
- Remove legacy global auto-update prefs and fallback logic: deleted global prefs/UI handling, MmkvManager last-attempt encode/decode, and related default setting.
- Ensure cancelling of scheduled task when removing a subscription and adjust SettingsManager initialization.
- Add localized toast strings for invalid update interval and update related layouts/resources.
This change enables individual update intervals per subscription and simplifies the updater by removing legacy global state and last-attempt persistence.
* Allow optional force-reschedule for subscriptions
Add a forceReschedule flag to SubscriptionUpdater.sync to choose the WorkManager policy (KEEP by default, REPLACE when forced). Refactor scheduleOne to accept an ExistingPeriodicWorkPolicy and use it when enqueuing periodic work; syncOne now forces REPLACE so a manual refresh recalculates next run. Remove the cancelAll helper and improve logging to include the reschedule flag/policy. This lets startup keep existing periodic work while allowing explicit reschedules when needed (e.g., after manual updates).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
---
V2rayNG/app/src/main/AndroidManifest.xml | 5 +
.../src/main/java/com/v2ray/ang/AppConfig.kt | 4 +-
.../com/v2ray/ang/dto/SubscriptionItem.kt | 2 +-
.../java/com/v2ray/ang/handler/MmkvManager.kt | 1 +
.../com/v2ray/ang/handler/SettingsManager.kt | 8 +-
.../v2ray/ang/handler/SubscriptionUpdater.kt | 196 +++++++++++++++---
.../com/v2ray/ang/receiver/BootReceiver.kt | 2 +
.../java/com/v2ray/ang/ui/MainActivity.kt | 2 +
.../java/com/v2ray/ang/ui/SettingsActivity.kt | 53 -----
.../java/com/v2ray/ang/ui/SubEditActivity.kt | 25 +++
.../src/main/res/layout/activity_sub_edit.xml | 21 +-
.../app/src/main/res/values-ar/strings.xml | 1 +
.../app/src/main/res/values-bn/strings.xml | 1 +
.../src/main/res/values-bqi-rIR/strings.xml | 1 +
.../app/src/main/res/values-fa/strings.xml | 1 +
.../app/src/main/res/values-ru/strings.xml | 1 +
.../app/src/main/res/values-vi/strings.xml | 1 +
.../src/main/res/values-zh-rCN/strings.xml | 1 +
.../src/main/res/values-zh-rTW/strings.xml | 1 +
V2rayNG/app/src/main/res/values/strings.xml | 1 +
.../app/src/main/res/xml/pref_settings.xml | 13 --
21 files changed, 238 insertions(+), 103 deletions(-)
diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml
index 9d2dd6e8..59a167b3 100644
--- a/V2rayNG/app/src/main/AndroidManifest.xml
+++ b/V2rayNG/app/src/main/AndroidManifest.xml
@@ -273,6 +273,11 @@
+
+
+ scheduleOne(
+ context = context,
+ subId = sub.guid,
+ shouldRun = sub.subscription.autoUpdate,
+ existingWorkPolicy = existingWorkPolicy
+ )
+ }
+ LogUtil.i(
+ AppConfig.TAG,
+ "SubscriptionUpdater: sync complete forceReschedule=$forceReschedule"
+ )
+ }
+
+ /**
+ * Sync a single subscription's task.
+ * Call from: SubEditActivity after saving, after a manual update (to reset the timer).
+ */
+ fun syncOne(context: Context = AngApplication.application, subId: String) {
+ val subItem = MmkvManager.decodeSubscription(subId) ?: return
+ scheduleOne(
+ context = context,
+ subId = subId,
+ shouldRun = subItem.autoUpdate,
+ existingWorkPolicy = ExistingPeriodicWorkPolicy.REPLACE
+ )
+ }
+
+ /**
+ * Cancel the auto-update task for a single subscription.
+ * Call from: when a subscription is deleted.
+ */
+ fun cancelOne(context: Context = AngApplication.application, subId: String) {
+ RemoteWorkManager.getInstance(context)
+ .cancelUniqueWork(taskName(subId))
+ }
+
+ // -------------------------------------------------------------------------
+ // Internal scheduling logic
+ // -------------------------------------------------------------------------
+
+ private fun taskName(subId: String) = "${AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME}_$subId"
+
+ private fun scheduleOne(
+ context: Context,
+ subId: String,
+ shouldRun: Boolean,
+ existingWorkPolicy: ExistingPeriodicWorkPolicy
+ ) {
+ val rw = RemoteWorkManager.getInstance(context)
+ if (!shouldRun) {
+ rw.cancelUniqueWork(taskName(subId))
+ LogUtil.d(AppConfig.TAG, "SubscriptionUpdater: cancelled task for $subId")
+ return
+ }
+
+ val subItem = MmkvManager.decodeSubscription(subId) ?: return
+
+ val intervalMinutes = maxOf(
+ AppConfig.SUBSCRIPTION_MIN_INTERVAL_MINUTES,
+ subItem.updateInterval
+ )
+
+ // Base initial delay on the last successful update time persisted in subscription.
+ val lastUpdated = subItem.lastUpdated
+ val intervalMillis = intervalMinutes * 60 * 1000L
+ val now = System.currentTimeMillis()
+ val initialDelayMillis = if (lastUpdated <= 0L) {
+ 0L
+ } else {
+ maxOf(0L, lastUpdated + intervalMillis - now)
+ }
+
+ val request = PeriodicWorkRequestBuilder(intervalMinutes, TimeUnit.MINUTES)
+ .setConstraints(
+ Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.CONNECTED)
+ .build()
+ )
+ .setInputData(workDataOf(KEY_SUB_ID to subId))
+ .setInitialDelay(initialDelayMillis, TimeUnit.MILLISECONDS)
+ .addTag(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME)
+ .build()
+
+ rw.enqueueUniquePeriodicWork(
+ taskName(subId),
+ existingWorkPolicy,
+ request
+ )
+
+ LogUtil.i(
+ AppConfig.TAG,
+ "SubscriptionUpdater: scheduled [$subId] interval=${intervalMinutes}min " +
+ "initialDelay=${initialDelayMillis / 1000}s policy=$existingWorkPolicy"
+ )
+ }
+
+ // -------------------------------------------------------------------------
+ // Worker
+ // -------------------------------------------------------------------------
+
+ private const val KEY_SUB_ID = "subId"
+
class UpdateTask(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
@@ -23,39 +158,50 @@ object SubscriptionUpdater {
NotificationCompat.Builder(applicationContext, AppConfig.SUBSCRIPTION_UPDATE_CHANNEL)
.setWhen(0)
.setTicker("Update")
- .setContentTitle(context.getString(R.string.title_pref_auto_update_subscription))
+ .setContentTitle(applicationContext.getString(R.string.title_pref_auto_update_subscription))
.setSmallIcon(R.drawable.ic_stat_name)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
- /**
- * Performs the subscription update work.
- * @return The result of the work.
- */
@SuppressLint("MissingPermission")
override suspend fun doWork(): Result {
- LogUtil.i(AppConfig.TAG, "subscription automatic update starting")
+ val subId = inputData.getString(KEY_SUB_ID)
+ LogUtil.i(AppConfig.TAG, "SubscriptionUpdater automatic update starting: $subId")
- val subs = MmkvManager.decodeSubscriptions().filter { it.subscription.autoUpdate }
-
- for (sub in subs) {
- val subItem = sub.subscription
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- notification.setChannelId(AppConfig.SUBSCRIPTION_UPDATE_CHANNEL)
- val channel =
- NotificationChannel(
- AppConfig.SUBSCRIPTION_UPDATE_CHANNEL,
- AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME,
- NotificationManager.IMPORTANCE_MIN
- )
- notificationManager.createNotificationChannel(channel)
- }
- notificationManager.notify(3, notification.build())
- LogUtil.i(AppConfig.TAG, "subscription automatic update: ---${subItem.remarks}")
- AngConfigManager.updateConfigViaSub(sub)
- notification.setContentText("Updating ${subItem.remarks}")
+ if (subId.isNullOrEmpty()) {
+ LogUtil.w(AppConfig.TAG, "SubscriptionUpdater: missing subId in worker input")
+ return Result.success()
}
+
+
+ val subItem = MmkvManager.decodeSubscription(subId)
+ if (subItem == null) {
+ LogUtil.w(AppConfig.TAG, "SubscriptionUpdater: no subscription found for $subId")
+ return Result.success()
+ }
+
+ if (!subItem.autoUpdate) {
+ LogUtil.i(AppConfig.TAG, "SubscriptionUpdater: auto-update disabled for $subId, skip")
+ return Result.success()
+ }
+
+ val sub = SubscriptionCache(subId, subItem)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ notificationManager.createNotificationChannel(
+ NotificationChannel(
+ AppConfig.SUBSCRIPTION_UPDATE_CHANNEL,
+ AppConfig.SUBSCRIPTION_UPDATE_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_MIN
+ )
+ )
+ }
+
+ notificationManager.notify(3, notification.build())
+ LogUtil.i(AppConfig.TAG, "SubscriptionUpdater automatic update: ---${sub.subscription.remarks}")
+ AngConfigManager.updateConfigViaSub(sub)
+ notification.setContentText("Updating ${sub.subscription.remarks}")
+
notificationManager.cancel(3)
return Result.success()
}
diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/receiver/BootReceiver.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/receiver/BootReceiver.kt
index 3d00f0eb..c34c067f 100644
--- a/V2rayNG/app/src/main/java/com/v2ray/ang/receiver/BootReceiver.kt
+++ b/V2rayNG/app/src/main/java/com/v2ray/ang/receiver/BootReceiver.kt
@@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent
import com.v2ray.ang.AppConfig
import com.v2ray.ang.handler.MmkvManager
+import com.v2ray.ang.handler.SubscriptionUpdater
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.LogUtil
@@ -37,5 +38,6 @@ class BootReceiver : BroadcastReceiver() {
LogUtil.i(AppConfig.TAG, "BootReceiver: Starting V2Ray service")
V2RayServiceManager.startVService(context)
+ SubscriptionUpdater.sync(context)
}
}
diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt
index 7df9bfd8..4c8a8466 100644
--- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt
+++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt
@@ -31,6 +31,7 @@ import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsChangeManager
import com.v2ray.ang.handler.SettingsManager
+import com.v2ray.ang.handler.SubscriptionUpdater
import com.v2ray.ang.handler.V2RayServiceManager
import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.Utils
@@ -98,6 +99,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
setupGroupTab()
setupViewModel()
+ SubscriptionUpdater.sync()
mainViewModel.reloadServerList()
checkAndRequestPermission(PermissionType.POST_NOTIFICATIONS) {
diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt
index fba3ce1d..9c281231 100644
--- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt
+++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SettingsActivity.kt
@@ -6,19 +6,12 @@ import androidx.preference.CheckBoxPreference
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
-import androidx.work.ExistingPeriodicWorkPolicy
-import androidx.work.PeriodicWorkRequest
-import androidx.work.multiprocess.RemoteWorkManager
-import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.VPN
import com.v2ray.ang.R
-import com.v2ray.ang.extension.toLongEx
import com.v2ray.ang.handler.MmkvManager
-import com.v2ray.ang.handler.SubscriptionUpdater
import com.v2ray.ang.helper.MmkvPreferenceDataStore
import com.v2ray.ang.util.Utils
-import java.util.concurrent.TimeUnit
class SettingsActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -48,8 +41,6 @@ class SettingsActivity : BaseActivity() {
private val fragmentLength by lazy { findPreference(AppConfig.PREF_FRAGMENT_LENGTH) }
private val fragmentInterval by lazy { findPreference(AppConfig.PREF_FRAGMENT_INTERVAL) }
- private val autoUpdateCheck by lazy { findPreference(AppConfig.SUBSCRIPTION_AUTO_UPDATE) }
- private val autoUpdateInterval by lazy { findPreference(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) }
private val mode by lazy { findPreference(AppConfig.PREF_MODE) }
private val hevTunLogLevel by lazy { findPreference(AppConfig.PREF_HEV_TUNNEL_LOGLEVEL) }
@@ -96,24 +87,6 @@ class SettingsActivity : BaseActivity() {
true
}
- autoUpdateCheck?.setOnPreferenceChangeListener { _, newValue ->
- val value = newValue as Boolean
- autoUpdateCheck?.isChecked = value
- autoUpdateInterval?.isEnabled = value
- autoUpdateInterval?.text?.toLongEx()?.let {
- if (newValue) configureUpdateTask(it) else cancelUpdateTask()
- }
- true
- }
-
- autoUpdateInterval?.setOnPreferenceChangeListener { _, newValue ->
- val interval = (newValue as? String)?.toLongEx()
- if (interval != null && autoUpdateCheck?.isChecked == true) {
- configureUpdateTask(interval)
- }
- true
- }
-
mode?.setOnPreferenceChangeListener { pref, newValue ->
val valueStr = newValue.toString()
(pref as? ListPreference)?.let { lp ->
@@ -203,9 +176,6 @@ class SettingsActivity : BaseActivity() {
// Initialize fragment-dependent UI states
updateFragment(MmkvManager.decodeSettingsBool(AppConfig.PREF_FRAGMENT_ENABLED, false))
- // Initialize auto-update interval state
- autoUpdateInterval?.isEnabled = MmkvManager.decodeSettingsBool(AppConfig.SUBSCRIPTION_AUTO_UPDATE, false)
-
updateDynamicSocksPort(MmkvManager.decodeSettingsBool(AppConfig.PREF_DYNAMIC_SOCKS_PORT, false))
}
@@ -243,29 +213,6 @@ class SettingsActivity : BaseActivity() {
vpnDns?.isEnabled = !enabled
}
- private fun configureUpdateTask(interval: Long) {
- val rw = RemoteWorkManager.getInstance(AngApplication.application)
- rw.cancelUniqueWork(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME)
- rw.enqueueUniquePeriodicWork(
- AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME,
- ExistingPeriodicWorkPolicy.UPDATE,
- PeriodicWorkRequest.Builder(
- SubscriptionUpdater.UpdateTask::class.java,
- interval,
- TimeUnit.MINUTES
- )
- .apply {
- setInitialDelay(interval, TimeUnit.MINUTES)
- }
- .build()
- )
- }
-
- private fun cancelUpdateTask() {
- val rw = RemoteWorkManager.getInstance(AngApplication.application)
- rw.cancelUniqueWork(AppConfig.SUBSCRIPTION_UPDATE_TASK_NAME)
- }
-
private fun updateMux(enabled: Boolean) {
muxConcurrency?.isEnabled = enabled
muxXudpConcurrency?.isEnabled = enabled
diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt
index c6bd3870..a2b113fe 100644
--- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt
+++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt
@@ -15,6 +15,7 @@ import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsChangeManager
import com.v2ray.ang.handler.SettingsManager
+import com.v2ray.ang.handler.SubscriptionUpdater
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -51,6 +52,7 @@ class SubEditActivity : BaseActivity() {
binding.etFilter.text = Utils.getEditable(subItem.filter)
binding.chkEnable.isChecked = subItem.enabled
binding.autoUpdateCheck.isChecked = subItem.autoUpdate
+ binding.etUpdateInterval.text = Utils.getEditable(subItem.updateInterval.toString())
binding.allowInsecureUrl.isChecked = subItem.allowInsecureUrl
binding.etPreProfile.text = Utils.getEditable(subItem.prevProfile)
binding.etNextProfile.text = Utils.getEditable(subItem.nextProfile)
@@ -65,6 +67,7 @@ class SubEditActivity : BaseActivity() {
binding.etUrl.text = null
binding.etFilter.text = null
binding.chkEnable.isChecked = true
+ binding.etUpdateInterval.text = null
binding.etPreProfile.text = null
binding.etNextProfile.text = null
return true
@@ -82,6 +85,27 @@ class SubEditActivity : BaseActivity() {
subItem.filter = binding.etFilter.text.toString()
subItem.enabled = binding.chkEnable.isChecked
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
+
+ val intervalInput = binding.etUpdateInterval.text.toString().trim()
+ val intervalMinutes = intervalInput.toLongOrNull()
+ if (subItem.autoUpdate) {
+ // autoUpdate is enabled: interval must be valid
+ if (intervalMinutes == null) {
+ // field is empty, reset to default
+ subItem.updateInterval = SubscriptionItem().updateInterval
+ } else if (intervalMinutes < AppConfig.SUBSCRIPTION_MIN_INTERVAL_MINUTES) {
+ toast(R.string.toast_invalid_update_interval)
+ return false
+ } else {
+ subItem.updateInterval = intervalMinutes
+ }
+ } else {
+ // autoUpdate is disabled: save only if the value is valid, otherwise keep the existing value
+ if (intervalMinutes != null && intervalMinutes >= AppConfig.SUBSCRIPTION_MIN_INTERVAL_MINUTES) {
+ subItem.updateInterval = intervalMinutes
+ }
+ }
+
subItem.prevProfile = binding.etPreProfile.text.toString()
subItem.nextProfile = binding.etNextProfile.text.toString()
subItem.allowInsecureUrl = binding.allowInsecureUrl.isChecked
@@ -105,6 +129,7 @@ class SubEditActivity : BaseActivity() {
}
MmkvManager.encodeSubscription(editSubId, subItem)
+ SubscriptionUpdater.syncOne(subId = editSubId)
toastSuccess(R.string.toast_success)
finish()
return true
diff --git a/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml b/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml
index 87a44c58..0ac5f9fa 100644
--- a/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml
+++ b/V2rayNG/app/src/main/res/layout/activity_sub_edit.xml
@@ -146,7 +146,6 @@
android:layout_weight="1.0"
android:text="@string/sub_auto_update" />
-
+
+
+
+
+
+
+
+
Previous proxy configuration remarks
Next proxy configuration remarks
The configuration remarks exists and is unique
+ فاصل التحديث غير صالح. الحد الأدنى هو 15 دقيقة.
تحديث الاشتراك (أول خطوة)
Tcping لجميع الإعدادات
اختبر جميع الإعدادات (3)
diff --git a/V2rayNG/app/src/main/res/values-bn/strings.xml b/V2rayNG/app/src/main/res/values-bn/strings.xml
index 63fec7ec..4b7103c9 100644
--- a/V2rayNG/app/src/main/res/values-bn/strings.xml
+++ b/V2rayNG/app/src/main/res/values-bn/strings.xml
@@ -300,6 +300,7 @@
Previous proxy configuration remarks
Next proxy configuration remarks
The configuration remarks exists and is unique
+ আপডেটের বিরতি সঠিক নয়। সর্বনিম্ন ১৫ মিনিট।
সাবস্ক্রিপশন আপডেট
সব কনফিগারেশন TCPing
সব কনফিগারেশন প্রকৃত বিলম্ব
diff --git a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml
index 9d1c2904..ba3cad76 100644
--- a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml
+++ b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml
@@ -300,6 +300,7 @@
نوم موستعار پروکسی دیندایی
نوم موستعار پروکسی نیایی
موتمعن بۊ ک نوم موستعار هڌس وو جۊرس نی
+ فاسله ورۊ کردن نا موئتبر هڌ. اقل مقدار 15 دؽقه هڌ.
ورۊ کردن اشتراک بونکۊ سکویی
Tcping کانفیگا بونکۊ سکویی
تئخیر واقعی کانفیگا بونکۊ سکویی
diff --git a/V2rayNG/app/src/main/res/values-fa/strings.xml b/V2rayNG/app/src/main/res/values-fa/strings.xml
index 581e90e5..2de37668 100644
--- a/V2rayNG/app/src/main/res/values-fa/strings.xml
+++ b/V2rayNG/app/src/main/res/values-fa/strings.xml
@@ -297,6 +297,7 @@
نام مستعار پروکسی قبلی
نام مستعار پروکسی بعدی
لطفاً مطمئن شوید که نام مستعار وجود دارد و منحصر به فرد است
+ بازه بروزرسانی نامعتبر است. حداقل مقدار 15 دقیقه است.
بهروزرسانی گروه فعلی اشتراک
TCPING کانفیگ های گروه فعلی
تاخیر واقعی کانفیگ های گروه فعلی
diff --git a/V2rayNG/app/src/main/res/values-ru/strings.xml b/V2rayNG/app/src/main/res/values-ru/strings.xml
index 623017ab..9c0f106e 100644
--- a/V2rayNG/app/src/main/res/values-ru/strings.xml
+++ b/V2rayNG/app/src/main/res/values-ru/strings.xml
@@ -303,6 +303,7 @@
Предыдущий профиль прокси
Следующий профиль прокси
Профиль должен быть уникальным
+ Неверный интервал обновления. Минимум 15 минут.
Обновить подписку
Проверить профили
Проверить задержку профилей
diff --git a/V2rayNG/app/src/main/res/values-vi/strings.xml b/V2rayNG/app/src/main/res/values-vi/strings.xml
index 8232249d..6098c3d3 100644
--- a/V2rayNG/app/src/main/res/values-vi/strings.xml
+++ b/V2rayNG/app/src/main/res/values-vi/strings.xml
@@ -300,6 +300,7 @@
Previous proxy configuration remarks
Next proxy configuration remarks
The configuration remarks exists and is unique
+ Khoảng thời gian cập nhật không hợp lệ. Giá trị tối thiểu là 15 phút.
Cập nhật các gói đăng ký
Ping tất cả máy chủ
Kiểm tra HTTP tất cả máy chủ
diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml
index 53ad90d1..3587e7e7 100644
--- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml
+++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml
@@ -298,6 +298,7 @@
前置代理配置别名
落地代理配置別名
请确保配置别名存在并唯一
+ 更新间隔无效,最小值为 15 分钟。
更新订阅
测试配置 Tcping
测试配置真连接
diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml
index 8efd19fa..0205150c 100644
--- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml
+++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml
@@ -299,6 +299,7 @@
前置代理設定檔别名
落地代理設定檔別名
请确保設定檔别名存在并唯一
+ 更新間隔無效,最小值為 15 分鐘。
更新訂閱
偵測設定 Tcping
偵測設定真延遲
diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml
index 10a170ac..e70a7cae 100644
--- a/V2rayNG/app/src/main/res/values/strings.xml
+++ b/V2rayNG/app/src/main/res/values/strings.xml
@@ -305,6 +305,7 @@
Previous proxy config remarks
Next proxy config remarks
The config remarks exist and are unique
+ Invalid update interval. Minimum is 15 minutes.
Update subscription
Tcping config
Real delay config
diff --git a/V2rayNG/app/src/main/res/xml/pref_settings.xml b/V2rayNG/app/src/main/res/xml/pref_settings.xml
index 19a086b1..0095bc76 100644
--- a/V2rayNG/app/src/main/res/xml/pref_settings.xml
+++ b/V2rayNG/app/src/main/res/xml/pref_settings.xml
@@ -278,19 +278,6 @@
android:title="@string/title_pref_fragment_packets" />
-
-
-
-