mirror of
https://github.com/MatsuriDayo/NekoBoxForAndroid.git
synced 2025-12-18 22:20:06 +08:00
dev global custom config
This commit is contained in:
parent
7c46729c83
commit
835fcca7b9
@ -16,6 +16,8 @@ object Key {
|
|||||||
const val MODE_VPN = "vpn"
|
const val MODE_VPN = "vpn"
|
||||||
const val MODE_PROXY = "proxy"
|
const val MODE_PROXY = "proxy"
|
||||||
|
|
||||||
|
const val GLOBAL_CUSTOM_CONFIG = "globalCustomConfig"
|
||||||
|
|
||||||
const val REMOTE_DNS = "remoteDns"
|
const val REMOTE_DNS = "remoteDns"
|
||||||
const val DIRECT_DNS = "directDns"
|
const val DIRECT_DNS = "directDns"
|
||||||
const val ENABLE_DNS_ROUTING = "enableDnsRouting"
|
const val ENABLE_DNS_ROUTING = "enableDnsRouting"
|
||||||
|
|||||||
@ -108,6 +108,8 @@ object DataStore : OnPreferenceDataStoreChangeListener {
|
|||||||
var speedInterval by configurationStore.stringToInt(Key.SPEED_INTERVAL)
|
var speedInterval by configurationStore.stringToInt(Key.SPEED_INTERVAL)
|
||||||
var showGroupInNotification by configurationStore.boolean("showGroupInNotification")
|
var showGroupInNotification by configurationStore.boolean("showGroupInNotification")
|
||||||
|
|
||||||
|
var globalCustomConfig by configurationStore.string(Key.GLOBAL_CUSTOM_CONFIG) { "" }
|
||||||
|
|
||||||
var remoteDns by configurationStore.string(Key.REMOTE_DNS) { "https://dns.google/dns-query" }
|
var remoteDns by configurationStore.string(Key.REMOTE_DNS) { "https://dns.google/dns-query" }
|
||||||
var directDns by configurationStore.string(Key.DIRECT_DNS) { "https://223.5.5.5/dns-query" }
|
var directDns by configurationStore.string(Key.DIRECT_DNS) { "https://223.5.5.5/dns-query" }
|
||||||
var enableDnsRouting by configurationStore.boolean(Key.ENABLE_DNS_ROUTING) { true }
|
var enableDnsRouting by configurationStore.boolean(Key.ENABLE_DNS_ROUTING) { true }
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import moe.matsuri.nb4a.proxy.config.ConfigBean
|
|||||||
import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean
|
import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean
|
||||||
import moe.matsuri.nb4a.proxy.shadowtls.buildSingBoxOutboundShadowTLSBean
|
import moe.matsuri.nb4a.proxy.shadowtls.buildSingBoxOutboundShadowTLSBean
|
||||||
import moe.matsuri.nb4a.utils.JavaUtil.gson
|
import moe.matsuri.nb4a.utils.JavaUtil.gson
|
||||||
|
import moe.matsuri.nb4a.utils.Util
|
||||||
import moe.matsuri.nb4a.utils.listByLineOrComma
|
import moe.matsuri.nb4a.utils.listByLineOrComma
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
|
||||||
@ -335,9 +336,7 @@ fun buildConfig(
|
|||||||
// internal outbound
|
// internal outbound
|
||||||
|
|
||||||
currentOutbound = when (bean) {
|
currentOutbound = when (bean) {
|
||||||
is ConfigBean -> SingBoxOption().apply {
|
is ConfigBean -> CustomSingBoxOption(bean.config)
|
||||||
_hack_custom_config = bean.config
|
|
||||||
}
|
|
||||||
|
|
||||||
is ShadowTLSBean -> // before StandardV2RayBean
|
is ShadowTLSBean -> // before StandardV2RayBean
|
||||||
buildSingBoxOutboundShadowTLSBean(bean)
|
buildSingBoxOutboundShadowTLSBean(bean)
|
||||||
@ -367,7 +366,7 @@ fun buildConfig(
|
|||||||
buildSingBoxOutboundAnyTLSBean(bean)
|
buildSingBoxOutboundAnyTLSBean(bean)
|
||||||
|
|
||||||
else -> throw IllegalStateException("can't reach")
|
else -> throw IllegalStateException("can't reach")
|
||||||
} as SingBoxOption
|
}
|
||||||
|
|
||||||
// internal mux
|
// internal mux
|
||||||
if (!muxApplied) {
|
if (!muxApplied) {
|
||||||
@ -739,10 +738,12 @@ fun buildConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_hack_custom_config = proxy.requireBean().customConfigJson
|
_hack_custom_config = DataStore.globalCustomConfig
|
||||||
}.let {
|
}.let {
|
||||||
|
val configMap = it.asMap()
|
||||||
|
Util.mergeJSON(configMap, proxy.requireBean().customConfigJson)
|
||||||
ConfigBuildResult(
|
ConfigBuildResult(
|
||||||
gson.toJson(it.asMap()),
|
gson.toJson(configMap),
|
||||||
externalIndexMap,
|
externalIndexMap,
|
||||||
proxy.id,
|
proxy.id,
|
||||||
trafficMap,
|
trafficMap,
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import io.nekohasekai.sagernet.databinding.LayoutBackupBinding
|
|||||||
import io.nekohasekai.sagernet.databinding.LayoutImportBinding
|
import io.nekohasekai.sagernet.databinding.LayoutImportBinding
|
||||||
import io.nekohasekai.sagernet.databinding.LayoutProgressBinding
|
import io.nekohasekai.sagernet.databinding.LayoutProgressBinding
|
||||||
import io.nekohasekai.sagernet.ktx.*
|
import io.nekohasekai.sagernet.ktx.*
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import moe.matsuri.nb4a.utils.Util
|
import moe.matsuri.nb4a.utils.Util
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@ -34,33 +35,53 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) {
|
|||||||
override fun name0() = app.getString(R.string.backup)
|
override fun name0() = app.getString(R.string.backup)
|
||||||
|
|
||||||
var content = ""
|
var content = ""
|
||||||
private val exportSettings = registerForActivityResult(ActivityResultContracts.CreateDocument()) { data ->
|
private val exportSettings =
|
||||||
if (data != null) {
|
registerForActivityResult(ActivityResultContracts.CreateDocument()) { data ->
|
||||||
runOnDefaultDispatcher {
|
if (data != null) {
|
||||||
try {
|
runOnDefaultDispatcher {
|
||||||
requireActivity().contentResolver.openOutputStream(
|
try {
|
||||||
data
|
requireActivity().contentResolver.openOutputStream(
|
||||||
)!!.bufferedWriter().use {
|
data
|
||||||
it.write(content)
|
)!!.bufferedWriter().use {
|
||||||
}
|
it.write(content)
|
||||||
onMainDispatcher {
|
}
|
||||||
snackbar(getString(R.string.action_export_msg)).show()
|
onMainDispatcher {
|
||||||
}
|
snackbar(getString(R.string.action_export_msg)).show()
|
||||||
} catch (e: Exception) {
|
}
|
||||||
Logs.w(e)
|
} catch (e: Exception) {
|
||||||
onMainDispatcher {
|
Logs.w(e)
|
||||||
snackbar(e.readableMessage).show()
|
onMainDispatcher {
|
||||||
|
snackbar(e.readableMessage).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val binding = LayoutBackupBinding.bind(view)
|
val binding = LayoutBackupBinding.bind(view)
|
||||||
|
|
||||||
|
binding.resetSettings.setOnClickListener {
|
||||||
|
MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm)
|
||||||
|
.setMessage(R.string.reset_settings)
|
||||||
|
.setNegativeButton(R.string.no, null)
|
||||||
|
.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
|
runOnDefaultDispatcher {
|
||||||
|
DataStore.configurationStore.reset()
|
||||||
|
delay(500)
|
||||||
|
runOnMainDispatcher {
|
||||||
|
ProcessPhoenix.triggerRebirth(
|
||||||
|
requireContext(),
|
||||||
|
Intent(requireContext(), MainActivity::class.java)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
binding.actionExport.setOnClickListener {
|
binding.actionExport.setOnClickListener {
|
||||||
runOnDefaultDispatcher {
|
runOnDefaultDispatcher {
|
||||||
content = doBackup(
|
content = doBackup(
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
package io.nekohasekai.sagernet.ui
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import io.nekohasekai.sagernet.R
|
|
||||||
import io.nekohasekai.sagernet.database.DataStore
|
|
||||||
import io.nekohasekai.sagernet.databinding.LayoutDebugBinding
|
|
||||||
import io.nekohasekai.sagernet.ktx.snackbar
|
|
||||||
|
|
||||||
class DebugFragment : NamedFragment(R.layout.layout_debug) {
|
|
||||||
|
|
||||||
override fun name0() = "Debug"
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
val binding = LayoutDebugBinding.bind(view)
|
|
||||||
|
|
||||||
binding.debugCrash.setOnClickListener {
|
|
||||||
error("test crash")
|
|
||||||
}
|
|
||||||
binding.resetSettings.setOnClickListener {
|
|
||||||
DataStore.configurationStore.reset()
|
|
||||||
snackbar("Cleared").show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -22,6 +22,9 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
|
|
||||||
private lateinit var isProxyApps: SwitchPreference
|
private lateinit var isProxyApps: SwitchPreference
|
||||||
|
|
||||||
|
private lateinit var globalCustomConfig: EditConfigPreference
|
||||||
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
@ -77,6 +80,8 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
|
|
||||||
val logLevel = findPreference<LongClickListPreference>(Key.LOG_LEVEL)!!
|
val logLevel = findPreference<LongClickListPreference>(Key.LOG_LEVEL)!!
|
||||||
val mtu = findPreference<MTUPreference>(Key.MTU)!!
|
val mtu = findPreference<MTUPreference>(Key.MTU)!!
|
||||||
|
globalCustomConfig = findPreference(Key.GLOBAL_CUSTOM_CONFIG)!!
|
||||||
|
globalCustomConfig.useConfigStore(Key.GLOBAL_CUSTOM_CONFIG)
|
||||||
|
|
||||||
logLevel.dialogLayoutResource = R.layout.layout_loglevel_help
|
logLevel.dialogLayoutResource = R.layout.layout_loglevel_help
|
||||||
logLevel.setOnPreferenceChangeListener { _, _ ->
|
logLevel.setOnPreferenceChangeListener { _, _ ->
|
||||||
@ -162,7 +167,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
resolveDestination.onPreferenceChangeListener = reloadListener
|
resolveDestination.onPreferenceChangeListener = reloadListener
|
||||||
tunImplementation.onPreferenceChangeListener = reloadListener
|
tunImplementation.onPreferenceChangeListener = reloadListener
|
||||||
acquireWakeLock.onPreferenceChangeListener = reloadListener
|
acquireWakeLock.onPreferenceChangeListener = reloadListener
|
||||||
|
globalCustomConfig.onPreferenceChangeListener = reloadListener
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -171,6 +176,9 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
if (::isProxyApps.isInitialized) {
|
if (::isProxyApps.isInitialized) {
|
||||||
isProxyApps.isChecked = DataStore.proxyApps
|
isProxyApps.isChecked = DataStore.proxyApps
|
||||||
}
|
}
|
||||||
|
if (::globalCustomConfig.isInitialized) {
|
||||||
|
globalCustomConfig.notifyChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -7,7 +7,6 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
|||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import io.nekohasekai.sagernet.R
|
import io.nekohasekai.sagernet.R
|
||||||
import io.nekohasekai.sagernet.databinding.LayoutToolsBinding
|
import io.nekohasekai.sagernet.databinding.LayoutToolsBinding
|
||||||
import io.nekohasekai.sagernet.ktx.isExpert
|
|
||||||
|
|
||||||
class ToolsFragment : ToolbarFragment(R.layout.layout_tools) {
|
class ToolsFragment : ToolbarFragment(R.layout.layout_tools) {
|
||||||
|
|
||||||
@ -19,8 +18,6 @@ class ToolsFragment : ToolbarFragment(R.layout.layout_tools) {
|
|||||||
tools.add(NetworkFragment())
|
tools.add(NetworkFragment())
|
||||||
tools.add(BackupFragment())
|
tools.add(BackupFragment())
|
||||||
|
|
||||||
if (isExpert) tools.add(DebugFragment())
|
|
||||||
|
|
||||||
val binding = LayoutToolsBinding.bind(view)
|
val binding = LayoutToolsBinding.bind(view)
|
||||||
binding.toolsPager.adapter = ToolsAdapter(tools)
|
binding.toolsPager.adapter = ToolsAdapter(tools)
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ class ConfigEditActivity : ThemedActivity() {
|
|||||||
|
|
||||||
var dirty = false
|
var dirty = false
|
||||||
var key = Key.SERVER_CONFIG
|
var key = Key.SERVER_CONFIG
|
||||||
|
var useConfigStore = false
|
||||||
|
|
||||||
class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {
|
class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {
|
||||||
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
||||||
@ -55,6 +56,7 @@ class ConfigEditActivity : ThemedActivity() {
|
|||||||
|
|
||||||
intent?.extras?.apply {
|
intent?.extras?.apply {
|
||||||
getString("key")?.let { key = it }
|
getString("key")?.let { key = it }
|
||||||
|
getString("useConfigStore")?.let { useConfigStore = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding = LayoutEditConfigBinding.inflate(layoutInflater)
|
binding = LayoutEditConfigBinding.inflate(layoutInflater)
|
||||||
@ -70,7 +72,11 @@ class ConfigEditActivity : ThemedActivity() {
|
|||||||
binding.editor.apply {
|
binding.editor.apply {
|
||||||
language = JsonLanguage()
|
language = JsonLanguage()
|
||||||
setHorizontallyScrolling(true)
|
setHorizontallyScrolling(true)
|
||||||
setTextContent(DataStore.profileCacheStore.getString(key)!!)
|
if (useConfigStore) {
|
||||||
|
setTextContent(DataStore.configurationStore.getString(key) ?: "")
|
||||||
|
} else {
|
||||||
|
setTextContent(DataStore.profileCacheStore.getString(key) ?: "")
|
||||||
|
}
|
||||||
addTextChangedListener {
|
addTextChangedListener {
|
||||||
if (!dirty) {
|
if (!dirty) {
|
||||||
dirty = true
|
dirty = true
|
||||||
@ -142,7 +148,11 @@ class ConfigEditActivity : ThemedActivity() {
|
|||||||
|
|
||||||
fun saveAndExit() {
|
fun saveAndExit() {
|
||||||
formatText()?.let {
|
formatText()?.let {
|
||||||
DataStore.profileCacheStore.putString(key, it)
|
if (useConfigStore) {
|
||||||
|
DataStore.configurationStore.putString(key, it)
|
||||||
|
} else {
|
||||||
|
DataStore.profileCacheStore.putString(key, it)
|
||||||
|
}
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,6 +47,24 @@ public class SingBoxOptions {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class CustomSingBoxOption extends SingBoxOption {
|
||||||
|
|
||||||
|
public transient String config;
|
||||||
|
|
||||||
|
public CustomSingBoxOption(String config) {
|
||||||
|
super();
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getBasicMap() {
|
||||||
|
Map<String, Object> map = gsonSingbox.fromJson(config, Map.class);
|
||||||
|
if (map == null) {
|
||||||
|
map = new HashMap<>();
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 自定义序列化器
|
// 自定义序列化器
|
||||||
public static class SingBoxOptionSerializer implements JsonSerializer<SingBoxOption> {
|
public static class SingBoxOptionSerializer implements JsonSerializer<SingBoxOption> {
|
||||||
@Override
|
@Override
|
||||||
@ -61,7 +79,12 @@ public class SingBoxOptions {
|
|||||||
},
|
},
|
||||||
TypeToken.get(src.getClass())
|
TypeToken.get(src.getClass())
|
||||||
);
|
);
|
||||||
Map<String, Object> map = gsonSingbox.fromJson(((TypeAdapter<SingBoxOption>) delegate).toJson(src), Map.class);
|
Map<String, Object> map;
|
||||||
|
if (src instanceof CustomSingBoxOption) {
|
||||||
|
map = ((CustomSingBoxOption) src).getBasicMap();
|
||||||
|
} else {
|
||||||
|
map = gsonSingbox.fromJson(((TypeAdapter<SingBoxOption>) delegate).toJson(src), Map.class);
|
||||||
|
}
|
||||||
if (src._hack_config_map != null && !src._hack_config_map.isEmpty()) {
|
if (src._hack_config_map != null && !src._hack_config_map.isEmpty()) {
|
||||||
Util.INSTANCE.mergeMap(map, src._hack_config_map);
|
Util.INSTANCE.mergeMap(map, src._hack_config_map);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
import io.nekohasekai.sagernet.Key
|
||||||
import io.nekohasekai.sagernet.R
|
import io.nekohasekai.sagernet.R
|
||||||
import io.nekohasekai.sagernet.database.DataStore
|
import io.nekohasekai.sagernet.database.DataStore
|
||||||
|
import io.nekohasekai.sagernet.ktx.Logs
|
||||||
import io.nekohasekai.sagernet.ktx.app
|
import io.nekohasekai.sagernet.ktx.app
|
||||||
import io.nekohasekai.sagernet.ui.profile.ConfigEditActivity
|
import io.nekohasekai.sagernet.ui.profile.ConfigEditActivity
|
||||||
|
|
||||||
@ -26,9 +28,27 @@ class EditConfigPreference : Preference {
|
|||||||
intent = Intent(context, ConfigEditActivity::class.java)
|
intent = Intent(context, ConfigEditActivity::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var configKey = Key.SERVER_CONFIG
|
||||||
|
var useConfigStore = false
|
||||||
|
|
||||||
|
fun useConfigStore(key: String) {
|
||||||
|
try {
|
||||||
|
this.configKey = key
|
||||||
|
useConfigStore = true
|
||||||
|
intent = intent!!.apply {
|
||||||
|
putExtra("useConfigStore", "1")
|
||||||
|
putExtra("key", key)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logs.w(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSummary(): CharSequence {
|
override fun getSummary(): CharSequence {
|
||||||
val config = DataStore.serverConfig
|
val config =
|
||||||
return if (DataStore.serverConfig.isBlank()) {
|
(if (useConfigStore) DataStore.configurationStore.getString(configKey) else DataStore.serverConfig)
|
||||||
|
?: ""
|
||||||
|
return if (config.isBlank()) {
|
||||||
return app.resources.getString(androidx.preference.R.string.not_set)
|
return app.resources.getString(androidx.preference.R.string.not_set)
|
||||||
} else {
|
} else {
|
||||||
app.resources.getString(R.string.lines, config.split('\n').size)
|
app.resources.getString(R.string.lines, config.split('\n').size)
|
||||||
|
|||||||
@ -134,12 +134,12 @@ object Util {
|
|||||||
} else if (v is List<*>) {
|
} else if (v is List<*>) {
|
||||||
if (k.startsWith("+")) { // prepend
|
if (k.startsWith("+")) { // prepend
|
||||||
val dstKey = k.removePrefix("+")
|
val dstKey = k.removePrefix("+")
|
||||||
var currentList = (dst[dstKey] as List<*>).toMutableList()
|
var currentList = (dst[dstKey] as? List<*>)?.toMutableList() ?: mutableListOf()
|
||||||
currentList = (v + currentList).toMutableList()
|
currentList = (v + currentList).toMutableList()
|
||||||
dst[dstKey] = currentList
|
dst[dstKey] = currentList
|
||||||
} else if (k.endsWith("+")) { // append
|
} else if (k.endsWith("+")) { // append
|
||||||
val dstKey = k.removeSuffix("+")
|
val dstKey = k.removeSuffix("+")
|
||||||
var currentList = (dst[dstKey] as List<*>).toMutableList()
|
var currentList = (dst[dstKey] as? List<*>)?.toMutableList() ?: mutableListOf()
|
||||||
currentList = (currentList + v).toMutableList()
|
currentList = (currentList + v).toMutableList()
|
||||||
dst[dstKey] = currentList
|
dst[dstKey] = currentList
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -5,6 +5,14 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/reset_settings"
|
||||||
|
style="@style/Widget.AppCompat.Button.Colored"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/reset_settings"
|
||||||
|
android:textColor="?whiteOrTextPrimary" />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/backup_configurations"
|
android:id="@+id/backup_configurations"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@ -492,4 +492,5 @@
|
|||||||
<string name="update_dialog_title">发现新版本</string>
|
<string name="update_dialog_title">发现新版本</string>
|
||||||
<string name="update_dialog_message">当前版本:%1$s\n可升级版本:%2$s\n是否前往下载?</string>
|
<string name="update_dialog_message">当前版本:%1$s\n可升级版本:%2$s\n是否前往下载?</string>
|
||||||
<string name="check_update_no">检查成功,但没有更新。</string>
|
<string name="check_update_no">检查成功,但没有更新。</string>
|
||||||
|
<string name="reset_settings">恢复默认设置</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -572,4 +572,5 @@
|
|||||||
<string name="update_dialog_title">New version available</string>
|
<string name="update_dialog_title">New version available</string>
|
||||||
<string name="update_dialog_message">Current version: %1$s\nAvailable version: %2$s\nDo you want to download it?</string>
|
<string name="update_dialog_message">Current version: %1$s\nAvailable version: %2$s\nDo you want to download it?</string>
|
||||||
<string name="check_update_no">Check successful, but no updates.</string>
|
<string name="check_update_no">Check successful, but no updates.</string>
|
||||||
|
<string name="reset_settings">Restore default settings</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -89,6 +89,11 @@
|
|||||||
app:key="logLevel"
|
app:key="logLevel"
|
||||||
app:title="@string/log_level"
|
app:title="@string/log_level"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<moe.matsuri.nb4a.ui.EditConfigPreference
|
||||||
|
app:icon="@drawable/ic_baseline_layers_24"
|
||||||
|
app:key="globalCustomConfig"
|
||||||
|
app:title="@string/custom_config"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/cag_route">
|
<PreferenceCategory app:title="@string/cag_route">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user