From 9af6472f8bf8fab585b6d8d465035b7af4f0c0fe Mon Sep 17 00:00:00 2001 From: zarazaex69 Date: Sat, 11 Apr 2026 12:21:13 +0300 Subject: [PATCH] feat(subscription): add update state management and UI feedback --- .../xyz/zarazaex/olc/ui/SubSettingActivity.kt | 80 +++++++++++-------- .../olc/ui/SubSettingRecyclerAdapter.kt | 33 +++++++- .../olc/viewmodel/SubscriptionsViewModel.kt | 3 + 3 files changed, 79 insertions(+), 37 deletions(-) diff --git a/V2rayNG/app/src/main/java/xyz/zarazaex/olc/ui/SubSettingActivity.kt b/V2rayNG/app/src/main/java/xyz/zarazaex/olc/ui/SubSettingActivity.kt index 8c2cb93b..e5521860 100644 --- a/V2rayNG/app/src/main/java/xyz/zarazaex/olc/ui/SubSettingActivity.kt +++ b/V2rayNG/app/src/main/java/xyz/zarazaex/olc/ui/SubSettingActivity.kt @@ -41,7 +41,6 @@ class SubSettingActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - //setContentView(binding.root) setContentViewWithToolbar(binding.root, showHomeAsUp = true, title = getString(R.string.title_sub_setting)) adapter = SubSettingRecyclerAdapter(viewModel, ActivityAdapterListener()) @@ -53,6 +52,10 @@ class SubSettingActivity : BaseActivity() { mItemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter)) mItemTouchHelper?.attachToRecyclerView(binding.recyclerView) + + viewModel.isUpdating.observe(this) { isUpdating -> + adapter.setUpdating(isUpdating) + } } override fun onResume() { @@ -62,44 +65,55 @@ class SubSettingActivity : BaseActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.action_sub_setting, menu) + viewModel.isUpdating.observe(this) { isUpdating -> + menu.findItem(R.id.sub_update)?.isEnabled = !isUpdating + menu.findItem(R.id.add_config)?.isEnabled = !isUpdating + } return super.onCreateOptionsMenu(menu) } - override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { - R.id.add_config -> { - startActivity(Intent(this, SubEditActivity::class.java)) - true - } - - R.id.sub_update -> { - showLoading() - - lifecycleScope.launch(Dispatchers.IO) { - val result = AngConfigManager.updateConfigViaSubAll() - delay(500L) - launch(Dispatchers.Main) { - 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 - ) - ) - } - hideLoading() - refreshData() - } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.add_config -> { + startActivity(Intent(this, SubEditActivity::class.java)) + true } - true + R.id.sub_update -> { + if (viewModel.isUpdating.value == true) { + return true + } + + showLoading() + viewModel.isUpdating.value = true + + 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 + ) + ) + } + hideLoading() + refreshData() + } + } + + true + } + + else -> super.onOptionsItemSelected(item) } - - else -> super.onOptionsItemSelected(item) - } @SuppressLint("NotifyDataSetChanged") diff --git a/V2rayNG/app/src/main/java/xyz/zarazaex/olc/ui/SubSettingRecyclerAdapter.kt b/V2rayNG/app/src/main/java/xyz/zarazaex/olc/ui/SubSettingRecyclerAdapter.kt index c6e56459..83107435 100644 --- a/V2rayNG/app/src/main/java/xyz/zarazaex/olc/ui/SubSettingRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/java/xyz/zarazaex/olc/ui/SubSettingRecyclerAdapter.kt @@ -20,6 +20,15 @@ class SubSettingRecyclerAdapter( private val adapterListener: BaseAdapterListener? ) : RecyclerView.Adapter(), ItemTouchHelperAdapter { + private var isUpdating = false + + fun setUpdating(updating: Boolean) { + if (isUpdating != updating) { + isUpdating = updating + notifyDataSetChanged() + } + } + override fun getItemCount() = viewModel.getAll().size override fun onBindViewHolder(holder: MainViewHolder, position: Int) { @@ -32,16 +41,28 @@ class SubSettingRecyclerAdapter( holder.itemSubSettingBinding.tvLastUpdated.text = Utils.formatTimestamp(subItem.lastUpdated) holder.itemView.setBackgroundColor(Color.TRANSPARENT) + val isEnabled = !isUpdating + + holder.itemSubSettingBinding.layoutEdit.isClickable = isEnabled + holder.itemSubSettingBinding.layoutEdit.alpha = if (isEnabled) 1.0f else 0.5f holder.itemSubSettingBinding.layoutEdit.setOnClickListener { - adapterListener?.onEdit(subId, position) + if (isEnabled) { + adapterListener?.onEdit(subId, position) + } } + holder.itemSubSettingBinding.layoutRemove.isClickable = isEnabled + holder.itemSubSettingBinding.layoutRemove.alpha = if (isEnabled) 1.0f else 0.5f holder.itemSubSettingBinding.layoutRemove.setOnClickListener { - adapterListener?.onRemove(subId, position) + if (isEnabled) { + adapterListener?.onRemove(subId, position) + } } + holder.itemSubSettingBinding.chkEnable.isEnabled = isEnabled + holder.itemSubSettingBinding.chkEnable.alpha = if (isEnabled) 1.0f else 0.5f holder.itemSubSettingBinding.chkEnable.setOnCheckedChangeListener { it, isChecked -> - if (!it.isPressed) return@setOnCheckedChangeListener + if (!it.isPressed || !isEnabled) return@setOnCheckedChangeListener subItem.enabled = isChecked viewModel.update(subId, subItem) } @@ -56,8 +77,12 @@ class SubSettingRecyclerAdapter( holder.itemSubSettingBinding.layoutShare.visibility = View.VISIBLE holder.itemSubSettingBinding.chkEnable.visibility = View.VISIBLE holder.itemSubSettingBinding.layoutLastUpdated.visibility = View.VISIBLE + holder.itemSubSettingBinding.layoutShare.isClickable = isEnabled + holder.itemSubSettingBinding.layoutShare.alpha = if (isEnabled) 1.0f else 0.5f holder.itemSubSettingBinding.layoutShare.setOnClickListener { - adapterListener?.onShare(subItem.url) + if (isEnabled) { + adapterListener?.onShare(subItem.url) + } } } } diff --git a/V2rayNG/app/src/main/java/xyz/zarazaex/olc/viewmodel/SubscriptionsViewModel.kt b/V2rayNG/app/src/main/java/xyz/zarazaex/olc/viewmodel/SubscriptionsViewModel.kt index ac8913ae..970f00fe 100644 --- a/V2rayNG/app/src/main/java/xyz/zarazaex/olc/viewmodel/SubscriptionsViewModel.kt +++ b/V2rayNG/app/src/main/java/xyz/zarazaex/olc/viewmodel/SubscriptionsViewModel.kt @@ -1,5 +1,6 @@ package xyz.zarazaex.olc.viewmodel +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import xyz.zarazaex.olc.dto.SubscriptionCache import xyz.zarazaex.olc.dto.SubscriptionItem @@ -11,6 +12,8 @@ class SubscriptionsViewModel : ViewModel() { private val subscriptions: MutableList = MmkvManager.decodeSubscriptions().toMutableList() + val isUpdating = MutableLiveData(false) + fun getAll(): List = subscriptions.toList() fun reload() {