Refactor BaseViewModel, which inherits from AndroidViewModel.

This commit is contained in:
2dust committed 2026-07-20 09:49:53 +08:00
1 parent 1dee6230f9
commit 1b9401fc42
13 files changed
+47 -28

No files matched your search

@@ -9,7 +9,6 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.widget.Toast import android.widget.Toast
import com.v2ray.ang.AngApplication
import com.v2ray.ang.compose.AppSnackbarManager import com.v2ray.ang.compose.AppSnackbarManager
import com.v2ray.ang.compose.ToastType import com.v2ray.ang.compose.ToastType
import com.v2ray.ang.enums.EConfigType import com.v2ray.ang.enums.EConfigType
@@ -17,9 +16,6 @@ import java.io.Serializable
import java.net.URI import java.net.URI
import java.util.Locale import java.util.Locale
val myApp: AngApplication
get() = AngApplication.application
private inline fun runOnMain(crossinline block: () -> Unit) { private inline fun runOnMain(crossinline block: () -> Unit) {
if (Looper.myLooper() == Looper.getMainLooper()) { if (Looper.myLooper() == Looper.getMainLooper()) {
block() block()
@@ -57,7 +57,7 @@ import kotlinx.coroutines.withContext
class MainActivity : HelperBaseComponentActivity() { class MainActivity : HelperBaseComponentActivity() {
private val mainViewModel: MainViewModel by viewModels { private val mainViewModel: MainViewModel by viewModels {
MainViewModel.Factory(MainRepository(application as AngApplication)) MainViewModel.Factory(application, MainRepository(application as AngApplication))
} }
private val requestVpnPermission = private val requestVpnPermission =
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import android.content.Context import android.content.Context
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AppInfo import com.v2ray.ang.dto.AppInfo
@@ -15,7 +16,7 @@ import java.text.Collator
/** /**
* ViewModel for AppPicker screen. * ViewModel for AppPicker screen.
*/ */
class AppPickerViewModel : BaseViewModel() { class AppPickerViewModel(application: Application) : BaseViewModel(application) {
private val _selectedPackages = MutableStateFlow<Set<String>>(emptySet()) private val _selectedPackages = MutableStateFlow<Set<String>>(emptySet())
val selectedPackages: StateFlow<Set<String>> = _selectedPackages.asStateFlow() val selectedPackages: StateFlow<Set<String>> = _selectedPackages.asStateFlow()
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import android.net.Uri import android.net.Uri
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
@@ -19,7 +20,7 @@ import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
class BackupViewModel : BaseViewModel() { class BackupViewModel(application: Application) : BaseViewModel(application) {
private val _webDavConfig = MutableStateFlow(MmkvManager.decodeWebDavConfig()) private val _webDavConfig = MutableStateFlow(MmkvManager.decodeWebDavConfig())
val webDavConfig: StateFlow<WebDavConfig?> = _webDavConfig.asStateFlow() val webDavConfig: StateFlow<WebDavConfig?> = _webDavConfig.asStateFlow()
@@ -1,8 +1,9 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import androidx.lifecycle.ViewModel import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope 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.toast
import com.v2ray.ang.extension.toastError import com.v2ray.ang.extension.toastError
import com.v2ray.ang.extension.toastSuccess import com.v2ray.ang.extension.toastSuccess
@@ -17,7 +18,14 @@ import kotlinx.coroutines.launch
/** /**
* Base ViewModel that encapsulates common UI event handling logic. * 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") @Suppress("PropertyName")
protected val _isLoading = MutableStateFlow(false) protected val _isLoading = MutableStateFlow(false)
@@ -31,56 +39,56 @@ abstract class BaseViewModel : ViewModel() {
* Send neutral toast event (Resource ID). * Send neutral toast event (Resource ID).
*/ */
fun toast(resId: Int) { fun toast(resId: Int) {
myApp.toast(resId) app.toast(resId)
} }
/** /**
* Send neutral toast event (String). * Send neutral toast event (String).
*/ */
fun toast(message: String) { fun toast(message: String) {
myApp.toast(message) app.toast(message)
} }
/** /**
* Send success toast event (Resource ID). * Send success toast event (Resource ID).
*/ */
fun toastSuccess(resId: Int) { fun toastSuccess(resId: Int) {
myApp.toastSuccess(resId) app.toastSuccess(resId)
} }
/** /**
* Send success toast event (String). * Send success toast event (String).
*/ */
fun toastSuccess(message: String) { fun toastSuccess(message: String) {
myApp.toastSuccess(message) app.toastSuccess(message)
} }
/** /**
* Send error toast event (Resource ID). * Send error toast event (Resource ID).
*/ */
fun toastError(resId: Int) { fun toastError(resId: Int) {
myApp.toastError(resId) app.toastError(resId)
} }
/** /**
* Send error toast event (String). * Send error toast event (String).
*/ */
fun toastError(message: String) { fun toastError(message: String) {
myApp.toastError(message) app.toastError(message)
} }
/** /**
* Get string from resource ID. * Get string from resource ID.
*/ */
fun getString(resId: Int): String { fun getString(resId: Int): String {
return myApp.getString(resId) return app.getString(resId)
} }
/** /**
* Get formatted string from resource ID. * Get formatted string from resource ID.
*/ */
fun getString(resId: Int, vararg formatArgs: Any?): String { fun getString(resId: Int, vararg formatArgs: Any?): String {
return myApp.getString(resId, *formatArgs) return app.getString(resId, *formatArgs)
} }
/** /**
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.CheckUpdateResult import com.v2ray.ang.dto.CheckUpdateResult
@@ -10,7 +11,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
class CheckUpdateViewModel : BaseViewModel() { class CheckUpdateViewModel(application: Application) : BaseViewModel(application) {
private val _checkPreRelease = MutableStateFlow( private val _checkPreRelease = MutableStateFlow(
MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false) MmkvManager.decodeSettingsBool(AppConfig.PREF_CHECK_UPDATE_PRE_RELEASE, false)
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.util.LogUtil import com.v2ray.ang.util.LogUtil
@@ -8,7 +9,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import java.io.IOException import java.io.IOException
class LogcatViewModel : BaseViewModel() { class LogcatViewModel(application: Application) : BaseViewModel(application) {
private val logsetsAll: MutableList<String> = mutableListOf() private val logsetsAll: MutableList<String> = mutableListOf()
private var currentFilter: String = "" private var currentFilter: String = ""
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -39,8 +40,9 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.regex.PatternSyntaxException import java.util.regex.PatternSyntaxException
class MainViewModel( class MainViewModel(
application: Application,
private val dataSource: MainDataSource private val dataSource: MainDataSource
) : BaseViewModel() { ) : BaseViewModel(application) {
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
@@ -759,11 +761,11 @@ class MainViewModel(
} }
// ---------- Factory ---------- // ---------- Factory ----------
class Factory(private val dataSource: MainDataSource) : ViewModelProvider.Factory { class Factory(private val application: Application, private val dataSource: MainDataSource) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) { if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(dataSource) as T return MainViewModel(application, dataSource) as T
} }
throw IllegalArgumentException("Unknown ViewModel class") throw IllegalArgumentException("Unknown ViewModel class")
} }
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import android.content.Context import android.content.Context
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE import com.v2ray.ang.AppConfig.ANG_PACKAGE
@@ -23,7 +24,7 @@ import java.text.Collator
* ViewModel for PerAppProxy screen. * ViewModel for PerAppProxy screen.
* Holds all UI state and business logic. * Holds all UI state and business logic.
*/ */
class PerAppProxyViewModel : BaseViewModel() { class PerAppProxyViewModel(application: Application) : BaseViewModel(application) {
// Blacklist (apps to be proxied or bypassed) // Blacklist (apps to be proxied or bypassed)
private val _blacklist = MutableStateFlow(loadBlacklist()) private val _blacklist = MutableStateFlow(loadBlacklist())
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import com.v2ray.ang.dto.entities.RulesetItem import com.v2ray.ang.dto.entities.RulesetItem
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.handler.SettingsManager
@@ -8,7 +9,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import java.util.UUID import java.util.UUID
class RoutingSettingsViewModel : BaseViewModel() { class RoutingSettingsViewModel(application: Application) : BaseViewModel(application) {
private val rulesets: MutableList<RulesetItem> = mutableListOf() private val rulesets: MutableList<RulesetItem> = mutableListOf()
private val _rulesetsFlow = MutableStateFlow<List<RulesetItem>>(emptyList()) private val _rulesetsFlow = MutableStateFlow<List<RulesetItem>>(emptyList())
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.root.RootManager import com.v2ray.ang.root.RootManager
@@ -7,7 +8,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class SettingsViewModel : BaseViewModel() { class SettingsViewModel(application: Application) : BaseViewModel(application) {
/** /**
* Checks for root access and requests it if necessary. * Checks for root access and requests it if necessary.
@@ -1,10 +1,15 @@
package com.v2ray.ang.viewmodel 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.R
import com.v2ray.ang.dto.entities.SubscriptionCache import com.v2ray.ang.dto.entities.SubscriptionCache
import com.v2ray.ang.dto.entities.SubscriptionItem import com.v2ray.ang.dto.entities.SubscriptionItem
import com.v2ray.ang.handler.AngConfigManager import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.MmkvManager.rememberMmkvBool
import com.v2ray.ang.handler.SettingsChangeManager import com.v2ray.ang.handler.SettingsChangeManager
import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.handler.SettingsManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -14,7 +19,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class SubscriptionsViewModel : BaseViewModel() { class SubscriptionsViewModel(application: Application) : BaseViewModel(application) {
private val subscriptions: MutableList<SubscriptionCache> = private val subscriptions: MutableList<SubscriptionCache> =
MmkvManager.decodeSubscriptions().toMutableList() MmkvManager.decodeSubscriptions().toMutableList()
@@ -1,5 +1,6 @@
package com.v2ray.ang.viewmodel package com.v2ray.ang.viewmodel
import android.app.Application
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.UrlContentRequest import com.v2ray.ang.dto.UrlContentRequest
import com.v2ray.ang.dto.entities.AssetUrlCache import com.v2ray.ang.dto.entities.AssetUrlCache
@@ -14,7 +15,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import java.io.File import java.io.File
class UserAssetViewModel : BaseViewModel() { class UserAssetViewModel(application: Application) : BaseViewModel(application) {
private val assets = mutableListOf<AssetUrlCache>() private val assets = mutableListOf<AssetUrlCache>()
private val builtInGeoFiles = listOf(AppConfig.GEOSITE_DAT, AppConfig.GEOIP_DAT, AppConfig.GEOIP_ONLY_CN_PRIVATE_DAT) private val builtInGeoFiles = listOf(AppConfig.GEOSITE_DAT, AppConfig.GEOIP_DAT, AppConfig.GEOIP_ONLY_CN_PRIVATE_DAT)