mirror of
https://github.com/openlibrecommunity/olcng.git
synced 2026-07-03 14:05:17 +02:00
feat(subscription): add per-subscription update status tracking
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package xyz.zarazaex.olc.dto
|
||||
|
||||
enum class SubscriptionUpdateStatus {
|
||||
IDLE,
|
||||
LOADING,
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
SKIPPED
|
||||
}
|
||||
|
||||
data class SubscriptionStatus(
|
||||
val guid: String,
|
||||
val status: SubscriptionUpdateStatus = SubscriptionUpdateStatus.IDLE,
|
||||
val configCount: Int = 0
|
||||
)
|
||||
@@ -576,16 +576,18 @@ object AngConfigManager {
|
||||
Log.i(AppConfig.TAG, url)
|
||||
val userAgent = it.subscription.userAgent
|
||||
|
||||
val timeout = if (url.startsWith("https://key.zarazaex.xyz/sub")) 3000 else 15000
|
||||
|
||||
var configText = try {
|
||||
val httpPort = SettingsManager.getHttpPort()
|
||||
HttpUtil.getUrlContentWithUserAgent(url, userAgent, 15000, httpPort)
|
||||
HttpUtil.getUrlContentWithUserAgent(url, userAgent, timeout, httpPort)
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error", e)
|
||||
""
|
||||
}
|
||||
if (configText.isEmpty()) {
|
||||
configText = try {
|
||||
HttpUtil.getUrlContentWithUserAgent(url, userAgent)
|
||||
HttpUtil.getUrlContentWithUserAgent(url, userAgent, timeout)
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Update subscription: Failed to get URL content with user agent", e)
|
||||
""
|
||||
|
||||
@@ -56,6 +56,10 @@ class SubSettingActivity : BaseActivity() {
|
||||
viewModel.isUpdating.observe(this) { isUpdating ->
|
||||
adapter.setUpdating(isUpdating)
|
||||
}
|
||||
|
||||
viewModel.subscriptionStatuses.observe(this) { statuses ->
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -86,27 +90,77 @@ class SubSettingActivity : BaseActivity() {
|
||||
|
||||
showLoading()
|
||||
viewModel.isUpdating.value = true
|
||||
viewModel.subscriptionStatuses.value = emptyMap()
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val result = AngConfigManager.updateConfigViaSubAll()
|
||||
delay(500L)
|
||||
launch(Dispatchers.Main) {
|
||||
viewModel.isUpdating.value = false
|
||||
if (result.successCount + result.failureCount + result.skipCount == 0) {
|
||||
toast(R.string.title_update_subscription_no_subscription)
|
||||
} else if (result.successCount > 0 && result.failureCount + result.skipCount == 0) {
|
||||
toast(getString(R.string.title_update_config_count, result.configCount))
|
||||
} else {
|
||||
toast(
|
||||
getString(
|
||||
R.string.title_update_subscription_result,
|
||||
result.configCount, result.successCount, result.failureCount, result.skipCount
|
||||
)
|
||||
)
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
val subscriptions = viewModel.getAll()
|
||||
var totalConfigCount = 0
|
||||
var successCount = 0
|
||||
var failureCount = 0
|
||||
var skipCount = 0
|
||||
|
||||
val jobs = subscriptions.map { subscription ->
|
||||
launch(Dispatchers.IO) {
|
||||
val subId = subscription.guid
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
viewModel.updateSubscriptionStatus(subId, xyz.zarazaex.olc.dto.SubscriptionUpdateStatus.LOADING)
|
||||
}
|
||||
|
||||
val result = AngConfigManager.updateConfigViaSub(subscription)
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
when {
|
||||
result.successCount > 0 -> {
|
||||
viewModel.updateSubscriptionStatus(
|
||||
subId,
|
||||
xyz.zarazaex.olc.dto.SubscriptionUpdateStatus.SUCCESS,
|
||||
result.configCount
|
||||
)
|
||||
}
|
||||
result.skipCount > 0 -> {
|
||||
viewModel.updateSubscriptionStatus(
|
||||
subId,
|
||||
xyz.zarazaex.olc.dto.SubscriptionUpdateStatus.SKIPPED
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
viewModel.updateSubscriptionStatus(
|
||||
subId,
|
||||
xyz.zarazaex.olc.dto.SubscriptionUpdateStatus.FAILED
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(this@SubSettingActivity) {
|
||||
totalConfigCount += result.configCount
|
||||
successCount += result.successCount
|
||||
failureCount += result.failureCount
|
||||
skipCount += result.skipCount
|
||||
}
|
||||
}
|
||||
hideLoading()
|
||||
refreshData()
|
||||
}
|
||||
|
||||
jobs.forEach { it.join() }
|
||||
|
||||
delay(500L)
|
||||
viewModel.isUpdating.value = false
|
||||
|
||||
if (successCount + failureCount + skipCount == 0) {
|
||||
toast(R.string.title_update_subscription_no_subscription)
|
||||
} else if (successCount > 0 && failureCount + skipCount == 0) {
|
||||
toast(getString(R.string.title_update_config_count, totalConfigCount))
|
||||
} else {
|
||||
toast(
|
||||
getString(
|
||||
R.string.title_update_subscription_result,
|
||||
totalConfigCount, successCount, failureCount, skipCount
|
||||
)
|
||||
)
|
||||
}
|
||||
hideLoading()
|
||||
refreshData()
|
||||
}
|
||||
|
||||
true
|
||||
|
||||
@@ -8,8 +8,10 @@ import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import xyz.zarazaex.olc.AppConfig
|
||||
import xyz.zarazaex.olc.R
|
||||
import xyz.zarazaex.olc.contracts.BaseAdapterListener
|
||||
import xyz.zarazaex.olc.databinding.ItemRecyclerSubSettingBinding
|
||||
import xyz.zarazaex.olc.dto.SubscriptionUpdateStatus
|
||||
import xyz.zarazaex.olc.helper.ItemTouchHelperAdapter
|
||||
import xyz.zarazaex.olc.helper.ItemTouchHelperViewHolder
|
||||
import xyz.zarazaex.olc.util.Utils
|
||||
@@ -41,6 +43,38 @@ class SubSettingRecyclerAdapter(
|
||||
holder.itemSubSettingBinding.tvLastUpdated.text = Utils.formatTimestamp(subItem.lastUpdated)
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
val subStatus = viewModel.getSubscriptionStatus(subId)
|
||||
when (subStatus?.status) {
|
||||
SubscriptionUpdateStatus.LOADING -> {
|
||||
holder.itemSubSettingBinding.progressBar.visibility = View.VISIBLE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.visibility = View.VISIBLE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.text = holder.itemView.context.getString(R.string.title_updating)
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.setTextColor(Color.GRAY)
|
||||
}
|
||||
SubscriptionUpdateStatus.SUCCESS -> {
|
||||
holder.itemSubSettingBinding.progressBar.visibility = View.GONE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.visibility = View.VISIBLE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.text = "✓ ${subStatus.configCount}"
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.setTextColor(Color.parseColor("#4CAF50"))
|
||||
}
|
||||
SubscriptionUpdateStatus.FAILED -> {
|
||||
holder.itemSubSettingBinding.progressBar.visibility = View.GONE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.visibility = View.VISIBLE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.text = "✗"
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.setTextColor(Color.parseColor("#F44336"))
|
||||
}
|
||||
SubscriptionUpdateStatus.SKIPPED -> {
|
||||
holder.itemSubSettingBinding.progressBar.visibility = View.GONE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.visibility = View.VISIBLE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.text = "—"
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.setTextColor(Color.GRAY)
|
||||
}
|
||||
else -> {
|
||||
holder.itemSubSettingBinding.progressBar.visibility = View.GONE
|
||||
holder.itemSubSettingBinding.tvUpdateStatus.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
val isEnabled = !isUpdating
|
||||
|
||||
holder.itemSubSettingBinding.layoutEdit.isClickable = isEnabled
|
||||
|
||||
@@ -4,6 +4,8 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import xyz.zarazaex.olc.dto.SubscriptionCache
|
||||
import xyz.zarazaex.olc.dto.SubscriptionItem
|
||||
import xyz.zarazaex.olc.dto.SubscriptionStatus
|
||||
import xyz.zarazaex.olc.dto.SubscriptionUpdateStatus
|
||||
import xyz.zarazaex.olc.handler.MmkvManager
|
||||
import xyz.zarazaex.olc.handler.SettingsChangeManager
|
||||
import xyz.zarazaex.olc.handler.SettingsManager
|
||||
@@ -13,12 +15,24 @@ class SubscriptionsViewModel : ViewModel() {
|
||||
MmkvManager.decodeSubscriptions().toMutableList()
|
||||
|
||||
val isUpdating = MutableLiveData<Boolean>(false)
|
||||
val subscriptionStatuses = MutableLiveData<Map<String, SubscriptionStatus>>(emptyMap())
|
||||
|
||||
fun getAll(): List<SubscriptionCache> = subscriptions.toList()
|
||||
|
||||
fun reload() {
|
||||
subscriptions.clear()
|
||||
subscriptions.addAll(MmkvManager.decodeSubscriptions())
|
||||
subscriptionStatuses.value = emptyMap()
|
||||
}
|
||||
|
||||
fun updateSubscriptionStatus(guid: String, status: SubscriptionUpdateStatus, configCount: Int = 0) {
|
||||
val currentStatuses = subscriptionStatuses.value?.toMutableMap() ?: mutableMapOf()
|
||||
currentStatuses[guid] = SubscriptionStatus(guid, status, configCount)
|
||||
subscriptionStatuses.postValue(currentStatuses)
|
||||
}
|
||||
|
||||
fun getSubscriptionStatus(guid: String): SubscriptionStatus? {
|
||||
return subscriptionStatuses.value?.get(guid)
|
||||
}
|
||||
|
||||
fun remove(subId: String): Boolean {
|
||||
|
||||
@@ -168,9 +168,26 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_last_updated"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="@dimen/padding_spacing_dp8"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_update_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"/>
|
||||
android:layout_marginStart="@dimen/padding_spacing_dp8"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -298,6 +298,7 @@
|
||||
<string name="title_import_config_count">Импортировано профилей: %d</string>
|
||||
<string name="title_export_config_count">Экспортировано профилей: %d</string>
|
||||
<string name="title_update_config_count">Обновлено профилей: %d</string>
|
||||
<string name="title_updating">Обновление…</string>
|
||||
<string name="title_update_subscription_result">Обновлено профилей: %1$d (успешно: %2$d, ошибок: %3$d, пропущено: %4$d)</string>
|
||||
<string name="title_update_subscription_no_subscription">Нет подписок</string>
|
||||
<string name="toast_server_not_found_in_group">Выбранный профиль не найден в текущей группе</string>
|
||||
|
||||
@@ -304,6 +304,7 @@
|
||||
<string name="title_import_config_count">Import %d configs</string>
|
||||
<string name="title_export_config_count">Export %d configs</string>
|
||||
<string name="title_update_config_count">Update %d configs</string>
|
||||
<string name="title_updating">Updating…</string>
|
||||
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
|
||||
<string name="title_update_subscription_no_subscription">No subscriptions</string>
|
||||
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
|
||||
|
||||
Reference in New Issue
Block a user