Add migration

- Fix some compile warnings
- Add migration method
- Add vmessBean migration
This commit is contained in:
yuhan6665
2021-03-26 20:55:00 -04:00
parent 00e6314afe
commit fb017c6659
16 changed files with 173 additions and 255 deletions
@@ -3,7 +3,6 @@ package com.v2ray.ang
import android.support.multidex.MultiDexApplication
import android.support.v7.preference.PreferenceManager
import com.tencent.mmkv.MMKV
import com.v2ray.ang.util.AngConfigManager
class AngApplication : MultiDexApplication() {
companion object {
@@ -25,7 +24,6 @@ class AngApplication : MultiDexApplication() {
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
AngConfigManager.inject(this)
MMKV.initialize(this)
}
}
@@ -27,7 +27,7 @@ inline fun Context.toast(message: CharSequence): Toast = ToastCompat
show()
}
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)!!
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
const val threshold = 1000
@@ -1,199 +0,0 @@
package com.v2ray.ang.ui
import android.app.ActivityOptions
import android.app.FragmentManager
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.Toolbar
import android.util.Log
import android.view.MenuItem
import android.view.View
import com.v2ray.ang.R
abstract class BaseDrawerActivity : BaseActivity() {
companion object {
private val TAG = "BaseDrawerActivity"
}
private var mToolbar: Toolbar? = null
private var mDrawerToggle: ActionBarDrawerToggle? = null
private var mDrawerLayout: DrawerLayout? = null
private var mToolbarInitialized: Boolean = false
private var mItemToOpenWhenDrawerCloses = -1
private val backStackChangedListener = FragmentManager.OnBackStackChangedListener { updateDrawerToggle() }
private val drawerListener = object : DrawerLayout.DrawerListener {
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
mDrawerToggle!!.onDrawerSlide(drawerView, slideOffset)
}
override fun onDrawerOpened(drawerView: View) {
mDrawerToggle!!.onDrawerOpened(drawerView)
//supportActionBar!!.setTitle(R.string.app_name)
}
override fun onDrawerClosed(drawerView: View) {
mDrawerToggle!!.onDrawerClosed(drawerView)
if (mItemToOpenWhenDrawerCloses >= 0) {
val extras = ActivityOptions.makeCustomAnimation(
this@BaseDrawerActivity, R.anim.fade_in, R.anim.fade_out).toBundle()
var activityClass: Class<*>? = null
when (mItemToOpenWhenDrawerCloses) {
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
R.id.settings -> activityClass = SettingsActivity::class.java
R.id.logcat -> {
startActivity(Intent(this@BaseDrawerActivity, LogcatActivity::class.java))
return
}
R.id.donate -> {
// startActivity<InappBuyActivity>()
return
}
}
if (activityClass != null) {
startActivity(Intent(this@BaseDrawerActivity, activityClass), extras)
finish()
}
}
}
override fun onDrawerStateChanged(newState: Int) {
mDrawerToggle!!.onDrawerStateChanged(newState)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "Activity onCreate")
}
override fun onStart() {
super.onStart()
if (!mToolbarInitialized) {
throw IllegalStateException("You must run super.initializeToolbar at " + "the end of your onCreate method")
}
}
public override fun onResume() {
super.onResume()
// Whenever the fragment back stack changes, we may need to update the
// action bar toggle: only top level screens show the hamburger-like icon, inner
// screens - either Activities or fragments - show the "Up" icon instead.
fragmentManager.addOnBackStackChangedListener(backStackChangedListener)
}
public override fun onPause() {
super.onPause()
fragmentManager.removeOnBackStackChangedListener(backStackChangedListener)
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
mDrawerToggle!!.syncState()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mDrawerToggle!!.onConfigurationChanged(newConfig)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (mDrawerToggle != null && mDrawerToggle!!.onOptionsItemSelected(item)) {
return true
}
// If not handled by drawerToggle, home needs to be handled by returning to previous
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
// If the drawer is open, back will close it
if (mDrawerLayout != null && mDrawerLayout!!.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout!!.closeDrawers()
return
}
// Otherwise, it may return to the previous fragment stack
val fragmentManager = fragmentManager
if (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStack()
} else {
// Lastly, it will rely on the system behavior for back
super.onBackPressed()
}
}
private fun updateDrawerToggle() {
if (mDrawerToggle == null) {
return
}
val isRoot = fragmentManager.backStackEntryCount == 0
mDrawerToggle!!.isDrawerIndicatorEnabled = isRoot
supportActionBar!!.setDisplayShowHomeEnabled(!isRoot)
supportActionBar!!.setDisplayHomeAsUpEnabled(!isRoot)
supportActionBar!!.setHomeButtonEnabled(!isRoot)
if (isRoot) {
mDrawerToggle!!.syncState()
}
}
protected fun initializeToolbar() {
mToolbar = findViewById<View>(R.id.toolbar) as Toolbar
if (mToolbar == null) {
throw IllegalStateException("Layout is required to include a Toolbar with id " + "'toolbar'")
}
// mToolbar.inflateMenu(R.menu.main);
mDrawerLayout = findViewById<View>(R.id.drawer_layout) as DrawerLayout
if (mDrawerLayout != null) {
val navigationView = findViewById<View>(R.id.nav_view) as NavigationView
?: throw IllegalStateException("Layout requires a NavigationView " + "with id 'nav_view'")
// Create an ActionBarDrawerToggle that will handle opening/closing of the drawer:
mDrawerToggle = ActionBarDrawerToggle(this, mDrawerLayout,
mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
mDrawerLayout!!.addDrawerListener(drawerListener)
populateDrawerItems(navigationView)
setSupportActionBar(mToolbar)
updateDrawerToggle()
} else {
setSupportActionBar(mToolbar)
}
mToolbarInitialized = true
}
private fun populateDrawerItems(navigationView: NavigationView) {
navigationView.setNavigationItemSelectedListener { menuItem ->
menuItem.isChecked = true
mItemToOpenWhenDrawerCloses = menuItem.itemId
mDrawerLayout!!.closeDrawers()
true
}
if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.sub_setting)
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.settings)
}
}
}
@@ -100,6 +100,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
setupViewModelObserver()
migrateLegacy()
}
private fun setupViewModelObserver() {
@@ -129,6 +130,22 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
mainViewModel.startListenBroadcast()
}
private fun migrateLegacy() {
GlobalScope.launch(Dispatchers.IO) {
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
if (result != null) {
launch(Dispatchers.Main) {
if (result) {
toast(getString(R.string.migration_success))
mainViewModel.reloadServerList()
} else {
toast(getString(R.string.migration_fail))
}
}
}
}
}
fun startV2Ray() {
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
return
@@ -229,7 +246,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
R.id.export_all -> {
if (AngConfigManager.shareNonCustomConfigsToClipboard(mainViewModel.serverList) == 0) {
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
@@ -92,7 +92,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
}
1 -> {
if (AngConfigManager.share2Clipboard(guid) == 0) {
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
@@ -156,7 +156,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
private fun shareFullContent(guid: String) {
if (AngConfigManager.shareFullContent2Clipboard(guid) == 0) {
if (AngConfigManager.shareFullContent2Clipboard(mActivity, guid) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
@@ -138,12 +138,12 @@ class PerAppProxyActivity : BaseActivity() {
}
})
switch_per_app_proxy.setOnCheckedChangeListener { buttonView, isChecked ->
switch_per_app_proxy.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
}
switch_per_app_proxy.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
switch_bypass_apps.setOnCheckedChangeListener { buttonView, isChecked ->
switch_bypass_apps.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_BYPASS_APPS, isChecked).apply()
}
switch_bypass_apps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
@@ -42,8 +42,8 @@ class RoutingSettingsFragment : Fragment() {
return fragment
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val content = defaultSharedPreferences.getString(arguments!!.getString(routing_arg), "")
et_routing_content.text = Utils.getEditable(content!!)
@@ -23,8 +23,8 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
private var mScannerView: ZXingScannerView? = null
public override fun onCreate(state: Bundle?) {
super.onCreate(state)
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
mScannerView?.setAutoFocus(true)
@@ -79,12 +79,10 @@ class TaskerActivity : BaseActivity() {
val intent = Intent()
val remarks = lstData[position]
var blurb = ""
if (switch_start_service.isChecked) {
blurb = "Start $remarks"
val blurb = if (switch_start_service.isChecked) {
"Start $remarks"
} else {
blurb = "Stop $remarks"
"Stop $remarks"
}
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
@@ -1,11 +1,13 @@
package com.v2ray.ang.util
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.support.v7.preference.PreferenceManager
import android.text.TextUtils
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_CONFIG
import com.v2ray.ang.AppConfig.HTTPS_PROTOCOL
import com.v2ray.ang.AppConfig.HTTP_PROTOCOL
@@ -19,43 +21,139 @@ import java.net.URLDecoder
import java.util.*
object AngConfigManager {
private lateinit var app: AngApplication
private lateinit var angConfig: AngConfig
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
fun inject(app: AngApplication) {
this.app = app
loadConfig()
}
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
/**
* loading config
* Legacy loading config
*/
private fun loadConfig() {
fun migrateLegacyConfig(c: Context): Boolean? {
try {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(app)
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(c)
val context = defaultSharedPreferences.getString(ANG_CONFIG, "")
if (!TextUtils.isEmpty(context)) {
angConfig = Gson().fromJson(context, AngConfig::class.java)
} else {
angConfig = AngConfig(0, vmess = arrayListOf(AngConfig.VmessBean()), subItem = arrayListOf(AngConfig.SubItemBean()))
angConfig.index = -1
angConfig.vmess.clear()
angConfig.subItem.clear()
if (context.isNullOrBlank()) {
return null
}
val angConfig = Gson().fromJson(context, AngConfig::class.java)
for (i in angConfig.vmess.indices) {
upgradeServerVersion(angConfig.vmess[i])
}
// if (configs.subItem == null) {
// configs.subItem = arrayListOf(AngConfig.SubItemBean())
// }
copyLegacySettings(defaultSharedPreferences)
migrateVmessBean(angConfig, defaultSharedPreferences)
migrateSubItemBean(angConfig)
defaultSharedPreferences.edit().remove(ANG_CONFIG).apply()
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
private fun copyLegacySettings(sharedPreferences: SharedPreferences) {
listOf(AppConfig.PREF_MODE,
AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS,
// AppConfig.PREF_LOCAL_DNS_PORT,
// AppConfig.PREF_SOCKS_PORT,
// AppConfig.PREF_HTTP_PORT,
// AppConfig.PREF_LOGLEVEL,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
AppConfig.PREF_ROUTING_MODE,
AppConfig.PREF_V2RAY_ROUTING_AGENT,
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
AppConfig.PREF_V2RAY_ROUTING_DIRECT,).forEach { key ->
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
}
listOf(AppConfig.PREF_SPEED_ENABLED,
AppConfig.PREF_SNIFFING_ENABLED,
AppConfig.PREF_PROXY_SHARING,
AppConfig.PREF_LOCAL_DNS_ENABLED,
// AppConfig.PREF_ALLOW_INSECURE,
// AppConfig.PREF_PREFER_IPV6,
AppConfig.PREF_PER_APP_PROXY,
AppConfig.PREF_BYPASS_APPS,).forEach { key ->
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
}
settingsStorage?.encode(AppConfig.PREF_PER_APP_PROXY_SET, sharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, setOf()))
}
private fun migrateVmessBean(angConfig: AngConfig, sharedPreferences: SharedPreferences) {
angConfig.vmess.forEachIndexed { index, vmessBean ->
val type = EConfigType.fromInt(vmessBean.configType) ?: return@forEachIndexed
val config = ServerConfig.create(type)
config.remarks = vmessBean.remarks
config.subscriptionId = vmessBean.subid
if (type == EConfigType.CUSTOM) {
val jsonConfig = sharedPreferences.getString(ANG_CONFIG + vmessBean.guid, "")
val v2rayConfig = try {
Gson().fromJson(jsonConfig, V2rayConfig::class.java)
} catch (e: Exception) {
e.printStackTrace()
return@forEachIndexed
}
config.fullConfig = v2rayConfig
} else {
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
vnext.address = vmessBean.address
vnext.port = vmessBean.port
vnext.users[0].id = vmessBean.id
if (config.configType == EConfigType.VMESS) {
vnext.users[0].alterId = vmessBean.alterId
vnext.users[0].security = vmessBean.security
} else if (config.configType == EConfigType.VLESS) {
vnext.users[0].encryption = vmessBean.security
// vnext.users[0].flow = vmessBean.flow
}
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
server.address = vmessBean.address
server.port = vmessBean.port
if (config.configType == EConfigType.SHADOWSOCKS) {
server.password = vmessBean.id
server.method = vmessBean.security
} else if (config.configType == EConfigType.SOCKS) {
if (TextUtils.isEmpty(vmessBean.security) && TextUtils.isEmpty(vmessBean.id)) {
server.users = null
} else {
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = vmessBean.security
socksUsersBean.pass = vmessBean.id
server.users = listOf(socksUsersBean)
}
} else if (config.configType == EConfigType.TROJAN) {
server.password = vmessBean.id
}
}
config.outboundBean?.streamSettings?.let { streamSetting ->
val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType,
vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path)
// val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
// settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
// } else {
// vmessBean.allowInsecure.toBoolean()
// }
streamSetting.populateTlsSettings(vmessBean.streamSecurity, false,
sni)//if (vmessBean.sni.isNotBlank()) vmessBean.sni else sni)
}
}
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
if (index == angConfig.index) {
mainStorage?.encode(KEY_SELECTED_SERVER, key)
}
}
}
private fun migrateSubItemBean(angConfig: AngConfig) {
angConfig.subItem.forEach {
val subItem = SubscriptionItem()
subItem.remarks = it.remarks
subItem.url = it.url
//subItem.enabled = it.enabled
subStorage?.encode(it.id, Gson().toJson(subItem))
}
}
/**
@@ -437,14 +535,14 @@ object AngConfigManager {
/**
* share2Clipboard
*/
fun share2Clipboard(guid: String): Int {
fun share2Clipboard(context: Context, guid: String): Int {
try {
val conf = shareConfig(guid)
if (TextUtils.isEmpty(conf)) {
return -1
}
Utils.setClipboard(app.applicationContext, conf)
Utils.setClipboard(context, conf)
} catch (e: Exception) {
e.printStackTrace()
@@ -456,7 +554,7 @@ object AngConfigManager {
/**
* share2Clipboard
*/
fun shareNonCustomConfigsToClipboard(serverList: List<String>): Int {
fun shareNonCustomConfigsToClipboard(context: Context, serverList: List<String>): Int {
try {
val sb = StringBuilder()
for (guid in serverList) {
@@ -468,7 +566,7 @@ object AngConfigManager {
sb.appendLine()
}
if (sb.count() > 0) {
Utils.setClipboard(app.applicationContext, sb.toString())
Utils.setClipboard(context, sb.toString())
}
} catch (e: Exception) {
e.printStackTrace()
@@ -497,12 +595,12 @@ object AngConfigManager {
/**
* shareFullContent2Clipboard
*/
fun shareFullContent2Clipboard(guid: String?): Int {
fun shareFullContent2Clipboard(context: Context, guid: String?): Int {
try {
if (guid == null) return -1
val result = V2rayConfigUtil.getV2rayConfig(app, guid)
val result = V2rayConfigUtil.getV2rayConfig(context, guid)
if (result.status) {
Utils.setClipboard(app.applicationContext, result.content)
Utils.setClipboard(context, result.content)
} else {
return -1
}
@@ -31,7 +31,7 @@ object AppManagerUtil {
return apps
}
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.create {
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
it.onNext(loadNetworkAppList(ctx))
}
@@ -258,7 +258,7 @@ object Utils {
*/
fun isValidUrl(value: String?): Boolean {
try {
if (Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
return true
}
} catch (e: WriterException) {
@@ -60,16 +60,16 @@ object V2rayConfigUtil {
//v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
inbounds(v2rayConfig, context)
inbounds(v2rayConfig)
v2rayConfig.outbounds[0] = outbound
routing(v2rayConfig, context)
routing(v2rayConfig)
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
customLocalDns(v2rayConfig, context)
customLocalDns(v2rayConfig)
} else {
customRemoteDns(v2rayConfig, context)
customRemoteDns(v2rayConfig)
}
if (settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) != true) {
v2rayConfig.stats = null
@@ -83,7 +83,7 @@ object V2rayConfigUtil {
/**
*
*/
private fun inbounds(v2rayConfig: V2rayConfig, context: Context): Boolean {
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
try {
//val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT) ?: AppConfig.PORT_SOCKS)
//val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT) ?: AppConfig.PORT_HTTP)
@@ -115,7 +115,7 @@ object V2rayConfigUtil {
/**
* routing
*/
private fun routing(v2rayConfig: V2rayConfig, context: Context): Boolean {
private fun routing(v2rayConfig: V2rayConfig): Boolean {
try {
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT) ?: "", AppConfig.TAG_AGENT, v2rayConfig)
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT) ?: "", AppConfig.TAG_DIRECT, v2rayConfig)
@@ -233,7 +233,7 @@ object V2rayConfigUtil {
/**
* Custom Dns
*/
private fun customLocalDns(v2rayConfig: V2rayConfig, context: Context): Boolean {
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
try {
val hosts = mutableMapOf<String, String>()
val servers = ArrayList<Any>()
@@ -342,7 +342,7 @@ object V2rayConfigUtil {
/**
* Custom Remote Dns
*/
private fun customRemoteDns(v2rayConfig: V2rayConfig, context: Context): Boolean {
private fun customRemoteDns(v2rayConfig: V2rayConfig): Boolean {
try {
val servers = ArrayList<Any>()
val hosts = mutableMapOf<String, String>()
@@ -6,6 +6,8 @@
<string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">数据迁移成功!</string>
<string name="migration_fail">数据迁移失败啦!</string>
<!-- Notifications -->
<string name="notification_action_stop_v2ray">停止</string>
@@ -6,6 +6,8 @@
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增組態</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">數據遷移成功!</string>
<string name="migration_fail">數據遷移失敗啦!</string>
<!-- Notifications -->
<string name="notification_action_stop_v2ray">停止</string>
@@ -6,6 +6,8 @@
<string name="app_tile_first_use">First use of this feature, please use the app to add server</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">Data migration success!</string>
<string name="migration_fail">Data migration failed!</string>
<!-- Notifications -->
<string name="notification_action_stop_v2ray">Stop</string>