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_PROXY = "proxy"
|
||||
|
||||
const val GLOBAL_CUSTOM_CONFIG = "globalCustomConfig"
|
||||
|
||||
const val REMOTE_DNS = "remoteDns"
|
||||
const val DIRECT_DNS = "directDns"
|
||||
const val ENABLE_DNS_ROUTING = "enableDnsRouting"
|
||||
|
||||
@ -108,6 +108,8 @@ object DataStore : OnPreferenceDataStoreChangeListener {
|
||||
var speedInterval by configurationStore.stringToInt(Key.SPEED_INTERVAL)
|
||||
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 directDns by configurationStore.string(Key.DIRECT_DNS) { "https://223.5.5.5/dns-query" }
|
||||
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.buildSingBoxOutboundShadowTLSBean
|
||||
import moe.matsuri.nb4a.utils.JavaUtil.gson
|
||||
import moe.matsuri.nb4a.utils.Util
|
||||
import moe.matsuri.nb4a.utils.listByLineOrComma
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
|
||||
@ -335,9 +336,7 @@ fun buildConfig(
|
||||
// internal outbound
|
||||
|
||||
currentOutbound = when (bean) {
|
||||
is ConfigBean -> SingBoxOption().apply {
|
||||
_hack_custom_config = bean.config
|
||||
}
|
||||
is ConfigBean -> CustomSingBoxOption(bean.config)
|
||||
|
||||
is ShadowTLSBean -> // before StandardV2RayBean
|
||||
buildSingBoxOutboundShadowTLSBean(bean)
|
||||
@ -367,7 +366,7 @@ fun buildConfig(
|
||||
buildSingBoxOutboundAnyTLSBean(bean)
|
||||
|
||||
else -> throw IllegalStateException("can't reach")
|
||||
} as SingBoxOption
|
||||
}
|
||||
|
||||
// internal mux
|
||||
if (!muxApplied) {
|
||||
@ -739,10 +738,12 @@ fun buildConfig(
|
||||
}
|
||||
}
|
||||
|
||||
_hack_custom_config = proxy.requireBean().customConfigJson
|
||||
_hack_custom_config = DataStore.globalCustomConfig
|
||||
}.let {
|
||||
val configMap = it.asMap()
|
||||
Util.mergeJSON(configMap, proxy.requireBean().customConfigJson)
|
||||
ConfigBuildResult(
|
||||
gson.toJson(it.asMap()),
|
||||
gson.toJson(configMap),
|
||||
externalIndexMap,
|
||||
proxy.id,
|
||||
trafficMap,
|
||||
|
||||
@ -23,6 +23,7 @@ import io.nekohasekai.sagernet.databinding.LayoutBackupBinding
|
||||
import io.nekohasekai.sagernet.databinding.LayoutImportBinding
|
||||
import io.nekohasekai.sagernet.databinding.LayoutProgressBinding
|
||||
import io.nekohasekai.sagernet.ktx.*
|
||||
import kotlinx.coroutines.delay
|
||||
import moe.matsuri.nb4a.utils.Util
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
@ -34,33 +35,53 @@ class BackupFragment : NamedFragment(R.layout.layout_backup) {
|
||||
override fun name0() = app.getString(R.string.backup)
|
||||
|
||||
var content = ""
|
||||
private val exportSettings = registerForActivityResult(ActivityResultContracts.CreateDocument()) { data ->
|
||||
if (data != null) {
|
||||
runOnDefaultDispatcher {
|
||||
try {
|
||||
requireActivity().contentResolver.openOutputStream(
|
||||
data
|
||||
)!!.bufferedWriter().use {
|
||||
it.write(content)
|
||||
}
|
||||
onMainDispatcher {
|
||||
snackbar(getString(R.string.action_export_msg)).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logs.w(e)
|
||||
onMainDispatcher {
|
||||
snackbar(e.readableMessage).show()
|
||||
private val exportSettings =
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument()) { data ->
|
||||
if (data != null) {
|
||||
runOnDefaultDispatcher {
|
||||
try {
|
||||
requireActivity().contentResolver.openOutputStream(
|
||||
data
|
||||
)!!.bufferedWriter().use {
|
||||
it.write(content)
|
||||
}
|
||||
onMainDispatcher {
|
||||
snackbar(getString(R.string.action_export_msg)).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logs.w(e)
|
||||
onMainDispatcher {
|
||||
snackbar(e.readableMessage).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
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 {
|
||||
runOnDefaultDispatcher {
|
||||
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 globalCustomConfig: EditConfigPreference
|
||||
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@ -77,6 +80,8 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
||||
|
||||
val logLevel = findPreference<LongClickListPreference>(Key.LOG_LEVEL)!!
|
||||
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.setOnPreferenceChangeListener { _, _ ->
|
||||
@ -162,7 +167,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
||||
resolveDestination.onPreferenceChangeListener = reloadListener
|
||||
tunImplementation.onPreferenceChangeListener = reloadListener
|
||||
acquireWakeLock.onPreferenceChangeListener = reloadListener
|
||||
|
||||
globalCustomConfig.onPreferenceChangeListener = reloadListener
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@ -171,6 +176,9 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
||||
if (::isProxyApps.isInitialized) {
|
||||
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 io.nekohasekai.sagernet.R
|
||||
import io.nekohasekai.sagernet.databinding.LayoutToolsBinding
|
||||
import io.nekohasekai.sagernet.ktx.isExpert
|
||||
|
||||
class ToolsFragment : ToolbarFragment(R.layout.layout_tools) {
|
||||
|
||||
@ -19,8 +18,6 @@ class ToolsFragment : ToolbarFragment(R.layout.layout_tools) {
|
||||
tools.add(NetworkFragment())
|
||||
tools.add(BackupFragment())
|
||||
|
||||
if (isExpert) tools.add(DebugFragment())
|
||||
|
||||
val binding = LayoutToolsBinding.bind(view)
|
||||
binding.toolsPager.adapter = ToolsAdapter(tools)
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ class ConfigEditActivity : ThemedActivity() {
|
||||
|
||||
var dirty = false
|
||||
var key = Key.SERVER_CONFIG
|
||||
var useConfigStore = false
|
||||
|
||||
class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {
|
||||
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
||||
@ -55,6 +56,7 @@ class ConfigEditActivity : ThemedActivity() {
|
||||
|
||||
intent?.extras?.apply {
|
||||
getString("key")?.let { key = it }
|
||||
getString("useConfigStore")?.let { useConfigStore = true }
|
||||
}
|
||||
|
||||
binding = LayoutEditConfigBinding.inflate(layoutInflater)
|
||||
@ -70,7 +72,11 @@ class ConfigEditActivity : ThemedActivity() {
|
||||
binding.editor.apply {
|
||||
language = JsonLanguage()
|
||||
setHorizontallyScrolling(true)
|
||||
setTextContent(DataStore.profileCacheStore.getString(key)!!)
|
||||
if (useConfigStore) {
|
||||
setTextContent(DataStore.configurationStore.getString(key) ?: "")
|
||||
} else {
|
||||
setTextContent(DataStore.profileCacheStore.getString(key) ?: "")
|
||||
}
|
||||
addTextChangedListener {
|
||||
if (!dirty) {
|
||||
dirty = true
|
||||
@ -142,7 +148,11 @@ class ConfigEditActivity : ThemedActivity() {
|
||||
|
||||
fun saveAndExit() {
|
||||
formatText()?.let {
|
||||
DataStore.profileCacheStore.putString(key, it)
|
||||
if (useConfigStore) {
|
||||
DataStore.configurationStore.putString(key, it)
|
||||
} else {
|
||||
DataStore.profileCacheStore.putString(key, it)
|
||||
}
|
||||
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> {
|
||||
@Override
|
||||
@ -61,7 +79,12 @@ public class SingBoxOptions {
|
||||
},
|
||||
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()) {
|
||||
Util.INSTANCE.mergeMap(map, src._hack_config_map);
|
||||
}
|
||||
|
||||
@ -4,8 +4,10 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import io.nekohasekai.sagernet.Key
|
||||
import io.nekohasekai.sagernet.R
|
||||
import io.nekohasekai.sagernet.database.DataStore
|
||||
import io.nekohasekai.sagernet.ktx.Logs
|
||||
import io.nekohasekai.sagernet.ktx.app
|
||||
import io.nekohasekai.sagernet.ui.profile.ConfigEditActivity
|
||||
|
||||
@ -26,9 +28,27 @@ class EditConfigPreference : Preference {
|
||||
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 {
|
||||
val config = DataStore.serverConfig
|
||||
return if (DataStore.serverConfig.isBlank()) {
|
||||
val config =
|
||||
(if (useConfigStore) DataStore.configurationStore.getString(configKey) else DataStore.serverConfig)
|
||||
?: ""
|
||||
return if (config.isBlank()) {
|
||||
return app.resources.getString(androidx.preference.R.string.not_set)
|
||||
} else {
|
||||
app.resources.getString(R.string.lines, config.split('\n').size)
|
||||
|
||||
@ -134,12 +134,12 @@ object Util {
|
||||
} else if (v is List<*>) {
|
||||
if (k.startsWith("+")) { // prepend
|
||||
val dstKey = k.removePrefix("+")
|
||||
var currentList = (dst[dstKey] as List<*>).toMutableList()
|
||||
var currentList = (dst[dstKey] as? List<*>)?.toMutableList() ?: mutableListOf()
|
||||
currentList = (v + currentList).toMutableList()
|
||||
dst[dstKey] = currentList
|
||||
} else if (k.endsWith("+")) { // append
|
||||
val dstKey = k.removeSuffix("+")
|
||||
var currentList = (dst[dstKey] as List<*>).toMutableList()
|
||||
var currentList = (dst[dstKey] as? List<*>)?.toMutableList() ?: mutableListOf()
|
||||
currentList = (currentList + v).toMutableList()
|
||||
dst[dstKey] = currentList
|
||||
} else {
|
||||
|
||||
@ -5,6 +5,14 @@
|
||||
android:orientation="vertical"
|
||||
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
|
||||
android:id="@+id/backup_configurations"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@ -492,4 +492,5 @@
|
||||
<string name="update_dialog_title">发现新版本</string>
|
||||
<string name="update_dialog_message">当前版本:%1$s\n可升级版本:%2$s\n是否前往下载?</string>
|
||||
<string name="check_update_no">检查成功,但没有更新。</string>
|
||||
<string name="reset_settings">恢复默认设置</string>
|
||||
</resources>
|
||||
@ -572,4 +572,5 @@
|
||||
<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="check_update_no">Check successful, but no updates.</string>
|
||||
<string name="reset_settings">Restore default settings</string>
|
||||
</resources>
|
||||
@ -89,6 +89,11 @@
|
||||
app:key="logLevel"
|
||||
app:title="@string/log_level"
|
||||
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 app:title="@string/cag_route">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user