mirror of
https://github.com/MatsuriDayo/NekoBoxForAndroid.git
synced 2025-12-18 22:20:06 +08:00
feat: editorkit json
This commit is contained in:
parent
24a4e1617c
commit
0eb3c6710c
@ -30,11 +30,11 @@ dependencies {
|
||||
|
||||
implementation(fileTree("libs"))
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||
implementation("androidx.activity:activity-ktx:1.6.1")
|
||||
implementation("androidx.fragment:fragment-ktx:1.5.5")
|
||||
implementation("androidx.activity:activity-ktx:1.7.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.5.6")
|
||||
implementation("androidx.browser:browser:1.5.0")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
@ -42,15 +42,16 @@ dependencies {
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
|
||||
implementation("androidx.preference:preference-ktx:1.2.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.0")
|
||||
implementation("androidx.work:work-multiprocess:2.8.0")
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
||||
implementation("androidx.work:work-multiprocess:2.8.1")
|
||||
|
||||
implementation("com.google.android.material:material:1.8.0")
|
||||
implementation("com.google.code.gson:gson:2.8.9")
|
||||
implementation("com.google.code.gson:gson:2.9.0")
|
||||
|
||||
implementation("com.github.jenly1314:zxing-lite:2.1.1")
|
||||
implementation("com.afollestad.material-dialogs:core:3.3.0")
|
||||
implementation("com.afollestad.material-dialogs:input:3.3.0")
|
||||
implementation("com.blacksquircle.ui:editorkit:2.6.0")
|
||||
implementation("com.blacksquircle.ui:language-base:2.6.0")
|
||||
implementation("com.blacksquircle.ui:language-json:2.6.0")
|
||||
|
||||
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.3")
|
||||
implementation("org.yaml:snakeyaml:1.30")
|
||||
@ -68,11 +69,11 @@ dependencies {
|
||||
exclude(group = "com.google.guava", module = "guava")
|
||||
}
|
||||
|
||||
implementation("androidx.room:room-runtime:2.5.0")
|
||||
kapt("androidx.room:room-compiler:2.5.0")
|
||||
implementation("androidx.room:room-ktx:2.5.0")
|
||||
implementation("androidx.room:room-runtime:2.5.1")
|
||||
kapt("androidx.room:room-compiler:2.5.1")
|
||||
implementation("androidx.room:room-ktx:2.5.1")
|
||||
implementation("com.github.MatrixDev.Roomigrant:RoomigrantLib:0.3.4")
|
||||
kapt("com.github.MatrixDev.Roomigrant:RoomigrantCompiler:0.3.4")
|
||||
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
|
||||
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android, com.blacksquircle.ui.editorkit" />
|
||||
|
||||
<permission
|
||||
android:name="${applicationId}.SERVICE"
|
||||
@ -143,6 +143,9 @@
|
||||
android:name="io.nekohasekai.sagernet.ui.VpnRequestActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:taskAffinity="" />
|
||||
<activity
|
||||
android:name="io.nekohasekai.sagernet.ui.profile.ConfigEditActivity"
|
||||
android:configChanges="uiMode" />
|
||||
<activity
|
||||
android:name="io.nekohasekai.sagernet.ui.profile.SocksSettingsActivity"
|
||||
android:configChanges="uiMode" />
|
||||
@ -152,9 +155,6 @@
|
||||
<activity
|
||||
android:name="io.nekohasekai.sagernet.ui.profile.ShadowsocksSettingsActivity"
|
||||
android:configChanges="uiMode" />
|
||||
<activity
|
||||
android:name="io.nekohasekai.sagernet.ui.profile.ShadowsocksRSettingsActivity"
|
||||
android:configChanges="uiMode" />
|
||||
<activity
|
||||
android:name="io.nekohasekai.sagernet.ui.profile.VMessSettingsActivity"
|
||||
android:configChanges="uiMode" />
|
||||
|
||||
@ -91,7 +91,10 @@ object Key {
|
||||
const val SERVER_ENCRYPTION = "serverEncryption"
|
||||
const val SERVER_ALPN = "serverALPN"
|
||||
const val SERVER_CERTIFICATES = "serverCertificates"
|
||||
|
||||
const val SERVER_CONFIG = "serverConfig"
|
||||
const val SERVER_CUSTOM = "serverCustom"
|
||||
const val SERVER_CUSTOM_OUTBOUND = "serverCustomOutbound"
|
||||
|
||||
const val SERVER_SECURITY_CATEGORY = "serverSecurityCategory"
|
||||
const val SERVER_TLS_CAMOUFLAGE_CATEGORY = "serverTlsCamouflageCategory"
|
||||
|
||||
@ -232,6 +232,8 @@ object DataStore : OnPreferenceDataStoreChangeListener {
|
||||
var landingProxyTmp by profileCacheStore.stringToInt(Key.GROUP_LANDING_PROXY)
|
||||
|
||||
var serverConfig by profileCacheStore.string(Key.SERVER_CONFIG)
|
||||
var serverCustom by profileCacheStore.string(Key.SERVER_CUSTOM)
|
||||
var serverCustomOutbound by profileCacheStore.string(Key.SERVER_CUSTOM_OUTBOUND)
|
||||
|
||||
var groupName by profileCacheStore.string(Key.GROUP_NAME)
|
||||
var groupType by profileCacheStore.stringToInt(Key.GROUP_TYPE)
|
||||
|
||||
@ -101,7 +101,7 @@ fun buildConfig(
|
||||
val globalOutbounds = HashMap<Long, String>()
|
||||
val selectorNames = ArrayList<String>()
|
||||
val group = SagerDatabase.groupDao.getById(proxy.groupId)
|
||||
var optionsToMerge = ""
|
||||
val optionsToMerge = proxy.requireBean().customConfigJson ?: ""
|
||||
|
||||
fun ProxyEntity.resolveChainInternal(): MutableList<ProxyEntity> {
|
||||
val bean = requireBean()
|
||||
@ -424,9 +424,6 @@ fun buildConfig(
|
||||
if (bean.customOutboundJson.isNotBlank()) {
|
||||
mergeJSON(bean.customOutboundJson, currentOutbound)
|
||||
}
|
||||
if (index == 0 && bean.customConfigJson.isNotBlank()) {
|
||||
optionsToMerge = bean.customConfigJson
|
||||
}
|
||||
}
|
||||
|
||||
pastEntity?.requireBean()?.apply {
|
||||
|
||||
@ -6,9 +6,9 @@ import android.text.InputType
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.webkit.*
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.input.input
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.nekohasekai.sagernet.BuildConfig
|
||||
import io.nekohasekai.sagernet.R
|
||||
import io.nekohasekai.sagernet.database.DataStore
|
||||
@ -55,17 +55,18 @@ class WebviewFragment : ToolbarFragment(R.layout.layout_webview), Toolbar.OnMenu
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_set_url -> {
|
||||
MaterialDialog(requireContext()).show {
|
||||
title(R.string.set_panel_url)
|
||||
input(
|
||||
prefill = DataStore.yacdURL,
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
|
||||
) { _, str ->
|
||||
DataStore.yacdURL = str.toString()
|
||||
val view = EditText(context).apply {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
|
||||
setText(DataStore.yacdURL)
|
||||
}
|
||||
MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.set_panel_url)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
DataStore.yacdURL = view.text.toString()
|
||||
mWebView.loadUrl(DataStore.yacdURL)
|
||||
}
|
||||
positiveButton(R.string.save)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
R.id.close -> {
|
||||
mWebView.onPause()
|
||||
|
||||
@ -0,0 +1,146 @@
|
||||
package io.nekohasekai.sagernet.ui.profile
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.blacksquircle.ui.editorkit.insert
|
||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||
import com.github.shadowsocks.plugin.Empty
|
||||
import com.github.shadowsocks.plugin.fragment.AlertDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.nekohasekai.sagernet.Key
|
||||
import io.nekohasekai.sagernet.R
|
||||
import io.nekohasekai.sagernet.database.DataStore
|
||||
import io.nekohasekai.sagernet.databinding.LayoutEditConfigBinding
|
||||
import io.nekohasekai.sagernet.ktx.*
|
||||
import io.nekohasekai.sagernet.ui.ThemedActivity
|
||||
import moe.matsuri.nb4a.ui.ExtendedKeyboard
|
||||
import org.json.JSONObject
|
||||
|
||||
class ConfigEditActivity : ThemedActivity() {
|
||||
|
||||
var dirty = false
|
||||
var key = Key.SERVER_CONFIG
|
||||
|
||||
class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {
|
||||
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
||||
setTitle(R.string.unsaved_changes_prompt)
|
||||
setPositiveButton(R.string.yes) { _, _ ->
|
||||
(requireActivity() as ConfigEditActivity).saveAndExit()
|
||||
}
|
||||
setNegativeButton(R.string.no) { _, _ ->
|
||||
requireActivity().finish()
|
||||
}
|
||||
setNeutralButton(android.R.string.cancel, null)
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var binding: LayoutEditConfigBinding
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
intent?.extras?.apply {
|
||||
getString("key")?.let { key = it }
|
||||
}
|
||||
|
||||
binding = LayoutEditConfigBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
supportActionBar?.apply {
|
||||
setTitle(R.string.config_settings)
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setHomeAsUpIndicator(R.drawable.ic_navigation_close)
|
||||
}
|
||||
|
||||
// binding.editor.colorScheme = mkTheme()
|
||||
binding.editor.language = JsonLanguage()
|
||||
// binding.editor.onChangeListener = OnChangeListener {
|
||||
// config = binding.editor.text.toString()
|
||||
// if (!dirty) {
|
||||
// dirty = true
|
||||
// DataStore.dirty = true
|
||||
// }
|
||||
// }
|
||||
binding.editor.setHorizontallyScrolling(true)
|
||||
binding.actionTab.setOnClickListener {
|
||||
binding.editor.insert(binding.editor.tab())
|
||||
}
|
||||
binding.actionUndo.setOnClickListener {
|
||||
try {
|
||||
binding.editor.undo()
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
binding.actionRedo.setOnClickListener {
|
||||
try {
|
||||
binding.editor.redo()
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
binding.actionFormat.setOnClickListener {
|
||||
formatText()?.let {
|
||||
binding.editor.setTextContent(it)
|
||||
}
|
||||
}
|
||||
|
||||
val extendedKeyboard = findViewById<ExtendedKeyboard>(R.id.extended_keyboard)
|
||||
extendedKeyboard.setKeyListener { char -> binding.editor.insert(char) }
|
||||
extendedKeyboard.setHasFixedSize(true)
|
||||
extendedKeyboard.submitList("{},:_\"".map { it.toString() })
|
||||
extendedKeyboard.setBackgroundColor(getColorAttr(R.attr.primaryOrTextPrimary))
|
||||
|
||||
binding.editor.setTextContent(DataStore.profileCacheStore.getString(key)!!)
|
||||
}
|
||||
|
||||
fun formatText(): String? {
|
||||
try {
|
||||
val txt = binding.editor.text.toString()
|
||||
if (txt.isBlank()) {
|
||||
return ""
|
||||
}
|
||||
return JSONObject(txt).toStringPretty()
|
||||
} catch (e: Exception) {
|
||||
MaterialAlertDialogBuilder(this).setTitle(R.string.error_title)
|
||||
.setMessage(e.readableMessage).show()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun saveAndExit() {
|
||||
formatText()?.let {
|
||||
DataStore.profileCacheStore.putString(key, it)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (dirty) UnsavedChangesDialogFragment().apply { key() }
|
||||
.show(supportFragmentManager, null) else super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
if (!super.onSupportNavigateUp()) finish()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.profile_apply_menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_apply -> {
|
||||
saveAndExit()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@ -6,11 +6,13 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.InputType
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.activity.result.component1
|
||||
import androidx.activity.result.component2
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
@ -22,8 +24,6 @@ import androidx.preference.EditTextPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.input.input
|
||||
import com.github.shadowsocks.plugin.Empty
|
||||
import com.github.shadowsocks.plugin.fragment.AlertDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@ -217,10 +217,6 @@ abstract class ProfileSettingsActivity<T : AbstractBean>(
|
||||
preferenceManager.preferenceDataStore = DataStore.profileCacheStore
|
||||
activity.apply {
|
||||
createPreferences(savedInstanceState, rootKey)
|
||||
|
||||
if (isSubscription) {
|
||||
// findPreference<Preference>(Key.PROFILE_NAME)?.isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +233,21 @@ abstract class ProfileSettingsActivity<T : AbstractBean>(
|
||||
DataStore.profileCacheStore.registerChangeListener(activity)
|
||||
}
|
||||
|
||||
var callbackCustom: ((String) -> Unit)? = null
|
||||
var callbackCustomOutbound: ((String) -> Unit)? = null
|
||||
|
||||
val resultCallbackCustom = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { (_, _) ->
|
||||
callbackCustom?.let { it(DataStore.serverCustom) }
|
||||
}
|
||||
|
||||
val resultCallbackCustomOutbound = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { (_, _) ->
|
||||
callbackCustomOutbound?.let { it(DataStore.serverCustomOutbound) }
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.action_delete -> {
|
||||
@ -263,36 +274,30 @@ abstract class ProfileSettingsActivity<T : AbstractBean>(
|
||||
R.id.action_custom_outbound_json -> {
|
||||
activity.proxyEntity?.apply {
|
||||
val bean = requireBean()
|
||||
MaterialDialog(activity).show {
|
||||
title(R.string.custom_outbound_json)
|
||||
input(
|
||||
prefill = bean.customOutboundJson,
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE,
|
||||
allowEmpty = true
|
||||
) { _, str ->
|
||||
bean.customOutboundJson = str.toString()
|
||||
DataStore.dirty = true
|
||||
}
|
||||
positiveButton(R.string.save)
|
||||
}
|
||||
DataStore.serverCustomOutbound = bean.customOutboundJson
|
||||
callbackCustomOutbound = { bean.customOutboundJson = it }
|
||||
resultCallbackCustomOutbound.launch(
|
||||
Intent(
|
||||
requireContext(),
|
||||
ConfigEditActivity::class.java
|
||||
).apply {
|
||||
putExtra("key", Key.SERVER_CUSTOM_OUTBOUND)
|
||||
})
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.action_custom_config_json -> {
|
||||
activity.proxyEntity?.apply {
|
||||
val bean = requireBean()
|
||||
MaterialDialog(activity).show {
|
||||
title(R.string.custom_config_json)
|
||||
input(
|
||||
prefill = bean.customConfigJson,
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE,
|
||||
allowEmpty = true
|
||||
) { _, str ->
|
||||
bean.customConfigJson = str.toString()
|
||||
DataStore.dirty = true
|
||||
}
|
||||
positiveButton(R.string.save)
|
||||
}
|
||||
DataStore.serverCustom = bean.customConfigJson
|
||||
callbackCustom = { bean.customConfigJson = it }
|
||||
resultCallbackCustom.launch(
|
||||
Intent(
|
||||
requireContext(),
|
||||
ConfigEditActivity::class.java
|
||||
).apply {
|
||||
putExtra("key", Key.SERVER_CUSTOM)
|
||||
})
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package moe.matsuri.nb4a.proxy.config
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
@ -10,14 +9,13 @@ import io.nekohasekai.sagernet.R
|
||||
import io.nekohasekai.sagernet.database.DataStore
|
||||
import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener
|
||||
import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity
|
||||
import moe.matsuri.nb4a.ui.EditConfigPreference
|
||||
|
||||
class ConfigSettingActivity :
|
||||
ProfileSettingsActivity<ConfigBean>(),
|
||||
OnPreferenceDataStoreChangeListener {
|
||||
|
||||
var beanType: Int = 0
|
||||
|
||||
lateinit var configPreference: EditTextPreference
|
||||
private var beanType: Int = 0
|
||||
|
||||
override fun createEntity() = ConfigBean()
|
||||
|
||||
@ -44,24 +42,30 @@ class ConfigSettingActivity :
|
||||
if (key != Key.PROFILE_DIRTY) {
|
||||
DataStore.dirty = true
|
||||
}
|
||||
if (key == Key.SERVER_CONFIG) {
|
||||
if (::configPreference.isInitialized) {
|
||||
configPreference.text = store.getString(key, "")
|
||||
}
|
||||
} else if (key == "isOutboundOnly") {
|
||||
if (key == "isOutboundOnly") {
|
||||
beanType = if (store.getBoolean(key, false)) 1 else 0
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var editConfigPreference: EditConfigPreference
|
||||
|
||||
override fun PreferenceFragmentCompat.createPreferences(
|
||||
savedInstanceState: Bundle?,
|
||||
rootKey: String?,
|
||||
) {
|
||||
addPreferencesFromResource(R.xml.config_preferences)
|
||||
|
||||
configPreference = findPreference(Key.SERVER_CONFIG)!!
|
||||
editConfigPreference = findPreference(Key.SERVER_CONFIG)!!
|
||||
|
||||
findPreference<SwitchPreference>("isOutboundOnly")!!.isChecked = beanType == 1
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (::editConfigPreference.isInitialized) {
|
||||
editConfigPreference.notifyChanged()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package moe.matsuri.nb4a.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import io.nekohasekai.sagernet.R
|
||||
import io.nekohasekai.sagernet.database.DataStore
|
||||
import io.nekohasekai.sagernet.ktx.app
|
||||
import io.nekohasekai.sagernet.ui.profile.ConfigEditActivity
|
||||
|
||||
class EditConfigPreference : Preference {
|
||||
|
||||
constructor(
|
||||
context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int
|
||||
) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context, attrs, defStyleAttr
|
||||
)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
init {
|
||||
intent = Intent(context, ConfigEditActivity::class.java)
|
||||
}
|
||||
|
||||
override fun getSummary(): CharSequence {
|
||||
val config = DataStore.serverConfig
|
||||
return if (DataStore.serverConfig.isBlank()) {
|
||||
return app.resources.getString(androidx.preference.R.string.not_set)
|
||||
} else {
|
||||
app.resources.getString(R.string.lines, config.split('\n').size)
|
||||
}
|
||||
}
|
||||
|
||||
public override fun notifyChanged() {
|
||||
super.notifyChanged()
|
||||
}
|
||||
|
||||
}
|
||||
102
app/src/main/java/moe/matsuri/nb4a/ui/ExtendedKeyboard.kt
Normal file
102
app/src/main/java/moe/matsuri/nb4a/ui/ExtendedKeyboard.kt
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2021 Squircle IDE contributors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package moe.matsuri.nb4a.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.nekohasekai.sagernet.databinding.ItemKeyboardKeyBinding
|
||||
|
||||
class ExtendedKeyboard @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||
|
||||
private lateinit var keyAdapter: KeyAdapter
|
||||
|
||||
fun setKeyListener(keyListener: OnKeyListener) {
|
||||
keyAdapter = KeyAdapter(keyListener)
|
||||
adapter = keyAdapter
|
||||
}
|
||||
|
||||
fun submitList(keys: List<String>) {
|
||||
keyAdapter.submitList(keys)
|
||||
}
|
||||
|
||||
private class KeyAdapter(
|
||||
private val keyListener: OnKeyListener
|
||||
) : ListAdapter<String, KeyAdapter.KeyViewHolder>(diffCallback) {
|
||||
|
||||
companion object {
|
||||
private val diffCallback = object : DiffUtil.ItemCallback<String>() {
|
||||
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): KeyViewHolder {
|
||||
return KeyViewHolder.create(parent, keyListener)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: KeyViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
private class KeyViewHolder(
|
||||
private val binding: ItemKeyboardKeyBinding,
|
||||
private val keyListener: OnKeyListener
|
||||
) : ViewHolder(binding.root) {
|
||||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup, keyListener: OnKeyListener): KeyViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = ItemKeyboardKeyBinding.inflate(inflater, parent, false)
|
||||
return KeyViewHolder(binding, keyListener)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var char: String
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener {
|
||||
keyListener.onKey(char)
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(item: String) {
|
||||
char = item
|
||||
binding.itemTitle.text = char
|
||||
binding.itemTitle.setTextColor(Color.WHITE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun interface OnKeyListener {
|
||||
fun onKey(char: String)
|
||||
}
|
||||
}
|
||||
5
app/src/main/res/drawable/baseline_keyboard_tab_24.xml
Normal file
5
app/src/main/res/drawable/baseline_keyboard_tab_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M11.59,7.41L15.17,11H1v2h14.17l-3.59,3.59L13,18l6,-6 -6,-6 -1.41,1.41zM20,6v12h2V6h-2z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_redo_24.xml
Normal file
5
app/src/main/res/drawable/baseline_redo_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_undo_24.xml
Normal file
5
app/src/main/res/drawable/baseline_undo_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z"/>
|
||||
</vector>
|
||||
27
app/src/main/res/layout/item_keyboard_key.xml
Normal file
27
app/src/main/res/layout/item_keyboard_key.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2021 Squircle IDE contributors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/item_title"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="2dp"
|
||||
android:textSize="16sp"
|
||||
android:typeface="monospace"
|
||||
tools:text="{" />
|
||||
101
app/src/main/res/layout/layout_edit_config.xml
Normal file
101
app/src/main/res/layout/layout_edit_config.xml
Normal file
@ -0,0 +1,101 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
layout="@layout/layout_appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.5dp"
|
||||
android:background="@android:color/darker_gray" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<com.blacksquircle.ui.editorkit.widget.TextProcessor
|
||||
android:id="@+id/editor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@null"
|
||||
android:completionThreshold="2"
|
||||
android:importantForAutofill="no"
|
||||
android:scrollbars="none" />
|
||||
|
||||
<com.blacksquircle.ui.editorkit.widget.TextScroller
|
||||
android:id="@+id/scroller"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
app:thumbTint="?colorPrimary" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/keyboard_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="2dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_tab"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?primaryOrTextPrimary"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/baseline_keyboard_tab_24"
|
||||
app:tint="?colorOnSurface" />
|
||||
|
||||
<moe.matsuri.nb4a.ui.ExtendedKeyboard
|
||||
android:id="@+id/extended_keyboard"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorSecondaryVariant"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="6"
|
||||
tools:listitem="@layout/item_keyboard_key" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_undo"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?primaryOrTextPrimary"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/baseline_undo_24"
|
||||
app:tint="?colorOnSurface" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_redo"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?primaryOrTextPrimary"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/baseline_redo_24"
|
||||
app:tint="?colorOnSurface" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/action_format"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?primaryOrTextPrimary"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_baseline_format_align_left_24"
|
||||
app:tint="?colorOnSurface" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?primaryOrTextPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@ -11,7 +11,7 @@
|
||||
app:key="isOutboundOnly"
|
||||
app:title="@string/is_outbound_only" />
|
||||
|
||||
<EditTextPreference
|
||||
<moe.matsuri.nb4a.ui.EditConfigPreference
|
||||
app:icon="@drawable/ic_baseline_layers_24"
|
||||
app:key="serverConfig"
|
||||
app:title="@string/custom_config"
|
||||
|
||||
@ -8,13 +8,13 @@ bash buildScript/lib/assets.sh
|
||||
exit
|
||||
|
||||
#### Download "external" from Internet
|
||||
rm -rf external
|
||||
mkdir -p external
|
||||
cd external
|
||||
|
||||
echo "Downloading preferencex-android"
|
||||
wget -q -O tmp.zip https://github.com/SagerNet/preferencex-android/archive/8bdb0c6ae44f378b073c6a1c850d03d729b70ff8.zip
|
||||
unzip tmp.zip > /dev/null 2>&1
|
||||
mv preferencex-android-* preferencex
|
||||
|
||||
rm tmp.zip
|
||||
#rm -rf external
|
||||
#mkdir -p external
|
||||
#cd external
|
||||
#
|
||||
#echo "Downloading preferencex-android"
|
||||
#wget -q -O tmp.zip https://github.com/SagerNet/preferencex-android/archive/8bdb0c6ae44f378b073c6a1c850d03d729b70ff8.zip
|
||||
#unzip tmp.zip > /dev/null 2>&1
|
||||
#mv preferencex-android-* preferencex
|
||||
#
|
||||
#rm tmp.zip
|
||||
|
||||
@ -7,6 +7,6 @@ apply(from = "../repositories.gradle.kts")
|
||||
|
||||
dependencies {
|
||||
// Gradle Plugins
|
||||
implementation("com.android.tools.build:gradle:7.3.1")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
|
||||
implementation("com.android.tools.build:gradle:7.4.2")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10")
|
||||
}
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import com.android.build.gradle.AbstractAppExtension
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
import org.gradle.api.JavaVersion
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.ExtensionAware
|
||||
import org.gradle.kotlin.dsl.getByName
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import kotlin.system.exitProcess
|
||||
@ -96,6 +99,13 @@ fun Project.setupCommon() {
|
||||
isMinifyEnabled = true
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
(android as ExtensionAware).extensions.getByName<KotlinJvmOptions>("kotlinOptions").apply {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
lintOptions {
|
||||
isShowAll = true
|
||||
isCheckAllWarnings = true
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Loading…
Reference in New Issue
Block a user