Commit a1834e63 authored by Mygod's avatar Mygod

Merge branch 'preference-1.1' into feature-hosts

parents b89dccd7 50d09e73
...@@ -9,7 +9,6 @@ buildscript { ...@@ -9,7 +9,6 @@ buildscript {
sdkVersion = 28 sdkVersion = 28
compileSdkVersion = 28 compileSdkVersion = 28
buildToolsVersion = '28.0.3' buildToolsVersion = '28.0.3'
preferencexVersion = '1.0.0'
junitVersion = '4.12' junitVersion = '4.12'
androidTestVersion = '1.1.1' androidTestVersion = '1.1.1'
androidEspressoVersion = '3.1.1' androidEspressoVersion = '3.1.1'
......
...@@ -51,12 +51,11 @@ dependencies { ...@@ -51,12 +51,11 @@ dependencies {
api "android.arch.work:work-runtime-ktx:$workVersion" api "android.arch.work:work-runtime-ktx:$workVersion"
api "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion" api "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
api 'androidx.preference:preference:1.0.0' api 'androidx.preference:preference:1.1.0-alpha04'
api "androidx.room:room-runtime:$roomVersion" api "androidx.room:room-runtime:$roomVersion"
api 'com.crashlytics.sdk.android:crashlytics:2.9.9' api 'com.crashlytics.sdk.android:crashlytics:2.9.9'
api 'com.google.firebase:firebase-config:16.1.3' api 'com.google.firebase:firebase-config:16.1.3'
api 'com.google.firebase:firebase-core:16.0.7' api 'com.google.firebase:firebase-core:16.0.7'
api "com.takisoft.preferencex:preferencex:$preferencexVersion"
api 'dnsjava:dnsjava:2.1.8' api 'dnsjava:dnsjava:2.1.8'
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
api 'org.connectbot.jsocks:jsocks:1.0.0' api 'org.connectbot.jsocks:jsocks:1.0.0'
......
{
"formatVersion": 1,
"database": {
"version": 28,
"identityHash": "b60ecca4d684ffe73173478bffd50a17",
"entities": [
{
"tableName": "Profile",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `host` TEXT NOT NULL, `remotePort` INTEGER NOT NULL, `password` TEXT NOT NULL, `method` TEXT NOT NULL, `route` TEXT NOT NULL, `remoteDns` TEXT NOT NULL, `proxyApps` INTEGER NOT NULL, `bypass` INTEGER NOT NULL, `udpdns` INTEGER NOT NULL, `ipv6` INTEGER NOT NULL, `metered` INTEGER NOT NULL, `individual` TEXT NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `plugin` TEXT, `udpFallback` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remotePort",
"columnName": "remotePort",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "method",
"columnName": "method",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "route",
"columnName": "route",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remoteDns",
"columnName": "remoteDns",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "proxyApps",
"columnName": "proxyApps",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bypass",
"columnName": "bypass",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "udpdns",
"columnName": "udpdns",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ipv6",
"columnName": "ipv6",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "metered",
"columnName": "metered",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "individual",
"columnName": "individual",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tx",
"columnName": "tx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rx",
"columnName": "rx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userOrder",
"columnName": "userOrder",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "plugin",
"columnName": "plugin",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "udpFallback",
"columnName": "udpFallback",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "KeyValuePair",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "valueType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "BLOB",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"key"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"b60ecca4d684ffe73173478bffd50a17\")"
]
}
}
\ No newline at end of file
...@@ -46,4 +46,11 @@ class MigrationTest { ...@@ -46,4 +46,11 @@ class MigrationTest {
db.close() db.close()
privateDatabase.runMigrationsAndValidate(TEST_DB, 27, true, PrivateDatabase.Migration27) privateDatabase.runMigrationsAndValidate(TEST_DB, 27, true, PrivateDatabase.Migration27)
} }
@Test
@Throws(IOException::class)
fun migrate28() {
val db = privateDatabase.createDatabase(TEST_DB, 27)
db.close()
privateDatabase.runMigrationsAndValidate(TEST_DB, 28, true, PrivateDatabase.Migration28)
}
} }
...@@ -101,12 +101,15 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -101,12 +101,15 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private var conn: ParcelFileDescriptor? = null private var conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null private var worker: ProtectWorker? = null
private var active = false private var active = false
private var metered = false
private var underlyingNetwork: Network? = null private var underlyingNetwork: Network? = null
set(value) { set(value) {
field = value field = value
if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks) if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks)
} }
private val underlyingNetworks get() = underlyingNetwork?.let { arrayOf(it) } private val underlyingNetworks get() =
// clearing underlyingNetworks makes Android 9+ consider the network to be metered
if (Build.VERSION.SDK_INT >= 28 && metered) null else underlyingNetwork?.let { arrayOf(it) }
override fun onBind(intent: Intent) = when (intent.action) { override fun onBind(intent: Intent) = when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent) SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
...@@ -190,6 +193,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -190,6 +193,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} }
} }
metered = profile.metered
active = true // possible race condition here? active = true // possible race condition here?
if (Build.VERSION.SDK_INT >= 22) builder.setUnderlyingNetworks(underlyingNetworks) if (Build.VERSION.SDK_INT >= 22) builder.setUnderlyingNetworks(underlyingNetworks)
......
...@@ -29,14 +29,15 @@ import com.github.shadowsocks.Core.app ...@@ -29,14 +29,15 @@ import com.github.shadowsocks.Core.app
import com.github.shadowsocks.database.migration.RecreateSchemaMigration import com.github.shadowsocks.database.migration.RecreateSchemaMigration
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
@Database(entities = [Profile::class, KeyValuePair::class], version = 27) @Database(entities = [Profile::class, KeyValuePair::class], version = 28)
abstract class PrivateDatabase : RoomDatabase() { abstract class PrivateDatabase : RoomDatabase() {
companion object { companion object {
private val instance by lazy { private val instance by lazy {
Room.databaseBuilder(app, PrivateDatabase::class.java, Key.DB_PROFILE) Room.databaseBuilder(app, PrivateDatabase::class.java, Key.DB_PROFILE)
.addMigrations( .addMigrations(
Migration26, Migration26,
Migration27 Migration27,
Migration28
) )
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.allowMainThreadQueries() .allowMainThreadQueries()
...@@ -61,4 +62,8 @@ abstract class PrivateDatabase : RoomDatabase() { ...@@ -61,4 +62,8 @@ abstract class PrivateDatabase : RoomDatabase() {
override fun migrate(database: SupportSQLiteDatabase) = override fun migrate(database: SupportSQLiteDatabase) =
database.execSQL("ALTER TABLE `Profile` ADD COLUMN `udpFallback` INTEGER") database.execSQL("ALTER TABLE `Profile` ADD COLUMN `udpFallback` INTEGER")
} }
object Migration28 : Migration(27, 28) {
override fun migrate(database: SupportSQLiteDatabase) =
database.execSQL("ALTER TABLE `Profile` ADD COLUMN `metered` INTEGER NOT NULL DEFAULT 0")
}
} }
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.annotation.TargetApi
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import android.util.Base64 import android.util.Base64
...@@ -58,6 +59,8 @@ data class Profile( ...@@ -58,6 +59,8 @@ data class Profile(
var bypass: Boolean = false, var bypass: Boolean = false,
var udpdns: Boolean = false, var udpdns: Boolean = false,
var ipv6: Boolean = true, var ipv6: Boolean = true,
@TargetApi(28)
var metered: Boolean = false,
var individual: String = "", var individual: String = "",
var tx: Long = 0, var tx: Long = 0,
var rx: Long = 0, var rx: Long = 0,
...@@ -70,7 +73,7 @@ data class Profile( ...@@ -70,7 +73,7 @@ data class Profile(
) : Parcelable, Serializable { ) : Parcelable, Serializable {
companion object { companion object {
private const val TAG = "ShadowParser" private const val TAG = "ShadowParser"
private const val serialVersionUID = 0L private const val serialVersionUID = 1L
private val pattern = private val pattern =
"""(?i)ss://[-a-zA-Z0-9+&@#/%?=.~*'()|!:,;\[\]]*[-a-zA-Z0-9+&@#/%=.~*'()|\[\]]""".toRegex() """(?i)ss://[-a-zA-Z0-9+&@#/%?=.~*'()|!:,;\[\]]*[-a-zA-Z0-9+&@#/%=.~*'()|\[\]]""".toRegex()
private val userInfoPattern = "^(.+?):(.*)$".toRegex() private val userInfoPattern = "^(.+?):(.*)$".toRegex()
...@@ -156,6 +159,7 @@ data class Profile( ...@@ -156,6 +159,7 @@ data class Profile(
if (fallback) return@apply if (fallback) return@apply
remoteDns = json.optString("remote_dns", remoteDns) remoteDns = json.optString("remote_dns", remoteDns)
ipv6 = json.optBoolean("ipv6", ipv6) ipv6 = json.optBoolean("ipv6", ipv6)
metered = json.optBoolean("metered", metered)
json.optJSONObject("proxy_apps")?.also { json.optJSONObject("proxy_apps")?.also {
proxyApps = it.optBoolean("enabled", proxyApps) proxyApps = it.optBoolean("enabled", proxyApps)
bypass = it.optBoolean("bypass", bypass) bypass = it.optBoolean("bypass", bypass)
...@@ -232,6 +236,7 @@ data class Profile( ...@@ -232,6 +236,7 @@ data class Profile(
fun copyFeatureSettingsTo(profile: Profile) { fun copyFeatureSettingsTo(profile: Profile) {
profile.route = route profile.route = route
profile.ipv6 = ipv6 profile.ipv6 = ipv6
profile.metered = metered
profile.proxyApps = proxyApps profile.proxyApps = proxyApps
profile.bypass = bypass profile.bypass = bypass
profile.individual = individual profile.individual = individual
...@@ -269,6 +274,7 @@ data class Profile( ...@@ -269,6 +274,7 @@ data class Profile(
put("route", route) put("route", route)
put("remote_dns", remoteDns) put("remote_dns", remoteDns)
put("ipv6", ipv6) put("ipv6", ipv6)
put("metered", metered)
put("proxy_apps", JSONObject().apply { put("proxy_apps", JSONObject().apply {
put("enabled", proxyApps) put("enabled", proxyApps)
if (proxyApps) { if (proxyApps) {
...@@ -295,6 +301,7 @@ data class Profile( ...@@ -295,6 +301,7 @@ data class Profile(
DataStore.bypass = bypass DataStore.bypass = bypass
DataStore.privateStore.putBoolean(Key.udpdns, udpdns) DataStore.privateStore.putBoolean(Key.udpdns, udpdns)
DataStore.privateStore.putBoolean(Key.ipv6, ipv6) DataStore.privateStore.putBoolean(Key.ipv6, ipv6)
DataStore.privateStore.putBoolean(Key.metered, metered)
DataStore.individual = individual DataStore.individual = individual
DataStore.plugin = plugin ?: "" DataStore.plugin = plugin ?: ""
DataStore.udpFallback = udpFallback DataStore.udpFallback = udpFallback
...@@ -315,6 +322,7 @@ data class Profile( ...@@ -315,6 +322,7 @@ data class Profile(
bypass = DataStore.bypass bypass = DataStore.bypass
udpdns = DataStore.privateStore.getBoolean(Key.udpdns, false) udpdns = DataStore.privateStore.getBoolean(Key.udpdns, false)
ipv6 = DataStore.privateStore.getBoolean(Key.ipv6, false) ipv6 = DataStore.privateStore.getBoolean(Key.ipv6, false)
metered = DataStore.privateStore.getBoolean(Key.metered, false)
individual = DataStore.individual individual = DataStore.individual
plugin = DataStore.plugin plugin = DataStore.plugin
udpFallback = DataStore.udpFallback udpFallback = DataStore.udpFallback
......
...@@ -42,7 +42,7 @@ object DataStore : OnPreferenceDataStoreChangeListener { ...@@ -42,7 +42,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
publicStore.registerChangeListener(this) publicStore.registerChangeListener(this)
} }
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) { override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
when (key) { when (key) {
Key.id -> if (DataStore.directBootAware) DirectBoot.update() Key.id -> if (DataStore.directBootAware) DirectBoot.update()
} }
......
...@@ -23,5 +23,5 @@ package com.github.shadowsocks.preference ...@@ -23,5 +23,5 @@ package com.github.shadowsocks.preference
import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceDataStore
interface OnPreferenceDataStoreChangeListener { interface OnPreferenceDataStoreChangeListener {
fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String)
} }
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.preference
import android.text.InputFilter
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.preference.EditTextPreference
object PortPreferenceListener : EditTextPreference.OnBindEditTextListener {
private val portLengthFilter = arrayOf(InputFilter.LengthFilter(5))
override fun onBindEditText(editText: EditText) {
editText.inputType = EditorInfo.TYPE_CLASS_NUMBER
editText.filters = portLengthFilter
editText.setSingleLine()
}
}
...@@ -50,6 +50,7 @@ object Key { ...@@ -50,6 +50,7 @@ object Key {
const val bypass = "isBypassApps" const val bypass = "isBypassApps"
const val udpdns = "isUdpDns" const val udpdns = "isUdpDns"
const val ipv6 = "isIpv6" const val ipv6 = "isIpv6"
const val metered = "metered"
const val host = "proxy" const val host = "proxy"
const val password = "sitekey" const val password = "sitekey"
......
...@@ -36,6 +36,8 @@ ...@@ -36,6 +36,8 @@
<!-- feature category --> <!-- feature category -->
<string name="ipv6">IPv6 Route</string> <string name="ipv6">IPv6 Route</string>
<string name="ipv6_summary">Redirect IPv6 traffic to remote</string> <string name="ipv6_summary">Redirect IPv6 traffic to remote</string>
<string name="metered">Metered Hint</string>
<string name="metered_summary">Hint system to treat VPN as metered</string>
<string name="route_list">Route</string> <string name="route_list">Route</string>
<string name="route_entry_all">All</string> <string name="route_entry_all">All</string>
<string name="route_entry_bypass_lan">Bypass LAN</string> <string name="route_entry_bypass_lan">Bypass LAN</string>
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true android.enableJetifier=true
android.enableR8=true android.enableR8=true
# android.enableR8.fullMode=true android.enableR8.fullMode=true
android.useAndroidX=true android.useAndroidX=true
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
......
...@@ -56,11 +56,11 @@ androidExtensions { ...@@ -56,11 +56,11 @@ androidExtensions {
dependencies { dependencies {
implementation project(':core') implementation project(':core')
implementation "androidx.browser:browser:1.0.0" implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.gms:play-services-vision:17.0.2' implementation 'com.google.android.gms:play-services-vision:17.0.2'
implementation 'com.google.firebase:firebase-ads:17.2.0' implementation 'com.google.firebase:firebase-ads:17.2.0'
implementation "com.takisoft.preferencex:preferencex-simplemenu:$preferencexVersion" implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.4' implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.4'
implementation 'net.glxn.qrgen:android:2.0' implementation 'net.glxn.qrgen:android:2.0'
implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3' implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3'
......
...@@ -23,17 +23,12 @@ package com.github.shadowsocks ...@@ -23,17 +23,12 @@ package com.github.shadowsocks
import android.app.Application import android.app.Application
import android.content.res.Configuration import android.content.res.Configuration
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.github.shadowsocks.preference.BottomSheetPreferenceDialogFragment
import com.github.shadowsocks.preference.IconListPreference
import com.takisoft.preferencex.PreferenceFragmentCompat
class App : Application() { class App : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Core.init(this, MainActivity::class) Core.init(this, MainActivity::class)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
BottomSheetPreferenceDialogFragment::class.java)
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
......
...@@ -22,22 +22,24 @@ package com.github.shadowsocks ...@@ -22,22 +22,24 @@ package com.github.shadowsocks
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.preference.EditTextPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.net.TcpFastOpen import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.preference.PortPreferenceListener
import com.github.shadowsocks.utils.remove import com.github.shadowsocks.utils.remove
import com.takisoft.preferencex.PreferenceFragmentCompat
class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore.publicStore preferenceManager.preferenceDataStore = DataStore.publicStore
DataStore.initGlobal() DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global) addPreferencesFromResource(R.xml.pref_global)
val boot = findPreference(Key.isAutoConnect) as SwitchPreference val boot = findPreference<SwitchPreference>(Key.isAutoConnect)!!
boot.setOnPreferenceChangeListener { _, value -> boot.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean BootReceiver.enabled = value as Boolean
true true
...@@ -45,13 +47,13 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { ...@@ -45,13 +47,13 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
boot.isChecked = BootReceiver.enabled boot.isChecked = BootReceiver.enabled
if (Build.VERSION.SDK_INT >= 24) boot.setSummary(R.string.auto_connect_summary_v24) if (Build.VERSION.SDK_INT >= 24) boot.setSummary(R.string.auto_connect_summary_v24)
val canToggleLocked = findPreference(Key.directBootAware) val canToggleLocked = findPreference<Preference>(Key.directBootAware)!!
if (Build.VERSION.SDK_INT >= 24) canToggleLocked.setOnPreferenceChangeListener { _, newValue -> if (Build.VERSION.SDK_INT >= 24) canToggleLocked.setOnPreferenceChangeListener { _, newValue ->
if (Core.directBootSupported && newValue as Boolean) DirectBoot.update() else DirectBoot.clean() if (Core.directBootSupported && newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
true true
} else canToggleLocked.remove() } else canToggleLocked.remove()
val tfo = findPreference(Key.tfo) as SwitchPreference val tfo = findPreference<SwitchPreference>(Key.tfo)!!
tfo.isChecked = DataStore.tcpFastOpen tfo.isChecked = DataStore.tcpFastOpen
tfo.setOnPreferenceChangeListener { _, value -> tfo.setOnPreferenceChangeListener { _, value ->
if (value as Boolean && !TcpFastOpen.sendEnabled) { if (value as Boolean && !TcpFastOpen.sendEnabled) {
...@@ -68,10 +70,13 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { ...@@ -68,10 +70,13 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version")) tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version"))
} }
val serviceMode = findPreference(Key.serviceMode) val serviceMode = findPreference<Preference>(Key.serviceMode)!!
val portProxy = findPreference(Key.portProxy) val portProxy = findPreference<EditTextPreference>(Key.portProxy)!!
val portLocalDns = findPreference(Key.portLocalDns) portProxy.onBindEditTextListener = PortPreferenceListener
val portTransproxy = findPreference(Key.portTransproxy) val portLocalDns = findPreference<EditTextPreference>(Key.portLocalDns)!!
portLocalDns.onBindEditTextListener = PortPreferenceListener
val portTransproxy = findPreference<EditTextPreference>(Key.portTransproxy)!!
portTransproxy.onBindEditTextListener = PortPreferenceListener
val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue -> val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) { val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) {
Key.modeProxy -> Pair(false, false) Key.modeProxy -> Pair(false, false)
......
...@@ -209,7 +209,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref ...@@ -209,7 +209,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
else ImportProfilesDialogFragment().withArg(ProfilesArg(profiles)).show(supportFragmentManager, null) else ImportProfilesDialogFragment().withArg(ProfilesArg(profiles)).show(supportFragmentManager, null)
} }
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) { override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
when (key) { when (key) {
Key.serviceMode -> handler.post { Key.serviceMode -> handler.post {
connection.disconnect(this) connection.disconnect(this)
......
...@@ -24,34 +24,26 @@ import android.app.Activity ...@@ -24,34 +24,26 @@ import android.app.Activity
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf import androidx.preference.*
import androidx.preference.Preference
import androidx.preference.PreferenceDataStore
import androidx.preference.SwitchPreference
import com.github.shadowsocks.Core.app import com.github.shadowsocks.Core.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.* import com.github.shadowsocks.plugin.*
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.*
import com.github.shadowsocks.preference.IconListPreference import com.github.shadowsocks.utils.*
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.preference.PluginConfigurationDialogFragment
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.readableMessage
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.takisoft.preferencex.EditTextPreference
import com.takisoft.preferencex.PreferenceFragmentCompat
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
class ProfileConfigFragment : PreferenceFragmentCompat(), class ProfileConfigFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener { Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener {
companion object { companion object PasswordSummaryProvider : Preference.SummaryProvider<EditTextPreference> {
override fun provideSummary(preference: EditTextPreference?) = "\u2022".repeat(preference?.text?.length ?: 0)
private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1 private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1
const val REQUEST_UNSAVED_CHANGES = 2 const val REQUEST_UNSAVED_CHANGES = 2
} }
...@@ -77,23 +69,29 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -77,23 +69,29 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
private lateinit var receiver: BroadcastReceiver private lateinit var receiver: BroadcastReceiver
private lateinit var udpFallback: Preference private lateinit var udpFallback: Preference
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore.privateStore preferenceManager.preferenceDataStore = DataStore.privateStore
val activity = requireActivity() val activity = requireActivity()
profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L) profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L)
addPreferencesFromResource(R.xml.pref_profile) addPreferencesFromResource(R.xml.pref_profile)
findPreference<EditTextPreference>(Key.remotePort)!!.onBindEditTextListener = PortPreferenceListener
findPreference<EditTextPreference>(Key.password)!!.summaryProvider = PasswordSummaryProvider
val serviceMode = DataStore.serviceMode val serviceMode = DataStore.serviceMode
findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy findPreference<Preference>(Key.remoteDns)!!.isEnabled = serviceMode != Key.modeProxy
isProxyApps = findPreference(Key.proxyApps) as SwitchPreference findPreference<Preference>(Key.ipv6)!!.isEnabled = serviceMode == Key.modeVpn
isProxyApps = findPreference(Key.proxyApps)!!
isProxyApps.isEnabled = serviceMode == Key.modeVpn isProxyApps.isEnabled = serviceMode == Key.modeVpn
isProxyApps.setOnPreferenceClickListener { isProxyApps.setOnPreferenceClickListener {
startActivity(Intent(activity, AppManager::class.java)) startActivity(Intent(activity, AppManager::class.java))
isProxyApps.isChecked = true isProxyApps.isChecked = true
false false
} }
findPreference(Key.udpdns).isEnabled = serviceMode != Key.modeProxy findPreference<Preference>(Key.metered)!!.apply {
plugin = findPreference(Key.plugin) as IconListPreference if (Build.VERSION.SDK_INT >= 28) isEnabled = serviceMode == Key.modeVpn else remove()
pluginConfigure = findPreference(Key.pluginConfigure) as EditTextPreference }
findPreference<Preference>(Key.udpdns)!!.isEnabled = serviceMode != Key.modeProxy
plugin = findPreference(Key.plugin)!!
pluginConfigure = findPreference(Key.pluginConfigure)!!
plugin.unknownValueSummary = getString(R.string.plugin_unknown) plugin.unknownValueSummary = getString(R.string.plugin_unknown)
plugin.setOnPreferenceChangeListener { _, newValue -> plugin.setOnPreferenceChangeListener { _, newValue ->
pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions, newValue as String) pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions, newValue as String)
...@@ -109,7 +107,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -109,7 +107,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
pluginConfigure.onPreferenceChangeListener = this pluginConfigure.onPreferenceChangeListener = this
initPlugins() initPlugins()
receiver = Core.listenForPackageChanges(false) { initPlugins() } receiver = Core.listenForPackageChanges(false) { initPlugins() }
udpFallback = findPreference(Key.udpFallback) udpFallback = findPreference(Key.udpFallback)!!
DataStore.privateStore.registerChangeListener(this) DataStore.privateStore.registerChangeListener(this)
} }
...@@ -122,14 +120,16 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -122,14 +120,16 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
pluginConfiguration = PluginConfiguration(DataStore.plugin) pluginConfiguration = PluginConfiguration(DataStore.plugin)
plugin.value = pluginConfiguration.selected plugin.value = pluginConfiguration.selected
plugin.init() plugin.init()
plugin.checkSummary()
pluginConfigure.isEnabled = pluginConfiguration.selected.isNotEmpty() pluginConfigure.isEnabled = pluginConfiguration.selected.isNotEmpty()
pluginConfigure.text = pluginConfiguration.selectedOptions.toString() pluginConfigure.text = pluginConfiguration.selectedOptions.toString()
} }
private fun showPluginEditor() = displayPreferenceDialog(PluginConfigurationDialogFragment(), Key.pluginConfigure, private fun showPluginEditor() {
bundleOf(Pair("key", Key.pluginConfigure), PluginConfigurationDialogFragment().apply {
Pair(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, pluginConfiguration.selected))) setArg(Key.pluginConfigure, pluginConfiguration.selected)
setTargetFragment(this@ProfileConfigFragment, 0)
}.show(fragmentManager ?: return, Key.pluginConfigure)
}
private fun saveAndExit() { private fun saveAndExit() {
val profile = ProfileManager.getProfile(profileId) ?: Profile() val profile = ProfileManager.getProfile(profileId) ?: Profile()
...@@ -161,18 +161,26 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -161,18 +161,26 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
false false
} }
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) { override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
if (key != Key.proxyApps && findPreference(key) != null) DataStore.dirty = true if (key != Key.proxyApps && findPreference<Preference>(key) != null) DataStore.dirty = true
} }
override fun onDisplayPreferenceDialog(preference: Preference) { override fun onDisplayPreferenceDialog(preference: Preference) {
if (preference.key == Key.pluginConfigure) { when (preference.key) {
val intent = PluginManager.buildIntent(pluginConfiguration.selected, PluginContract.ACTION_CONFIGURE) Key.plugin -> BottomSheetPreferenceDialogFragment().apply {
if (intent.resolveActivity(requireContext().packageManager) == null) showPluginEditor() else setArg(Key.plugin)
startActivityForResult(intent setTargetFragment(this@ProfileConfigFragment, 0)
.putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.selectedOptions.toString()), }.show(fragmentManager ?: return, Key.plugin)
REQUEST_CODE_PLUGIN_CONFIGURE) Key.pluginConfigure -> {
} else super.onDisplayPreferenceDialog(preference) val intent = PluginManager.buildIntent(pluginConfiguration.selected, PluginContract.ACTION_CONFIGURE)
if (intent.resolveActivity(requireContext().packageManager) == null) showPluginEditor() else {
startActivityForResult(intent
.putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.selectedOptions.toString()),
REQUEST_CODE_PLUGIN_CONFIGURE)
}
}
else -> super.onDisplayPreferenceDialog(preference)
}
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
......
...@@ -32,6 +32,7 @@ import android.view.View ...@@ -32,6 +32,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.preference.PreferenceDialogFragmentCompat import androidx.preference.PreferenceDialogFragmentCompat
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
...@@ -97,6 +98,10 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() { ...@@ -97,6 +98,10 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() {
} }
} }
fun setArg(key: String) {
arguments = bundleOf(PreferenceDialogFragmentCompat.ARG_KEY to key)
}
private val preference by lazy { getPreference() as IconListPreference } private val preference by lazy { getPreference() as IconListPreference }
private var clickedIndex = -1 private var clickedIndex = -1
......
...@@ -26,6 +26,14 @@ import android.util.AttributeSet ...@@ -26,6 +26,14 @@ import android.util.AttributeSet
import androidx.preference.ListPreference import androidx.preference.ListPreference
class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPreference(context, attrs) { class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPreference(context, attrs) {
companion object FallbackProvider : SummaryProvider<IconListPreference> {
override fun provideSummary(preference: IconListPreference?): CharSequence? {
val i = preference?.selectedEntry
return if (i != null && i < 0) preference.unknownValueSummary?.format(preference.value) else
preference?.entry
}
}
var entryIcons: Array<Drawable?>? = null var entryIcons: Array<Drawable?>? = null
val selectedEntry: Int get() = entryValues.indexOf(value) val selectedEntry: Int get() = entryValues.indexOf(value)
val entryIcon: Drawable? get() = entryIcons?.getOrNull(selectedEntry) val entryIcon: Drawable? get() = entryIcons?.getOrNull(selectedEntry)
...@@ -49,7 +57,6 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr ...@@ -49,7 +57,6 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr
val listener = listener val listener = listener
if (listener == null || listener.onPreferenceChange(preference, newValue)) { if (listener == null || listener.onPreferenceChange(preference, newValue)) {
value = newValue.toString() value = newValue.toString()
checkSummary()
if (entryIcons != null) icon = entryIcon if (entryIcons != null) icon = entryIcon
true true
} else false } else false
...@@ -60,13 +67,9 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr ...@@ -60,13 +67,9 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr
// a.recycle() // a.recycle()
} }
fun checkSummary() {
val unknownValueSummary = unknownValueSummary
if (unknownValueSummary != null) summary = if (selectedEntry < 0) unknownValueSummary.format(value) else "%s"
}
fun init() { fun init() {
icon = entryIcon icon = entryIcon
summaryProvider = FallbackProvider
} }
override fun onSetInitialValue(defaultValue: Any?) { override fun onSetInitialValue(defaultValue: Any?) {
super.onSetInitialValue(defaultValue) super.onSetInitialValue(defaultValue)
......
...@@ -23,17 +23,23 @@ package com.github.shadowsocks.preference ...@@ -23,17 +23,23 @@ package com.github.shadowsocks.preference
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.preference.EditTextPreferenceDialogFragmentCompat
import androidx.preference.PreferenceDialogFragmentCompat
import com.github.shadowsocks.ProfileConfigActivity import com.github.shadowsocks.ProfileConfigActivity
import com.github.shadowsocks.plugin.PluginContract import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.plugin.PluginManager import com.github.shadowsocks.plugin.PluginManager
import com.takisoft.preferencex.EditTextPreferenceDialogFragmentCompat
class PluginConfigurationDialogFragment : EditTextPreferenceDialogFragmentCompat() { class PluginConfigurationDialogFragment : EditTextPreferenceDialogFragmentCompat() {
companion object { companion object {
const val PLUGIN_ID_FRAGMENT_TAG = private const val PLUGIN_ID_FRAGMENT_TAG =
"com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID" "com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID"
} }
fun setArg(key: String, plugin: String) {
arguments = bundleOf(PreferenceDialogFragmentCompat.ARG_KEY to key, PLUGIN_ID_FRAGMENT_TAG to plugin)
}
private lateinit var editText: EditText private lateinit var editText: EditText
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) { override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
......
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="?attr/colorControlNormal">
<path android:fillColor="#FF000000" android:pathData="M13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92V2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<!-- Based on: https://android.googlesource.com/platform/frameworks/support/+/9b8fb51/preference/res/layout/preference_dialog_edittext.xml -->
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="48dp"
android:layout_marginBottom="48dp"
android:overScrollMode="ifContentScrolls">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@android:id/message"
style="?android:attr/textAppearanceSmall"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="48dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"/>
<com.google.android.material.textfield.TextInputLayout
android:orientation="vertical"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@android:id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textPassword"
android:typeface="monospace"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:initialExpandedChildrenCount="3"> app:initialExpandedChildrenCount="3">
<SwitchPreference android:key="isAutoConnect"
android:persistent="false"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"/>
<SwitchPreference <SwitchPreference
android:key="directBootAware" app:key="isAutoConnect"
android:icon="@drawable/ic_action_lock" app:persistent="false"
android:summary="@string/direct_boot_aware_summary" app:summary="@string/auto_connect_summary"
android:title="@string/direct_boot_aware"/> app:title="@string/auto_connect"/>
<SwitchPreference <SwitchPreference
android:key="tcp_fastopen" app:key="directBootAware"
android:icon="@drawable/ic_action_offline_bolt" app:icon="@drawable/ic_action_lock"
android:summary="@string/tcp_fastopen_summary" app:summary="@string/direct_boot_aware_summary"
android:title="TCP Fast Open"/> app:title="@string/direct_boot_aware"/>
<SwitchPreference
app:key="tcp_fastopen"
app:icon="@drawable/ic_action_offline_bolt"
app:summary="@string/tcp_fastopen_summary"
app:title="TCP Fast Open"/>
<SimpleMenuPreference <com.takisoft.preferencex.SimpleMenuPreference
android:key="serviceMode" app:key="serviceMode"
android:entries="@array/service_modes" app:entries="@array/service_modes"
android:entryValues="@array/service_mode_values" app:entryValues="@array/service_mode_values"
android:defaultValue="vpn" app:defaultValue="vpn"
android:summary="%s" app:title="@string/service_mode"
android:title="@string/service_mode"/> app:useSimpleSummaryProvider="true"/>
<AutoSummaryEditTextPreference <EditTextPreference
android:key="portProxy" app:key="portProxy"
android:inputType="number" app:title="@string/port_proxy"
android:maxLength="5" app:useSimpleSummaryProvider="true"/>
app:pref_summaryHasText="%s" <EditTextPreference
android:singleLine="true" app:key="portLocalDns"
android:title="@string/port_proxy"/> app:icon="@drawable/ic_action_dns"
<AutoSummaryEditTextPreference app:title="@string/port_local_dns"
android:key="portLocalDns" app:useSimpleSummaryProvider="true"/>
android:icon="@drawable/ic_action_dns" <EditTextPreference
android:inputType="number" app:key="portTransproxy"
android:maxLength="5" app:title="@string/port_transproxy"
app:pref_summaryHasText="%s" app:useSimpleSummaryProvider="true"/>
android:singleLine="true"
android:title="@string/port_local_dns"/>
<AutoSummaryEditTextPreference
android:key="portTransproxy"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/port_transproxy"/>
</PreferenceScreen> </PreferenceScreen>
...@@ -2,95 +2,93 @@ ...@@ -2,95 +2,93 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<AutoSummaryEditTextPreference <EditTextPreference
android:key="profileName" app:key="profileName"
app:pref_summaryHasText="%s" app:title="@string/profile_name"
android:title="@string/profile_name"/> app:useSimpleSummaryProvider="true"/>
<PreferenceCategory <PreferenceCategory
android:title="@string/proxy_cat"> app:title="@string/proxy_cat">
<AutoSummaryEditTextPreference <EditTextPreference
android:key="proxy" app:key="proxy"
app:pref_summaryHasText="%s" app:singleLine="true"
android:singleLine="true" app:title="@string/proxy"
android:title="@string/proxy"/> app:useSimpleSummaryProvider="true"/>
<AutoSummaryEditTextPreference <EditTextPreference
android:key="remotePortNum" app:key="remotePortNum"
android:inputType="number" app:title="@string/remote_port"
android:maxLength="5" app:useSimpleSummaryProvider="true"/>
app:pref_summaryHasText="%s" <EditTextPreference
android:singleLine="true" app:dialogLayout="@layout/preference_dialog_password"
android:title="@string/remote_port"/> app:key="sitekey"
<AutoSummaryEditTextPreference app:title="@string/sitekey"/>
android:inputType="textPassword" <com.takisoft.preferencex.SimpleMenuPreference
android:typeface="monospace" app:key="encMethod"
app:pref_summaryPasswordSubstituteLength="0" app:icon="@drawable/ic_notification_enhanced_encryption"
android:key="sitekey" app:entries="@array/enc_method_entry"
app:pref_summaryHasText="%s" app:entryValues="@array/enc_method_value"
android:singleLine="true" app:title="@string/enc_method"
android:title="@string/sitekey"/> app:useSimpleSummaryProvider="true"/>
<SimpleMenuPreference
android:key="encMethod"
android:icon="@drawable/ic_notification_enhanced_encryption"
android:entries="@array/enc_method_entry"
android:entryValues="@array/enc_method_value"
android:summary="%s"
android:title="@string/enc_method"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/feature_cat"> app:title="@string/feature_cat">
<SimpleMenuPreference <com.takisoft.preferencex.SimpleMenuPreference
android:key="route" app:key="route"
android:entries="@array/route_entry" app:entries="@array/route_entry"
android:entryValues="@array/route_value" app:entryValues="@array/route_value"
android:summary="%s" app:title="@string/route_list"
android:title="@string/route_list"/> app:useSimpleSummaryProvider="true"/>
<AutoSummaryEditTextPreference <EditTextPreference
android:key="remoteDns" app:key="remoteDns"
android:icon="@drawable/ic_action_dns" app:icon="@drawable/ic_action_dns"
android:title="@string/remote_dns" app:title="@string/remote_dns"
app:pref_summaryHasText="%s"/> app:useSimpleSummaryProvider="true"/>
<SwitchPreference <SwitchPreference
android:key="isIpv6" app:key="isIpv6"
android:icon="@drawable/ic_image_looks_6" app:icon="@drawable/ic_image_looks_6"
android:summary="@string/ipv6_summary" app:summary="@string/ipv6_summary"
android:title="@string/ipv6"/> app:title="@string/ipv6"/>
<SwitchPreference <SwitchPreference
android:key="isProxyApps" app:key="isProxyApps"
android:icon="@drawable/ic_navigation_apps" app:icon="@drawable/ic_navigation_apps"
android:summary="@string/proxied_apps_summary" app:summary="@string/proxied_apps_summary"
android:title="@string/proxied_apps"/> app:title="@string/proxied_apps"/>
<SwitchPreference <SwitchPreference
android:key="isUdpDns" app:key="isUdpDns"
android:icon="@drawable/ic_action_dns" app:icon="@drawable/ic_action_dns"
android:summary="@string/udp_dns_summary" app:summary="@string/udp_dns_summary"
android:title="@string/udp_dns"/> app:title="@string/udp_dns"/>
<SwitchPreference
app:key="metered"
app:icon="@drawable/ic_device_data_usage"
app:summary="@string/metered_summary"
app:title="@string/metered"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:title="@string/plugin"> app:title="@string/plugin">
<com.github.shadowsocks.preference.IconListPreference <com.github.shadowsocks.preference.IconListPreference
android:key="plugin" app:key="plugin"
android:persistent="false" app:persistent="false"
android:summary="%s" app:title="@string/plugin"
android:title="@string/plugin"/> app:useSimpleSummaryProvider="true"/>
<AutoSummaryEditTextPreference <EditTextPreference
android:key="plugin.configure" app:key="plugin.configure"
android:icon="@drawable/ic_action_settings" app:icon="@drawable/ic_action_settings"
android:persistent="false" app:persistent="false"
android:singleLine="true" app:singleLine="true"
app:pref_summaryHasText="%s" app:title="@string/plugin_configure"
android:title="@string/plugin_configure"/> app:useSimpleSummaryProvider="true"/>
<Preference <Preference
android:key="udpFallback" app:key="udpFallback"
android:title="@string/udp_fallback" app:title="@string/udp_fallback"
android:summary="@string/plugin_disabled"> app:summary="@string/plugin_disabled">
<intent android:targetPackage="com.github.shadowsocks" <intent android:targetPackage="com.github.shadowsocks"
android:targetClass="com.github.shadowsocks.UdpFallbackProfileActivity"/> android:targetClass="com.github.shadowsocks.UdpFallbackProfileActivity"/>
</Preference> </Preference>
......
...@@ -20,11 +20,7 @@ ...@@ -20,11 +20,7 @@
package com.github.shadowsocks.tv package com.github.shadowsocks.tv
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.updateLayoutParams
import androidx.leanback.preference.LeanbackPreferenceDialogFragmentCompat import androidx.leanback.preference.LeanbackPreferenceDialogFragmentCompat
import androidx.leanback.preference.LeanbackSettingsFragmentCompat import androidx.leanback.preference.LeanbackSettingsFragmentCompat
import androidx.preference.* import androidx.preference.*
...@@ -59,11 +55,4 @@ class MainFragment : LeanbackSettingsFragmentCompat() { ...@@ -59,11 +55,4 @@ class MainFragment : LeanbackSettingsFragmentCompat() {
} }
return super.onPreferenceDisplayDialog(caller, pref) return super.onPreferenceDisplayDialog(caller, pref)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<View>(R.id.settings_preference_fragment_container).updateLayoutParams {
width = ViewGroup.LayoutParams.MATCH_PARENT
}
}
} }
...@@ -35,10 +35,7 @@ import androidx.leanback.preference.LeanbackPreferenceFragmentCompat ...@@ -35,10 +35,7 @@ import androidx.leanback.preference.LeanbackPreferenceFragmentCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.get import androidx.lifecycle.get
import androidx.preference.ListPreference import androidx.preference.*
import androidx.preference.Preference
import androidx.preference.PreferenceDataStore
import androidx.preference.SwitchPreference
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.BootReceiver import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
...@@ -51,6 +48,7 @@ import com.github.shadowsocks.net.HttpsTest ...@@ -51,6 +48,7 @@ import com.github.shadowsocks.net.HttpsTest
import com.github.shadowsocks.net.TcpFastOpen import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.preference.PortPreferenceListener
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.datas import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
...@@ -71,9 +69,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -71,9 +69,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private lateinit var serviceMode: Preference private lateinit var serviceMode: Preference
private lateinit var tfo: SwitchPreference private lateinit var tfo: SwitchPreference
private lateinit var shareOverLan: Preference private lateinit var shareOverLan: Preference
private lateinit var portProxy: Preference private lateinit var portProxy: EditTextPreference
private lateinit var portLocalDns: Preference private lateinit var portLocalDns: EditTextPreference
private lateinit var portTransproxy: Preference private lateinit var portTransproxy: EditTextPreference
private val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue -> private val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) { val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) {
Key.modeProxy -> Pair(false, false) Key.modeProxy -> Pair(false, false)
...@@ -155,12 +153,12 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -155,12 +153,12 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
preferenceManager.preferenceDataStore = DataStore.publicStore preferenceManager.preferenceDataStore = DataStore.publicStore
DataStore.initGlobal() DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_main) addPreferencesFromResource(R.xml.pref_main)
fab = findPreference(Key.id) as ListPreference fab = findPreference(Key.id)!!
populateProfiles() populateProfiles()
stats = findPreference(Key.controlStats) stats = findPreference(Key.controlStats)!!
controlImport = findPreference(Key.controlImport) controlImport = findPreference(Key.controlImport)!!
(findPreference(Key.isAutoConnect) as SwitchPreference).apply { findPreference<SwitchPreference>(Key.isAutoConnect)!!.apply {
setOnPreferenceChangeListener { _, value -> setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean BootReceiver.enabled = value as Boolean
true true
...@@ -168,7 +166,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -168,7 +166,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
isChecked = BootReceiver.enabled isChecked = BootReceiver.enabled
} }
tfo = findPreference(Key.tfo) as SwitchPreference tfo = findPreference(Key.tfo)!!
tfo.isChecked = DataStore.tcpFastOpen tfo.isChecked = DataStore.tcpFastOpen
tfo.setOnPreferenceChangeListener { _, value -> tfo.setOnPreferenceChangeListener { _, value ->
if (value as Boolean && !TcpFastOpen.sendEnabled) { if (value as Boolean && !TcpFastOpen.sendEnabled) {
...@@ -185,13 +183,16 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -185,13 +183,16 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version")) tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version"))
} }
serviceMode = findPreference(Key.serviceMode) serviceMode = findPreference(Key.serviceMode)!!
shareOverLan = findPreference(Key.shareOverLan) shareOverLan = findPreference(Key.shareOverLan)!!
portProxy = findPreference(Key.portProxy) portProxy = findPreference(Key.portProxy)!!
portLocalDns = findPreference(Key.portLocalDns) portProxy.onBindEditTextListener = PortPreferenceListener
portTransproxy = findPreference(Key.portTransproxy) portLocalDns = findPreference(Key.portLocalDns)!!
portLocalDns.onBindEditTextListener = PortPreferenceListener
portTransproxy = findPreference(Key.portTransproxy)!!
portTransproxy.onBindEditTextListener = PortPreferenceListener
serviceMode.onPreferenceChangeListener = onServiceModeChange serviceMode.onPreferenceChangeListener = onServiceModeChange
findPreference(Key.about).apply { findPreference<Preference>(Key.about)!!.apply {
summary = getString(R.string.about_title, BuildConfig.VERSION_NAME) summary = getString(R.string.about_title, BuildConfig.VERSION_NAME)
setOnPreferenceClickListener { setOnPreferenceClickListener {
Toast.makeText(requireContext(), "https://shadowsocks.org/android", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "https://shadowsocks.org/android", Toast.LENGTH_SHORT).show()
...@@ -235,7 +236,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -235,7 +236,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
} }
} }
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) { override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
when (key) { when (key) {
Key.serviceMode -> handler.post { Key.serviceMode -> handler.post {
connection.disconnect(requireContext()) connection.disconnect(requireContext())
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
<item name="android:colorButtonNormal">@color/material_accent_200</item> <item name="android:colorButtonNormal">@color/material_accent_200</item>
<item name="android:colorPrimary">@color/color_primary</item> <item name="android:colorPrimary">@color/color_primary</item>
<item name="android:colorPrimaryDark">@color/color_primary_dark</item> <item name="android:colorPrimaryDark">@color/color_primary_dark</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Leanback</item> <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Leanback</item>
</style> </style>
</resources> </resources>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android" app:title="@string/app_name">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/app_name">
<!-- profileId is stored in privateStore so its value needs to be populated manually --> <!-- profileId is stored in privateStore so its value needs to be populated manually -->
<ListPreference <ListPreference
android:key="profileId" app:key="profileId"
android:title="@string/connect" app:title="@string/connect"
android:summary="%s" app:persistent="false"
android:persistent="false"/> app:useSimpleSummaryProvider="true"/>
<Preference <Preference
android:key="control.stats" app:key="control.stats"
android:title="@string/connection_test_pending" app:title="@string/connection_test_pending"
android:summary="@string/stat_summary"/> app:summary="@string/stat_summary"/>
<Preference <Preference
android:key="control.import" app:key="control.import"
android:title="@string/action_replace_file"/> app:title="@string/action_replace_file"/>
<Preference <Preference
android:key="control.export" app:key="control.export"
android:title="@string/action_export_file"/> app:title="@string/action_export_file"/>
<PreferenceCategory <PreferenceCategory
android:title="@string/settings" app:title="@string/settings"
app:initialExpandedChildrenCount="2"> app:initialExpandedChildrenCount="2">
<SwitchPreference <SwitchPreference
android:key="isAutoConnect" app:key="isAutoConnect"
android:persistent="false" app:persistent="false"
android:summary="@string/auto_connect_summary" app:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"/> app:title="@string/auto_connect"/>
<SwitchPreference <SwitchPreference
android:key="tcp_fastopen" app:key="tcp_fastopen"
android:summary="@string/tcp_fastopen_summary" app:summary="@string/tcp_fastopen_summary"
android:title="TCP Fast Open"/> app:title="TCP Fast Open"/>
<ListPreference <ListPreference
android:key="serviceMode" app:key="serviceMode"
android:entries="@array/service_modes" app:entries="@array/service_modes"
android:entryValues="@array/service_mode_values" app:entryValues="@array/service_mode_values"
android:defaultValue="vpn" app:defaultValue="vpn"
android:summary="%s" app:title="@string/service_mode"
android:title="@string/service_mode"/> app:useSimpleSummaryProvider="true"/>
<SwitchPreference <SwitchPreference
android:key="shareOverLan" app:key="shareOverLan"
android:title="@string/share_over_lan"/> app:title="@string/share_over_lan"/>
<com.takisoft.preferencex.AutoSummaryEditTextPreference <EditTextPreference
android:key="portProxy" app:key="portProxy"
android:inputType="number" app:title="@string/port_proxy"
android:maxLength="5" app:useSimpleSummaryProvider="true"/>
app:pref_summaryHasText="%s" <EditTextPreference
android:singleLine="true" app:key="portLocalDns"
android:title="@string/port_proxy"/> app:title="@string/port_local_dns"
<com.takisoft.preferencex.AutoSummaryEditTextPreference app:useSimpleSummaryProvider="true"/>
android:key="portLocalDns" <EditTextPreference
android:inputType="number" app:key="portTransproxy"
android:maxLength="5" app:title="@string/port_transproxy"
app:pref_summaryHasText="%s" app:useSimpleSummaryProvider="true"/>
android:singleLine="true"
android:title="@string/port_local_dns"/>
<com.takisoft.preferencex.AutoSummaryEditTextPreference
android:key="portTransproxy"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/port_transproxy"/>
</PreferenceCategory> </PreferenceCategory>
<Preference <Preference
android:key="about" app:key="about"
android:title="@string/about"/> app:title="@string/about"/>
</PreferenceScreen> </PreferenceScreen>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment