diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5f3bc8b..054a795 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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")
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 09ea468..4607b16 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="internalOnly">
-
+
+
@@ -152,9 +155,6 @@
-
diff --git a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
index e6a1e13..d84be13 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt
@@ -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"
diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
index dbb74a0..41ad25c 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
@@ -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)
diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
index ece2d99..705b53a 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt
@@ -101,7 +101,7 @@ fun buildConfig(
val globalOutbounds = HashMap()
val selectorNames = ArrayList()
val group = SagerDatabase.groupDao.getById(proxy.groupId)
- var optionsToMerge = ""
+ val optionsToMerge = proxy.requireBean().customConfigJson ?: ""
fun ProxyEntity.resolveChainInternal(): MutableList {
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 {
diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt
index 79cdebd..c2e7055 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt
@@ -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()
diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt
new file mode 100644
index 0000000..5b66d44
--- /dev/null
+++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt
@@ -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() {
+ 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(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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt
index 8b1da23..1d138eb 100644
--- a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt
+++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt
@@ -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(
preferenceManager.preferenceDataStore = DataStore.profileCacheStore
activity.apply {
createPreferences(savedInstanceState, rootKey)
-
- if (isSubscription) {
-// findPreference(Key.PROFILE_NAME)?.isEnabled = false
- }
}
}
@@ -237,6 +233,21 @@ abstract class ProfileSettingsActivity(
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(
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
}
diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt
index 8721579..7c0472b 100644
--- a/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt
+++ b/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt
@@ -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(),
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("isOutboundOnly")!!.isChecked = beanType == 1
}
+ override fun onResume() {
+ super.onResume()
+
+ if (::editConfigPreference.isInitialized) {
+ editConfigPreference.notifyChanged()
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/moe/matsuri/nb4a/ui/EditConfigPreference.kt b/app/src/main/java/moe/matsuri/nb4a/ui/EditConfigPreference.kt
new file mode 100644
index 0000000..6b2ea81
--- /dev/null
+++ b/app/src/main/java/moe/matsuri/nb4a/ui/EditConfigPreference.kt
@@ -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()
+ }
+
+}
diff --git a/app/src/main/java/moe/matsuri/nb4a/ui/ExtendedKeyboard.kt b/app/src/main/java/moe/matsuri/nb4a/ui/ExtendedKeyboard.kt
new file mode 100644
index 0000000..fabe5a0
--- /dev/null
+++ b/app/src/main/java/moe/matsuri/nb4a/ui/ExtendedKeyboard.kt
@@ -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) {
+ keyAdapter.submitList(keys)
+ }
+
+ private class KeyAdapter(
+ private val keyListener: OnKeyListener
+ ) : ListAdapter(diffCallback) {
+
+ companion object {
+ private val diffCallback = object : DiffUtil.ItemCallback() {
+ 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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/baseline_keyboard_tab_24.xml b/app/src/main/res/drawable/baseline_keyboard_tab_24.xml
new file mode 100644
index 0000000..3f7efd9
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_keyboard_tab_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_redo_24.xml b/app/src/main/res/drawable/baseline_redo_24.xml
new file mode 100644
index 0000000..31c325f
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_redo_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/baseline_undo_24.xml b/app/src/main/res/drawable/baseline_undo_24.xml
new file mode 100644
index 0000000..5d17998
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_undo_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/item_keyboard_key.xml b/app/src/main/res/layout/item_keyboard_key.xml
new file mode 100644
index 0000000..e88ac72
--- /dev/null
+++ b/app/src/main/res/layout/item_keyboard_key.xml
@@ -0,0 +1,27 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_edit_config.xml b/app/src/main/res/layout/layout_edit_config.xml
new file mode 100644
index 0000000..7b22e21
--- /dev/null
+++ b/app/src/main/res/layout/layout_edit_config.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/config_preferences.xml b/app/src/main/res/xml/config_preferences.xml
index ec2b3e7..8866cd9 100644
--- a/app/src/main/res/xml/config_preferences.xml
+++ b/app/src/main/res/xml/config_preferences.xml
@@ -11,7 +11,7 @@
app:key="isOutboundOnly"
app:title="@string/is_outbound_only" />
- /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
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 12c940b..a6aa10a 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -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")
}
diff --git a/buildSrc/src/main/kotlin/Helpers.kt b/buildSrc/src/main/kotlin/Helpers.kt
index 432f437..9d1801f 100644
--- a/buildSrc/src/main/kotlin/Helpers.kt
+++ b/buildSrc/src/main/kotlin/Helpers.kt
@@ -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("kotlinOptions").apply {
+ jvmTarget = JavaVersion.VERSION_1_8.toString()
+ }
lintOptions {
isShowAll = true
isCheckAllWarnings = true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index cade3fa..7e62b17 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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