diff --git a/.gitignore b/.gitignore
index 9ea44594..5cdc29d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ gen_*
*.o
local.sbt
.deps
+/proguard-sbt.txt
#Intellij IDEA
*.iml
diff --git a/build.sbt b/build.sbt
index 7f69674c..052fa5cb 100644
--- a/build.sbt
+++ b/build.sbt
@@ -33,6 +33,7 @@ useSupportVectors
libraryDependencies ++= Seq(
"com.android.support" % "design" % "24.2.1",
"com.android.support" % "gridlayout-v7" % "24.2.1",
+ "com.android.support" % "preference-v14" % "24.2.1",
"com.github.clans" % "fab" % "1.6.4",
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
"com.github.kevinsawicki" % "http-request" % "6.0",
diff --git a/src/main/res/drawable-v21/background_stat.xml b/src/main/res/drawable-v21/background_stat.xml
index baf76d50..48bdd29f 100644
--- a/src/main/res/drawable-v21/background_stat.xml
+++ b/src/main/res/drawable-v21/background_stat.xml
@@ -1,6 +1,6 @@
-
-
+
diff --git a/src/main/res/drawable/background_stat.xml b/src/main/res/drawable/background_stat.xml
index cda31441..46fbe227 100644
--- a/src/main/res/drawable/background_stat.xml
+++ b/src/main/res/drawable/background_stat.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/src/main/res/layout/layout_profiles.xml b/src/main/res/layout/layout_profiles.xml
index bf37c802..ac543fe5 100644
--- a/src/main/res/layout/layout_profiles.xml
+++ b/src/main/res/layout/layout_profiles.xml
@@ -17,8 +17,8 @@
android:layout_gravity="bottom|end"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
- app:menu_colorNormal="@color/material_accent_500"
- app:menu_colorPressed="@color/material_accent_700"
+ app:menu_colorNormal="@color/material_primary_500"
+ app:menu_colorPressed="@color/material_primary_700"
app:fab_colorRipple="@android:color/white"
app:layout_behavior="com.github.shadowsocks.widget.FloatingActionMenuBehavior">
@@ -26,8 +26,8 @@
android:id="@+id/fab_manual_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:fab_colorNormal="@color/material_accent_500"
- app:fab_colorPressed="@color/material_accent_700"
+ app:fab_colorNormal="@color/material_primary_500"
+ app:fab_colorPressed="@color/material_primary_700"
app:fab_colorRipple="@android:color/white"
app:fab_size="mini"
app:fab_label="@string/add_profile_methods_manual_settings"
@@ -36,8 +36,8 @@
android:id="@+id/fab_qrcode_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:fab_colorNormal="@color/material_accent_500"
- app:fab_colorPressed="@color/material_accent_700"
+ app:fab_colorNormal="@color/material_primary_500"
+ app:fab_colorPressed="@color/material_primary_700"
app:fab_colorRipple="@android:color/white"
app:fab_size="mini"
app:fab_label="@string/add_profile_methods_scan_qr_code"
@@ -46,8 +46,8 @@
android:id="@+id/fab_nfc_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:fab_colorNormal="@color/material_accent_500"
- app:fab_colorPressed="@color/material_accent_700"
+ app:fab_colorNormal="@color/material_primary_500"
+ app:fab_colorPressed="@color/material_primary_700"
app:fab_colorRipple="@android:color/white"
app:fab_size="mini"
app:fab_label="NFC"
@@ -56,8 +56,8 @@
android:id="@+id/fab_import_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:fab_colorNormal="@color/material_accent_500"
- app:fab_colorPressed="@color/material_accent_700"
+ app:fab_colorNormal="@color/material_primary_500"
+ app:fab_colorPressed="@color/material_primary_700"
app:fab_colorRipple="@android:color/white"
app:fab_size="mini"
app:fab_label="@string/action_import"
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index 6e0ba266..dd3ad062 100644
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -24,13 +24,9 @@
Настройки Сервера
Имя Профиля
Сервер
- Имя или IP удалённого сервера
Удалённый порт
- %d (номер порта удалённого сервера)
Локальный порт
- %d (номер порта локального сервера)
Пароль
- Пароль удалённого сервера
Метод Шифрования
@@ -148,5 +144,4 @@
KCP порт
%d (KCPTUN омер порта удалённого сервера)
KCP параметры
- Командная строка KCPTUN
diff --git a/src/main/res/values-v21/styles.xml b/src/main/res/values-v21/styles.xml
new file mode 100644
index 00000000..1050141b
--- /dev/null
+++ b/src/main/res/values-v21/styles.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index d02363d5..4beaa3c9 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -9,13 +9,9 @@
配置文件
新建或切换到其他配置文件
服务器
- 远程服务器的地址
远程端口
- %d(远程服务器的端口号)
本地端口
- %d(本地客户端的端口号)
密码
- 远程服务器的密码
加密方法
功能设置
HTTP 代理服务器
@@ -142,6 +138,5 @@
KCP 端口
%d(远端 KCPTUN 服务器的端口号)
KCP 参数
- KCPTUN 的命令行参数
diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml
index 7c4ceca8..0d1d7e26 100644
--- a/src/main/res/values-zh/strings.xml
+++ b/src/main/res/values-zh/strings.xml
@@ -10,13 +10,9 @@
設定檔
切換至其他設定檔或新增新設定檔
伺服器
- 遠端伺服器的 IP 位址或主機名稱
遠端連接埠
- %d(遠端伺服器的連接埠號碼)
連接埠號碼
- %d(本機伺服器的連接埠號碼)
密碼
- 遠端伺服器的密碼
加密方法
功能設定
HTTP Proxy
@@ -144,6 +140,5 @@
KCP 連接埠
%d(遠端 KCPTUN 伺服器的連接埠號碼)
KCP 參數
- KCPTUN 的命令行參數
diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml
index 1ce9d120..fc9b5af9 100644
--- a/src/main/res/values/attrs.xml
+++ b/src/main/res/values/attrs.xml
@@ -1,9 +1,5 @@
-
-
-
-
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
index 311f4003..afb029ba 100644
--- a/src/main/res/values/colors.xml
+++ b/src/main/res/values/colors.xml
@@ -6,8 +6,9 @@
#90A4AE
#607D8B
#455A64
- @color/material_blue_grey_300
- @color/material_blue_grey_500
- @color/material_blue_grey_700
+ @color/material_blue_grey_300
+ @color/material_blue_grey_500
+ @color/material_blue_grey_700
+ @color/material_blue_grey_500
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index d230a127..8d896677 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -29,13 +29,9 @@
Server Settings
Profile Name
Server
- Hostname or IP of the remote server
Remote Port
- %d (port number of the remote server)
Local Port
- %d (port number of the local server)
Password
- Password of the remote server
Encrypt Method
@@ -155,5 +151,4 @@
KCP Port
%d (port number of the remote KCPTUN server)
KCP Parameters (CLI)
- The command line of KCPTUN
diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml
index d73f18a5..5de30f82 100644
--- a/src/main/res/values/styles.xml
+++ b/src/main/res/values/styles.xml
@@ -6,20 +6,43 @@
- 24sp
-
+
+
+
+
+
+
+
diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml
index 982f0a16..d673f44e 100644
--- a/src/main/res/xml/pref_all.xml
+++ b/src/main/res/xml/pref_all.xml
@@ -2,39 +2,40 @@
-
-
-
-
-
-
-
-
+
-
-
-
+
-
-
-
-
+
-
+
-
+
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
index 3fc2f914..2df8a806 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
@@ -34,7 +34,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
private val builder = new NotificationCompat.Builder(service)
.setWhen(0)
- .setColor(ContextCompat.getColor(service, R.color.material_accent_500))
+ .setColor(ContextCompat.getColor(service, R.color.material_primary_500))
.setTicker(service.getString(R.string.forward_success))
.setContentTitle(profileName)
.setContentIntent(PendingIntent.getActivity(service, 0, new Intent(service, classOf[Shadowsocks])
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
index 054a6869..51cb0d38 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
@@ -6,14 +6,16 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.{Intent, SharedPreferences}
import android.net.Uri
import android.os.Bundle
-import android.preference.{Preference, PreferenceFragment, SwitchPreference}
import android.support.design.widget.Snackbar
+import android.support.v14.preference.SwitchPreference
import android.support.v7.app.AlertDialog
+import android.support.v7.preference.{DropDownPreference, Preference}
import android.webkit.{WebView, WebViewClient}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile
-import com.github.shadowsocks.preferences._
+import com.github.shadowsocks.preferences.KcpCliPreferenceDialogFragment
import com.github.shadowsocks.utils.{Key, TcpFastOpen, Utils}
+import tk.mygod.preference._
object ShadowsocksSettings {
// Constants
@@ -27,31 +29,25 @@ object ShadowsocksSettings {
pref.asInstanceOf[DropDownPreference].setValue(value)
}
- def updatePasswordEditTextPreference(pref: Preference, value: String) {
- pref.setSummary(value)
- pref.asInstanceOf[PasswordEditTextPreference].setText(value)
+ def updateEditTextPreference(pref: Preference, value: String) {
+ pref.asInstanceOf[EditTextPreference].setText(value)
}
def updateNumberPickerPreference(pref: Preference, value: Int) {
pref.asInstanceOf[NumberPickerPreference].setValue(value)
}
- def updateSummaryEditTextPreference(pref: Preference, value: String) {
- pref.setSummary(value)
- pref.asInstanceOf[SummaryEditTextPreference].setText(value)
- }
-
def updateSwitchPreference(pref: Preference, value: Boolean) {
pref.asInstanceOf[SwitchPreference].setChecked(value)
}
def updatePreference(pref: Preference, name: String, profile: Profile) {
name match {
- case Key.name => updateSummaryEditTextPreference(pref, profile.name)
- case Key.host => updateSummaryEditTextPreference(pref, profile.host)
+ case Key.name => updateEditTextPreference(pref, profile.name)
+ case Key.host => updateEditTextPreference(pref, profile.host)
case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort)
case Key.localPort => updateNumberPickerPreference(pref, profile.localPort)
- case Key.password => updatePasswordEditTextPreference(pref, profile.password)
+ case Key.password => updateEditTextPreference(pref, profile.password)
case Key.method => updateDropDownPreference(pref, profile.method)
case Key.route => updateDropDownPreference(pref, profile.route)
case Key.proxyApps => updateSwitchPreference(pref, profile.proxyApps)
@@ -60,7 +56,7 @@ object ShadowsocksSettings {
case Key.ipv6 => updateSwitchPreference(pref, profile.ipv6)
case Key.kcp => updateSwitchPreference(pref, profile.kcp)
case Key.kcpPort => updateNumberPickerPreference(pref, profile.kcpPort)
- case Key.kcpcli => updateSummaryEditTextPreference(pref, profile.kcpcli)
+ case Key.kcpcli => updateEditTextPreference(pref, profile.kcpcli)
}
}
}
@@ -73,8 +69,7 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
private var isProxyApps: SwitchPreference = _
- override def onCreate(savedInstanceState: Bundle) {
- super.onCreate(savedInstanceState)
+ override def onCreatePreferences(bundle: Bundle, key: String) {
addPreferencesFromResource(R.xml.pref_all)
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
@@ -206,6 +201,15 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
})
}
+ override def onDisplayPreferenceDialog(preference: Preference) = preference match {
+ case _: EditTextPreference => displayPreferenceDialog(preference.getKey,
+ if (preference.getKey == Key.kcpcli) new KcpCliPreferenceDialogFragment()
+ else new EditTextPreferenceDialogFragment())
+ case _: NumberPickerPreference =>
+ displayPreferenceDialog(preference.getKey, new NumberPickerPreferenceDialogFragment())
+ case _ => super.onDisplayPreferenceDialog(preference)
+ }
+
def refreshProfile() {
profile = app.currentProfile match {
case Some(p) => p
diff --git a/src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala
deleted file mode 100644
index 43230c5d..00000000
--- a/src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala
+++ /dev/null
@@ -1,169 +0,0 @@
-package com.github.shadowsocks.preferences
-
-import android.content.Context
-import android.content.res.TypedArray
-import android.preference.Preference
-import android.support.annotation.{ArrayRes, NonNull}
-import android.support.v7.widget.AppCompatSpinner
-import android.util.AttributeSet
-import android.view.{View, ViewGroup}
-import android.widget.AdapterView.OnItemSelectedListener
-import android.widget.{AdapterView, ArrayAdapter}
-import com.github.shadowsocks.R
-
-/**
- * @author Mygod
- */
-final class DropDownPreference(private val mContext: Context, attrs: AttributeSet = null)
- extends Preference(mContext, attrs) with SummaryPreference {
-
- private val mAdapter = new ArrayAdapter[String](mContext, android.R.layout.simple_spinner_dropdown_item)
- private val mSpinner = new AppCompatSpinner(mContext)
- private var mEntries: Array[CharSequence] = _
- private var mEntryValues: Array[CharSequence] = _
- private var mSelectedIndex: Int = -1
-
- mSpinner.setVisibility(View.INVISIBLE)
- mSpinner.setAdapter(mAdapter)
- mSpinner.setOnItemSelectedListener(new OnItemSelectedListener {
- def onNothingSelected(parent: AdapterView[_]) { }
- def onItemSelected(parent: AdapterView[_], view: View, position: Int, id: Long) = {
- val value = getValue(position)
- if (position != mSelectedIndex && callChangeListener(value)) setValue(position, value)
- }
- })
- setOnPreferenceClickListener((preference: Preference) => {
- // TODO: not working with scrolling
- // mSpinner.setDropDownVerticalOffset(Utils.dpToPx(getContext, -48 * mSelectedIndex).toInt)
- mSpinner.performClick
- true
- })
- val a: TypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.DropDownPreference)
- setEntries(a.getTextArray(R.styleable.DropDownPreference_android_entries))
- setEntryValues(a.getTextArray(R.styleable.DropDownPreference_android_entryValues))
- a.recycle
-
- protected override def getSummaryValue = getEntry
-
- /**
- * Sets the human-readable entries to be shown in the list. This will be shown in subsequent dialogs.
- *
- * Each entry must have a corresponding index in [[setEntryValues(CharSequence[])]].
- *
- * @param entries The entries.
- * @see [[setEntryValues(CharSequence[])]]
- */
- def setEntries(entries: Array[CharSequence]) {
- mEntries = entries
- mAdapter.clear
- if (entries != null) for (entry <- entries) mAdapter.add(entry.toString)
- }
- /**
- * @see #setEntries(CharSequence[])
- * @param entriesResId The entries array as a resource.
- */
- def setEntries(@ArrayRes entriesResId: Int): Unit = setEntries(getContext.getResources.getTextArray(entriesResId))
- /**
- * The list of entries to be shown in the list in subsequent dialogs.
- *
- * @return The list as an array.
- */
- def getEntries = mEntries
-
- /**
- * The array to find the value to save for a preference when an entry from
- * entries is selected. If a user clicks on the second item in entries, the
- * second item in this array will be saved to the preference.
- *
- * @param entryValues The array to be used as values to save for the preference.
- */
- def setEntryValues(entryValues: Array[CharSequence]) = mEntryValues = entryValues
- /**
- * @see #setEntryValues(CharSequence[])
- * @param entryValuesResId The entry values array as a resource.
- */
- def setEntryValues(@ArrayRes entryValuesResId: Int): Unit =
- setEntryValues(getContext.getResources.getTextArray(entryValuesResId))
- /**
- * Returns the array of values to be saved for the preference.
- *
- * @return The array of values.
- */
- def getEntryValues: Array[CharSequence] = mEntryValues
-
- private def getValue(index: Int) = (if (mEntryValues == null) index else mEntryValues(index)).toString
- /**
- * Sets the value of the key. This should be one of the entries in [[getEntryValues]].
- *
- * @param value The value to set for the key.
- */
- def setValue(value: String): Unit = setValue(findIndexOfValue(value), value)
-
- /**
- * Sets the value to the given index from the entry values.
- *
- * @param index The index of the value to set.
- */
- def setValueIndex(index: Int) = setValue(index, getValue(index))
-
- private def setValue(index: Int, value: String) {
- persistString(value)
- mSelectedIndex = index
- mSpinner.setSelection(index)
- notifyChanged
- }
-
- /**
- * Returns the value of the key. This should be one of the entries in [[getEntryValues]].
- *
- * @return The value of the key.
- */
- def getValue = if (mEntryValues == null || mSelectedIndex < 0) null else mEntryValues(mSelectedIndex).toString
-
- /**
- * Returns the entry corresponding to the current value.
- *
- * @return The entry corresponding to the current value, or null.
- */
- def getEntry = {
- val index = getValueIndex
- if (index >= 0 && mEntries != null) mEntries(index) else null
- }
-
- /**
- * Returns the index of the given value (in the entry values array).
- *
- * @param value The value whose index should be returned.
- * @return The index of the value, or -1 if not found.
- */
- def findIndexOfValue(value: String): Int = {
- if (value != null && mEntryValues != null) {
- var i = mEntryValues.length - 1
- while (i >= 0) {
- if (mEntryValues(i) == value) return i
- i -= 1
- }
- }
- -1
- }
-
- def getValueIndex = mSelectedIndex
-
- protected override def onGetDefaultValue(a: TypedArray, index: Int) = a.getString(index)
- protected override def onSetInitialValue (restoreValue: Boolean, defaultValue: AnyRef) =
- setValue(if (restoreValue) getPersistedString(getValue) else defaultValue.asInstanceOf[String])
-
- def setDropDownWidth(dimenResId: Int) =
- mSpinner.setDropDownWidth(mContext.getResources.getDimensionPixelSize(dimenResId))
-
- protected override def onBindView(@NonNull view: View) {
- super.onBindView(view)
- val parent = mSpinner.getParent.asInstanceOf[ViewGroup]
- if (view eq parent) return
- if (parent != null) parent.removeView(mSpinner)
- view.asInstanceOf[ViewGroup].addView(mSpinner, 0)
- val lp = mSpinner.getLayoutParams
- lp.width = 0
- mSpinner.setLayoutParams(lp)
- }
-}
diff --git a/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala
similarity index 70%
rename from src/main/scala/com/github/shadowsocks/preferences/KcpCliPreference.scala
rename to src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala
index d072c612..7f5bf600 100644
--- a/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreference.scala
+++ b/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala
@@ -1,21 +1,22 @@
package com.github.shadowsocks.preferences
import android.app.AlertDialog
-import android.content.{Context, DialogInterface}
-import android.util.AttributeSet
+import android.content.DialogInterface
import eu.chainfire.libsuperuser.Shell
+import tk.mygod.preference.EditTextPreferenceDialogFragment
-import scala.collection.JavaConversions._
+import scala.collection.JavaConverters._
/**
* @author Mygod
*/
-class KcpCliPreference(context: Context, attrs: AttributeSet) extends SummaryEditTextPreference(context, attrs) {
+class KcpCliPreferenceDialogFragment extends EditTextPreferenceDialogFragment {
override def onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
- builder.setNeutralButton("?", ((_, _) => new AlertDialog.Builder(context)
+ builder.setNeutralButton("?", ((_, _) => new AlertDialog.Builder(builder.getContext)
.setTitle("?")
- .setMessage(asScalaBuffer(Shell.SH.run(context.getApplicationInfo.dataDir + "/kcptun --help"))
+ .setMessage(Shell.SH.run(builder.getContext.getApplicationInfo.dataDir + "/kcptun --help")
+ .asScala
.dropWhile(line => line != "GLOBAL OPTIONS:")
.drop(1)
.takeWhile(line => line.length() > 3)
diff --git a/src/main/scala/com/github/shadowsocks/preferences/PasswordEditTextPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/PasswordEditTextPreference.scala
deleted file mode 100644
index c0e3a15b..00000000
--- a/src/main/scala/com/github/shadowsocks/preferences/PasswordEditTextPreference.scala
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Shadowsocks - A shadowsocks client for Android
- * Copyright (C) 2013
- *
- * 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 .
- *
- *
- * ___====-_ _-====___
- * _--^^^#####// \\#####^^^--_
- * _-^##########// ( ) \\##########^-_
- * -############// |\^^/| \\############-
- * _/############// (@::@) \\############\_
- * /#############(( \\// ))#############\
- * -###############\\ (oo) //###############-
- * -#################\\ / VV \ //#################-
- * -###################\\/ \//###################-
- * _#/|##########/\######( /\ )######/\##########|\#_
- * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
- * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
- * ` ` ` ` / | | | | \ ' ' ' '
- * ( | | | | )
- * __\ | | | | /__
- * (vvv(VVV)(VVV)vvv)
- *
- * HERE BE DRAGONS
- *
- */
-
-package com.github.shadowsocks.preferences
-
-import android.content.Context
-import android.preference.EditTextPreference
-import android.util.AttributeSet
-
-class PasswordEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int)
- extends EditTextPreference(context, attrs, defStyle) {
-
- def this(context: Context, attrs: AttributeSet) = {
- this(context, attrs, android.R.attr.editTextPreferenceStyle)
- mDefaultSummary = getSummary
- }
-
- override def setText(text: String) {
- super.setText(text)
- setSummary(text)
- }
-
- override def setSummary(summary: CharSequence) {
- if (summary.toString.isEmpty) {
- super.setSummary(mDefaultSummary)
- } else {
- super.setSummary(summary.toString.map(c => "*").mkString)
- }
- }
-
- private var mDefaultSummary: CharSequence = getSummary
-}
diff --git a/src/main/scala/com/github/shadowsocks/preferences/SummaryEditTextPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/SummaryEditTextPreference.scala
deleted file mode 100644
index ac7c76c5..00000000
--- a/src/main/scala/com/github/shadowsocks/preferences/SummaryEditTextPreference.scala
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Shadowsocks - A shadowsocks client for Android
- * Copyright (C) 2013
- *
- * 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 .
- *
- *
- * ___====-_ _-====___
- * _--^^^#####// \\#####^^^--_
- * _-^##########// ( ) \\##########^-_
- * -############// |\^^/| \\############-
- * _/############// (@::@) \\############\_
- * /#############(( \\// ))#############\
- * -###############\\ (oo) //###############-
- * -#################\\ / VV \ //#################-
- * -###################\\/ \//###################-
- * _#/|##########/\######( /\ )######/\##########|\#_
- * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
- * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
- * ` ` ` ` / | | | | \ ' ' ' '
- * ( | | | | )
- * __\ | | | | /__
- * (vvv(VVV)(VVV)vvv)
- *
- * HERE BE DRAGONS
- *
- */
-
-package com.github.shadowsocks.preferences
-
-import android.content.Context
-import android.preference.EditTextPreference
-import android.text.TextUtils
-import android.util.AttributeSet
-
-class SummaryEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int)
- extends EditTextPreference(context, attrs, defStyle) {
-
- def this(context: Context, attrs: AttributeSet) = {
- this(context, attrs, android.R.attr.editTextPreferenceStyle)
- mDefaultSummary = getSummary
- }
-
- override def setText(text: String) {
- super.setText(text)
- setSummary(text)
- }
-
- override def setSummary(summary: CharSequence) {
- if (TextUtils.isEmpty(summary)) {
- super.setSummary(mDefaultSummary)
- } else {
- super.setSummary(summary)
- }
- }
-
- private var mDefaultSummary: CharSequence = getSummary
-}
diff --git a/src/main/scala/com/github/shadowsocks/preferences/SummaryPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/SummaryPreference.scala
deleted file mode 100644
index 91f44752..00000000
--- a/src/main/scala/com/github/shadowsocks/preferences/SummaryPreference.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.github.shadowsocks.preferences
-
-import java.util.Locale
-
-import android.preference.Preference
-
-/**
- * Make your preference support %s in summary. Override getSummaryValue to customize what to put in.
- * @author Mygod
- */
-trait SummaryPreference extends Preference {
- protected def getSummaryValue: AnyRef
-
- /**
- * Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it
- * (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place.
- *
- * @return the summary with appropriate string substitution
- */
- override def getSummary = super.getSummary.toString.formatLocal(Locale.ENGLISH, getSummaryValue)
-}
diff --git a/src/main/scala/tk/mygod/preference/EditTextPreference.scala b/src/main/scala/tk/mygod/preference/EditTextPreference.scala
new file mode 100644
index 00000000..e77016c2
--- /dev/null
+++ b/src/main/scala/tk/mygod/preference/EditTextPreference.scala
@@ -0,0 +1,32 @@
+package tk.mygod.preference
+
+import android.content.Context
+import android.support.v7.preference.{EditTextPreference => Parent}
+import android.support.v7.widget.AppCompatEditText
+import android.text.InputType
+import android.util.AttributeSet
+
+/**
+ * Fixed EditTextPreference + SummaryPreference with password support!
+ * Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/tree/master/app/src/main/java/android/support/v7/preference
+ */
+class EditTextPreference(context: Context, attrs: AttributeSet = null) extends Parent(context, attrs)
+ with SummaryPreference {
+ val editText = new AppCompatEditText(context, attrs)
+ editText.setId(android.R.id.edit)
+
+ override protected def getSummaryValue = {
+ var text = getText
+ if (text == null) text = ""
+ val inputType = editText.getInputType
+ if (inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD) ||
+ inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) ||
+ inputType == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD))
+ "\u2022" * text.length else text
+ }
+
+ override def setText(text: String) = {
+ super.setText(text)
+ notifyChanged
+ }
+}
diff --git a/src/main/scala/tk/mygod/preference/EditTextPreferenceDialogFragment.scala b/src/main/scala/tk/mygod/preference/EditTextPreferenceDialogFragment.scala
new file mode 100644
index 00000000..521ee1a9
--- /dev/null
+++ b/src/main/scala/tk/mygod/preference/EditTextPreferenceDialogFragment.scala
@@ -0,0 +1,30 @@
+package tk.mygod.preference
+
+import android.content.Context
+import android.support.v14.preference.PreferenceDialogFragment
+import android.view.{View, ViewGroup}
+
+class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
+ private lazy val preference = getPreference.asInstanceOf[EditTextPreference]
+ private lazy val editText = preference.editText
+
+ override protected def onCreateDialogView(context: Context) = {
+ val parent = editText.getParent.asInstanceOf[ViewGroup]
+ if (parent != null) parent.removeView(editText)
+ editText
+ }
+
+ override protected def onBindDialogView(view: View) {
+ super.onBindDialogView(view)
+ editText.setText(preference.getText)
+ val text = editText.getText
+ if (text != null) editText.setSelection(0, text.length)
+ }
+
+ override protected def needInputMethod = true
+
+ def onDialogClosed(positiveResult: Boolean) = if (positiveResult) {
+ val value = editText.getText.toString
+ if (preference.callChangeListener(value)) preference.setText(value)
+ }
+}
diff --git a/src/main/scala/com/github/shadowsocks/preferences/NumberPickerPreference.scala b/src/main/scala/tk/mygod/preference/NumberPickerPreference.scala
similarity index 52%
rename from src/main/scala/com/github/shadowsocks/preferences/NumberPickerPreference.scala
rename to src/main/scala/tk/mygod/preference/NumberPickerPreference.scala
index a253e0c4..05294277 100644
--- a/src/main/scala/com/github/shadowsocks/preferences/NumberPickerPreference.scala
+++ b/src/main/scala/tk/mygod/preference/NumberPickerPreference.scala
@@ -1,20 +1,15 @@
-package com.github.shadowsocks.preferences
+package tk.mygod.preference
import android.content.Context
import android.content.res.TypedArray
-import android.os.Bundle
-import android.preference.DialogPreference
+import android.support.v7.preference.DialogPreference
import android.util.AttributeSet
-import android.view.{ViewGroup, WindowManager}
import android.widget.NumberPicker
import com.github.shadowsocks.R
-/**
- * @author Mygod
- */
-final class NumberPickerPreference(context: Context, attrs: AttributeSet = null)
+class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null)
extends DialogPreference(context, attrs) with SummaryPreference {
- private val picker = new NumberPicker(context)
+ private[preference] val picker = new NumberPicker(context)
private var value: Int = _
{
@@ -28,7 +23,7 @@ final class NumberPickerPreference(context: Context, attrs: AttributeSet = null)
def getMin = if (picker == null) 0 else picker.getMinValue
def getMax = picker.getMaxValue
def setValue(i: Int) {
- if (i == getValue) return
+ if (i == value) return
picker.setValue(i)
value = picker.getValue
persistInt(value)
@@ -37,28 +32,8 @@ final class NumberPickerPreference(context: Context, attrs: AttributeSet = null)
def setMin(value: Int) = picker.setMinValue(value)
def setMax(value: Int) = picker.setMaxValue(value)
- override protected def showDialog(state: Bundle) {
- super.showDialog(state)
- getDialog.getWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- }
- override protected def onCreateDialogView = {
- val parent = picker.getParent.asInstanceOf[ViewGroup]
- if (parent != null) parent.removeView(picker)
- picker
- }
- override protected def onDialogClosed(positiveResult: Boolean) {
- picker.clearFocus // commit changes
- super.onDialogClosed(positiveResult) // forward compatibility
- if (positiveResult) {
- val value = picker.getValue
- if (callChangeListener(value)) {
- setValue(value)
- return
- }
- }
- picker.setValue(value)
- }
- override protected def onGetDefaultValue(a: TypedArray, index: Int) = a.getInt(index, getMin).asInstanceOf[AnyRef]
+ override protected def onGetDefaultValue(a: TypedArray, index: Int): AnyRef =
+ a.getInt(index, getMin).asInstanceOf[AnyRef]
override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
val default = defaultValue.asInstanceOf[Int]
setValue(if (restorePersistedValue) getPersistedInt(default) else default)
diff --git a/src/main/scala/tk/mygod/preference/NumberPickerPreferenceDialogFragment.scala b/src/main/scala/tk/mygod/preference/NumberPickerPreferenceDialogFragment.scala
new file mode 100644
index 00000000..e77cc883
--- /dev/null
+++ b/src/main/scala/tk/mygod/preference/NumberPickerPreferenceDialogFragment.scala
@@ -0,0 +1,36 @@
+package tk.mygod.preference
+
+import android.content.Context
+import android.support.v14.preference.PreferenceDialogFragment
+import android.view.inputmethod.InputMethodManager
+import android.view.{View, ViewGroup}
+
+class NumberPickerPreferenceDialogFragment extends PreferenceDialogFragment {
+ private lazy val preference = getPreference.asInstanceOf[NumberPickerPreference]
+ private lazy val picker = preference.picker
+ private lazy val inputMethodManager =
+ getActivity.getSystemService(Context.INPUT_METHOD_SERVICE).asInstanceOf[InputMethodManager]
+
+ override protected def onCreateDialogView(context: Context) = {
+ val parent = picker.getParent.asInstanceOf[ViewGroup]
+ if (parent != null) parent.removeView(picker)
+ picker
+ }
+
+ override protected def onBindDialogView(view: View) {
+ super.onBindDialogView(view)
+ picker.setValue(preference.getValue)
+ }
+
+ override protected def needInputMethod = true
+
+ def onDialogClosed(positiveResult: Boolean) {
+ picker.clearFocus // commit changes
+ if (positiveResult) {
+ val value = picker.getValue
+ if (preference.callChangeListener(value)) preference.setValue(value)
+ }
+ inputMethodManager.hideSoftInputFromWindow(getActivity.getCurrentFocus.getWindowToken,
+ InputMethodManager.HIDE_NOT_ALWAYS)
+ }
+}
diff --git a/src/main/scala/tk/mygod/preference/PreferenceCategory.scala b/src/main/scala/tk/mygod/preference/PreferenceCategory.scala
new file mode 100644
index 00000000..55e64e61
--- /dev/null
+++ b/src/main/scala/tk/mygod/preference/PreferenceCategory.scala
@@ -0,0 +1,18 @@
+package tk.mygod.preference
+
+import android.content.Context
+import android.support.v7.preference.{PreferenceViewHolder, PreferenceCategory => Base}
+import android.util.AttributeSet
+import android.view.ViewGroup.MarginLayoutParams
+
+/**
+ * Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/blob/4c2bb2896895dbedb37b659b36bd2a96b33c1605/preference-v7/src/main/java/com/takisoft/fix/support/v7/preference/PreferenceCategory.java
+ *
+ * @author Mygod
+ */
+class PreferenceCategory(context: Context, attrs: AttributeSet = null) extends Base(context, attrs) {
+ override def onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.findViewById(android.R.id.title).getLayoutParams.asInstanceOf[MarginLayoutParams].bottomMargin = 0
+ }
+}
diff --git a/src/main/scala/tk/mygod/preference/PreferenceFragment.scala b/src/main/scala/tk/mygod/preference/PreferenceFragment.scala
new file mode 100644
index 00000000..ae0f5e5f
--- /dev/null
+++ b/src/main/scala/tk/mygod/preference/PreferenceFragment.scala
@@ -0,0 +1,27 @@
+package tk.mygod.preference
+
+import android.app.DialogFragment
+import android.os.Bundle
+import android.support.v14.preference.{PreferenceFragment => Base}
+import android.support.v7.preference.PreferenceScreen
+import android.view.{LayoutInflater, ViewGroup}
+
+abstract class PreferenceFragment extends Base {
+ override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle) =
+ super.onCreateView(inflater, container, savedInstanceState)
+
+ protected final def displayPreferenceDialog(key: String, fragment: DialogFragment) {
+ val bundle = new Bundle(1)
+ bundle.putString("key", key)
+ fragment.setArguments(bundle)
+ fragment.setTargetFragment(this, 0)
+ fragment.show(getFragmentManager, "android.support.v14.preference.PreferenceFragment.DIALOG")
+ }
+
+ override protected def onCreateAdapter(screen: PreferenceScreen) = new PreferenceGroupAdapter(screen)
+
+ override def onResume {
+ super.onResume
+ getListView.scrollBy(0, 0)
+ }
+}
diff --git a/src/main/scala/tk/mygod/preference/PreferenceGroupAdapter.scala b/src/main/scala/tk/mygod/preference/PreferenceGroupAdapter.scala
new file mode 100644
index 00000000..e193a444
--- /dev/null
+++ b/src/main/scala/tk/mygod/preference/PreferenceGroupAdapter.scala
@@ -0,0 +1,65 @@
+package tk.mygod.preference
+
+import java.lang.reflect.Field
+import java.util.List
+
+import android.os.Build
+import android.support.v4.content.ContextCompat
+import android.support.v4.view.ViewCompat
+import android.support.v7.preference.{PreferenceGroup, PreferenceViewHolder, PreferenceGroupAdapter => Old}
+import android.view.{LayoutInflater, View, ViewGroup}
+import com.github.shadowsocks.R
+
+/**
+ * Fix by: https://github.com/Gericop/Android-Support-Preference-V7-Fix/commit/7de016b007e28a264001a8bb353f110a7f64bb69
+ *
+ * @author Mygod
+ */
+object PreferenceGroupAdapter {
+ private var preferenceLayoutsField: Field = _
+ private var fieldResId: Field = _
+ private var fieldWidgetResId: Field = _
+ private val preferenceViewHolderConstructor = classOf[PreferenceViewHolder].getDeclaredConstructor(classOf[View])
+
+ {
+ val oldClass = classOf[Old]
+ preferenceLayoutsField = oldClass.getDeclaredField("mPreferenceLayouts")
+ preferenceLayoutsField.setAccessible(true)
+ val c = oldClass.getDeclaredClasses.filter(c => c.getSimpleName == "PreferenceLayout").head
+ fieldResId = c.getDeclaredField("resId")
+ fieldResId.setAccessible(true)
+ fieldWidgetResId = c.getDeclaredField("widgetResId")
+ fieldWidgetResId.setAccessible(true)
+ preferenceViewHolderConstructor.setAccessible(true)
+ }
+}
+
+class PreferenceGroupAdapter(group: PreferenceGroup) extends Old(group) {
+ import PreferenceGroupAdapter._
+
+ protected lazy val preferenceLayouts = preferenceLayoutsField.get(this).asInstanceOf[List[AnyRef]]
+
+ override def onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (Build.VERSION.SDK_INT < 21) {
+ val context = parent.getContext
+ val inflater = LayoutInflater.from(context)
+ val pl = preferenceLayouts.get(viewType)
+ val view = inflater.inflate(fieldResId.get(pl).asInstanceOf[Int], parent, false)
+ if (view.getBackground == null) {
+ val array = context.obtainStyledAttributes(null, R.styleable.BackgroundStyle)
+ var background = array.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground)
+ if (background == null)
+ background = ContextCompat.getDrawable(context, android.R.drawable.list_selector_background)
+ array.recycle
+ val (s, t, e, b) = (ViewCompat.getPaddingStart(view), view.getPaddingTop,
+ ViewCompat.getPaddingEnd(view), view.getPaddingBottom)
+ view.setBackground(background)
+ ViewCompat.setPaddingRelative(view, s, t, e, b)
+ }
+ val widgetFrame = view.findViewById(android.R.id.widget_frame).asInstanceOf[ViewGroup]
+ if (widgetFrame != null) {
+ val widgetResId = fieldWidgetResId.get(pl).asInstanceOf[Int]
+ if (widgetResId != 0) inflater.inflate(widgetResId, widgetFrame) else widgetFrame.setVisibility(View.GONE)
+ }
+ preferenceViewHolderConstructor.newInstance(view)
+ } else super.onCreateViewHolder(parent, viewType)
+}
diff --git a/src/main/scala/tk/mygod/preference/SummaryPreference.scala b/src/main/scala/tk/mygod/preference/SummaryPreference.scala
new file mode 100755
index 00000000..bb01cbc1
--- /dev/null
+++ b/src/main/scala/tk/mygod/preference/SummaryPreference.scala
@@ -0,0 +1,25 @@
+package tk.mygod.preference
+
+import android.support.v7.preference.Preference
+
+/**
+ * Make your preference support %s in summary. Override getSummaryValue to customize what to put in.
+ * Based on:
+ * https://github.com/android/platform_frameworks_base/blob/master/core/java/android/preference/ListPreference.java
+ *
+ * @author Mygod
+ */
+trait SummaryPreference extends Preference {
+ protected def getSummaryValue: AnyRef
+
+ /**
+ * Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it
+ * (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place.
+ *
+ * @return the summary with appropriate string substitution
+ */
+ override def getSummary = {
+ val summary = super.getSummary
+ if (summary == null) null else String.format(summary.toString, getSummaryValue)
+ }
+}