From 1b9401fc42214816218b212717f8e910d2327b84 Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Mon, 20 Jul 2026 09:49:53 +0800 Subject: [PATCH] Refactor BaseViewModel, which inherits from AndroidViewModel. --- .../main/java/com/v2ray/ang/extension/_Ext.kt | 4 --- .../com/v2ray/ang/ui/main/MainActivity.kt | 2 +- .../v2ray/ang/viewmodel/AppPickerViewModel.kt | 3 +- .../v2ray/ang/viewmodel/BackupViewModel.kt | 3 +- .../com/v2ray/ang/viewmodel/BaseViewModel.kt | 30 ++++++++++++------- .../ang/viewmodel/CheckUpdateViewModel.kt | 3 +- .../v2ray/ang/viewmodel/LogcatViewModel.kt | 3 +- .../com/v2ray/ang/viewmodel/MainViewModel.kt | 8 +++-- .../ang/viewmodel/PerAppProxyViewModel.kt | 3 +- .../ang/viewmodel/RoutingSettingsViewModel.kt | 3 +- .../v2ray/ang/viewmodel/SettingsViewModel.kt | 3 +- .../ang/viewmodel/SubscriptionsViewModel.kt | 7 ++++- .../v2ray/ang/viewmodel/UserAssetViewModel.kt | 3 +- 13 files changed, 47 insertions(+), 28 deletions(-) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/extension/_Ext.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/extension/_Ext.kt index 7a2cc8d1..49bb8279 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/extension/_Ext.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/extension/_Ext.kt @@ -9,7 +9,6 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.widget.Toast -import com.v2ray.ang.AngApplication import com.v2ray.ang.compose.AppSnackbarManager import com.v2ray.ang.compose.ToastType import com.v2ray.ang.enums.EConfigType @@ -17,9 +16,6 @@ import java.io.Serializable import java.net.URI import java.util.Locale -val myApp: AngApplication - get() = AngApplication.application - private inline fun runOnMain(crossinline block: () -> Unit) { if (Looper.myLooper() == Looper.getMainLooper()) { block() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainActivity.kt index 34acdf88..314a604f 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainActivity.kt @@ -57,7 +57,7 @@ import kotlinx.coroutines.withContext class MainActivity : HelperBaseComponentActivity() { private val mainViewModel: MainViewModel by viewModels { - MainViewModel.Factory(MainRepository(application as AngApplication)) + MainViewModel.Factory(application, MainRepository(application as AngApplication)) } private val requestVpnPermission = diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/AppPickerViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/AppPickerViewModel.kt index b337b514..b1d82ae8 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/AppPickerViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/AppPickerViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import android.content.Context import com.v2ray.ang.AppConfig import com.v2ray.ang.dto.AppInfo @@ -15,7 +16,7 @@ import java.text.Collator /** * ViewModel for AppPicker screen. */ -class AppPickerViewModel : BaseViewModel() { +class AppPickerViewModel(application: Application) : BaseViewModel(application) { private val _selectedPackages = MutableStateFlow>(emptySet()) val selectedPackages: StateFlow> = _selectedPackages.asStateFlow() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/BackupViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/BackupViewModel.kt index 71aba86c..7d61d779 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/BackupViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/BackupViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import android.net.Uri import com.tencent.mmkv.MMKV import com.v2ray.ang.AppConfig @@ -19,7 +20,7 @@ import java.io.File import java.text.SimpleDateFormat import java.util.Locale -class BackupViewModel : BaseViewModel() { +class BackupViewModel(application: Application) : BaseViewModel(application) { private val _webDavConfig = MutableStateFlow(MmkvManager.decodeWebDavConfig()) val webDavConfig: StateFlow = _webDavConfig.asStateFlow() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/BaseViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/BaseViewModel.kt index 5c8a9771..71c7cedf 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/BaseViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/BaseViewModel.kt @@ -1,8 +1,9 @@ package com.v2ray.ang.viewmodel -import androidx.lifecycle.ViewModel +import android.app.Application +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope -import com.v2ray.ang.extension.myApp +import com.v2ray.ang.AngApplication import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toastError import com.v2ray.ang.extension.toastSuccess @@ -17,7 +18,14 @@ import kotlinx.coroutines.launch /** * Base ViewModel that encapsulates common UI event handling logic. */ -abstract class BaseViewModel : ViewModel() { +abstract class BaseViewModel(application: Application) : AndroidViewModel(application) { + + /** + * Safe access to custom application instance. + */ + protected val app: AngApplication by lazy { + application as AngApplication + } @Suppress("PropertyName") protected val _isLoading = MutableStateFlow(false) @@ -31,56 +39,56 @@ abstract class BaseViewModel : ViewModel() { * Send neutral toast event (Resource ID). */ fun toast(resId: Int) { - myApp.toast(resId) + app.toast(resId) } /** * Send neutral toast event (String). */ fun toast(message: String) { - myApp.toast(message) + app.toast(message) } /** * Send success toast event (Resource ID). */ fun toastSuccess(resId: Int) { - myApp.toastSuccess(resId) + app.toastSuccess(resId) } /** * Send success toast event (String). */ fun toastSuccess(message: String) { - myApp.toastSuccess(message) + app.toastSuccess(message) } /** * Send error toast event (Resource ID). */ fun toastError(resId: Int) { - myApp.toastError(resId) + app.toastError(resId) } /** * Send error toast event (String). */ fun toastError(message: String) { - myApp.toastError(message) + app.toastError(message) } /** * Get string from resource ID. */ fun getString(resId: Int): String { - return myApp.getString(resId) + return app.getString(resId) } /** * Get formatted string from resource ID. */ fun getString(resId: Int, vararg formatArgs: Any?): String { - return myApp.getString(resId, *formatArgs) + return app.getString(resId, *formatArgs) } /** diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/CheckUpdateViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/CheckUpdateViewModel.kt index 17a2f3d7..6b3339d3 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/CheckUpdateViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/CheckUpdateViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.dto.CheckUpdateResult @@ -10,7 +11,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -class CheckUpdateViewModel : BaseViewModel() { +class CheckUpdateViewModel(application: Application) : BaseViewModel(application) { private val _checkPreRelease = MutableStateFlow( MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/LogcatViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/LogcatViewModel.kt index 7826259b..e6797f5d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/LogcatViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/LogcatViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.util.LogUtil @@ -8,7 +9,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import java.io.IOException -class LogcatViewModel : BaseViewModel() { +class LogcatViewModel(application: Application) : BaseViewModel(application) { private val logsetsAll: MutableList = mutableListOf() private var currentFilter: String = "" diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt index 46d251eb..b8a50375 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope @@ -39,8 +40,9 @@ import java.util.concurrent.ConcurrentHashMap import java.util.regex.PatternSyntaxException class MainViewModel( + application: Application, private val dataSource: MainDataSource -) : BaseViewModel() { +) : BaseViewModel(application) { private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default @@ -759,11 +761,11 @@ class MainViewModel( } // ---------- Factory ---------- - class Factory(private val dataSource: MainDataSource) : ViewModelProvider.Factory { + class Factory(private val application: Application, private val dataSource: MainDataSource) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { - return MainViewModel(dataSource) as T + return MainViewModel(application, dataSource) as T } throw IllegalArgumentException("Unknown ViewModel class") } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/PerAppProxyViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/PerAppProxyViewModel.kt index da31a4a6..da2faf75 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/PerAppProxyViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/PerAppProxyViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import android.content.Context import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.ANG_PACKAGE @@ -23,7 +24,7 @@ import java.text.Collator * ViewModel for PerAppProxy screen. * Holds all UI state and business logic. */ -class PerAppProxyViewModel : BaseViewModel() { +class PerAppProxyViewModel(application: Application) : BaseViewModel(application) { // Blacklist (apps to be proxied or bypassed) private val _blacklist = MutableStateFlow(loadBlacklist()) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/RoutingSettingsViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/RoutingSettingsViewModel.kt index c899f848..b35b77aa 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/RoutingSettingsViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/RoutingSettingsViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import com.v2ray.ang.dto.entities.RulesetItem import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.SettingsManager @@ -8,7 +9,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import java.util.UUID -class RoutingSettingsViewModel : BaseViewModel() { +class RoutingSettingsViewModel(application: Application) : BaseViewModel(application) { private val rulesets: MutableList = mutableListOf() private val _rulesetsFlow = MutableStateFlow>(emptyList()) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SettingsViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SettingsViewModel.kt index 5512a8d3..c98101b7 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SettingsViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SettingsViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.root.RootManager @@ -7,7 +8,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -class SettingsViewModel : BaseViewModel() { +class SettingsViewModel(application: Application) : BaseViewModel(application) { /** * Checks for root access and requests it if necessary. diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SubscriptionsViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SubscriptionsViewModel.kt index e02a0cda..2e765f30 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SubscriptionsViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/SubscriptionsViewModel.kt @@ -1,10 +1,15 @@ package com.v2ray.ang.viewmodel +import android.app.Application +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.dto.entities.SubscriptionCache import com.v2ray.ang.dto.entities.SubscriptionItem import com.v2ray.ang.handler.AngConfigManager import com.v2ray.ang.handler.MmkvManager +import com.v2ray.ang.handler.MmkvManager.rememberMmkvBool import com.v2ray.ang.handler.SettingsChangeManager import com.v2ray.ang.handler.SettingsManager import kotlinx.coroutines.Dispatchers @@ -14,7 +19,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext -class SubscriptionsViewModel : BaseViewModel() { +class SubscriptionsViewModel(application: Application) : BaseViewModel(application) { private val subscriptions: MutableList = MmkvManager.decodeSubscriptions().toMutableList() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/UserAssetViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/UserAssetViewModel.kt index 00c690e9..0ab926fa 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/UserAssetViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/UserAssetViewModel.kt @@ -1,5 +1,6 @@ package com.v2ray.ang.viewmodel +import android.app.Application import com.v2ray.ang.AppConfig import com.v2ray.ang.dto.UrlContentRequest import com.v2ray.ang.dto.entities.AssetUrlCache @@ -14,7 +15,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import java.io.File -class UserAssetViewModel : BaseViewModel() { +class UserAssetViewModel(application: Application) : BaseViewModel(application) { private val assets = mutableListOf() private val builtInGeoFiles = listOf(AppConfig.GEOSITE_DAT, AppConfig.GEOIP_DAT, AppConfig.GEOIP_ONLY_CN_PRIVATE_DAT)