Commit 20506eed authored by Mygod's avatar Mygod

Migrate to preference support library

Fix #875. Step 1 for #762.

Note: This will make main UI very different on 4.4.
parent dd63bda2
......@@ -15,6 +15,7 @@ gen_*
*.o
local.sbt
.deps
/proguard-sbt.txt
#Intellij IDEA
*.iml
......
......@@ -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",
......
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item>
<color android:color="@color/material_accent_300"/>
<color android:color="@color/material_primary_300"/>
</item>
</ripple>
<?xml version="1.0" encoding="utf-8"?>
<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/material_accent_300"/>
<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/material_primary_300"/>
......@@ -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"
......
......@@ -24,13 +24,9 @@
<string name="proxy_cat">Настройки Сервера</string>
<string name="profile_name">Имя Профиля</string>
<string name="proxy">Сервер</string>
<string name="proxy_summary">Имя или IP удалённого сервера</string>
<string name="remote_port">Удалённый порт</string>
<string name="remote_port_summary">%d (номер порта удалённого сервера)</string>
<string name="port">Локальный порт</string>
<string name="port_summary">%d (номер порта локального сервера)</string>
<string name="sitekey">Пароль</string>
<string name="sitekey_summary">Пароль удалённого сервера</string>
<string name="enc_method">Метод Шифрования</string>
<!-- feature category -->
......@@ -148,5 +144,4 @@
<string name="kcp_port">KCP порт</string>
<string name="kcp_port_summary">%d (KCPTUN омер порта удалённого сервера)</string>
<string name="kcpcli">KCP параметры</string>
<string name="kcpcli_summary">Командная строка KCPTUN</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Material" parent="Theme.MaterialBase"/>
</resources>
......@@ -9,13 +9,9 @@
<string name="profile">配置文件</string>
<string name="profile_summary">新建或切换到其他配置文件</string>
<string name="proxy">服务器</string>
<string name="proxy_summary">远程服务器的地址</string>
<string name="remote_port">远程端口</string>
<string name="remote_port_summary">%d(远程服务器的端口号)</string>
<string name="port">本地端口</string>
<string name="port_summary">%d(本地客户端的端口号)</string>
<string name="sitekey">密码</string>
<string name="sitekey_summary">远程服务器的密码</string>
<string name="enc_method">加密方法</string>
<string name="feature_cat">功能设置</string>
<string name="http_proxy">HTTP 代理服务器</string>
......@@ -142,6 +138,5 @@
<string name="kcp_port">KCP 端口</string>
<string name="kcp_port_summary">%d(远端 KCPTUN 服务器的端口号)</string>
<string name="kcpcli">KCP 参数</string>
<string name="kcpcli_summary">KCPTUN 的命令行参数</string>
</resources>
......@@ -10,13 +10,9 @@
<string name="profile">設定檔</string>
<string name="profile_summary">切換至其他設定檔或新增新設定檔</string>
<string name="proxy">伺服器</string>
<string name="proxy_summary">遠端伺服器的 IP 位址或主機名稱</string>
<string name="remote_port">遠端連接埠</string>
<string name="remote_port_summary">%d(遠端伺服器的連接埠號碼)</string>
<string name="port">連接埠號碼</string>
<string name="port_summary">%d(本機伺服器的連接埠號碼)</string>
<string name="sitekey">密碼</string>
<string name="sitekey_summary">遠端伺服器的密碼</string>
<string name="enc_method">加密方法</string>
<string name="feature_cat">功能設定</string>
<string name="http_proxy">HTTP Proxy</string>
......@@ -144,6 +140,5 @@
<string name="kcp_port">KCP 連接埠</string>
<string name="kcp_port_summary">%d(遠端 KCPTUN 伺服器的連接埠號碼)</string>
<string name="kcpcli">KCP 參數</string>
<string name="kcpcli_summary">KCPTUN 的命令行參數</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DropDownPreference">
<attr name="android:entries" />
<attr name="android:entryValues" />
</declare-styleable>
<declare-styleable name="NumberPickerPreference">
<attr name="max" format="integer" />
<attr name="min" format="integer" />
......
......@@ -6,8 +6,9 @@
<color name="material_blue_grey_300">#90A4AE</color>
<color name="material_blue_grey_500">#607D8B</color>
<color name="material_blue_grey_700">#455A64</color>
<color name="material_accent_300">@color/material_blue_grey_300</color>
<color name="material_accent_500">@color/material_blue_grey_500</color>
<color name="material_accent_700">@color/material_blue_grey_700</color>
<color name="material_primary_300">@color/material_blue_grey_300</color>
<color name="material_primary_500">@color/material_blue_grey_500</color>
<color name="material_primary_700">@color/material_blue_grey_700</color>
<color name="material_accent_200">@color/material_blue_grey_500</color>
</resources>
......@@ -29,13 +29,9 @@
<string name="proxy_cat">Server Settings</string>
<string name="profile_name">Profile Name</string>
<string name="proxy">Server</string>
<string name="proxy_summary">Hostname or IP of the remote server</string>
<string name="remote_port">Remote Port</string>
<string name="remote_port_summary">%d (port number of the remote server)</string>
<string name="port">Local Port</string>
<string name="port_summary">%d (port number of the local server)</string>
<string name="sitekey">Password</string>
<string name="sitekey_summary">Password of the remote server</string>
<string name="enc_method">Encrypt Method</string>
<!-- feature category -->
......@@ -155,5 +151,4 @@
<string name="kcp_port">KCP Port</string>
<string name="kcp_port_summary">%d (port number of the remote KCPTUN server)</string>
<string name="kcpcli">KCP Parameters (CLI)</string>
<string name="kcpcli_summary">The command line of KCPTUN</string>
</resources>
......@@ -6,20 +6,43 @@
<item name="android:textSize">24sp</item>
</style>
<style name="Theme.Material" parent="Theme.AppCompat.Light.NoActionBar">
<style name="Theme.MaterialBase" parent="Theme.AppCompat.Light.NoActionBar">
<!--suppress NewApi -->
<item name="android:navigationBarColor">@color/material_accent_700</item>
<item name="colorAccent">@color/material_accent_500</item>
<item name="colorButtonNormal">@color/material_accent_500</item>
<item name="colorPrimary">@color/material_accent_500</item>
<item name="colorPrimaryDark">@color/material_accent_700</item>
<item name="android:navigationBarColor">@color/material_primary_700</item>
<item name="colorAccent">@color/material_primary_500</item>
<item name="colorButtonNormal">@color/material_primary_500</item>
<item name="colorPrimary">@color/material_primary_500</item>
<item name="colorPrimaryDark">@color/material_primary_700</item>
<item name="preferenceTheme">@style/Theme.Material.PreferenceThemeOverlay</item>
</style>
<style name="Theme.Material.Dialog" parent="Theme.AppCompat.Light.Dialog">
<item name="colorAccent">@color/material_accent_500</item>
<item name="colorPrimary">@color/material_accent_500</item>
<item name="colorPrimaryDark">@color/material_accent_700</item>
<item name="colorAccent">@color/material_primary_500</item>
<item name="colorButtonNormal">@color/material_accent_200</item>
<item name="colorPrimary">@color/material_primary_500</item>
<item name="colorPrimaryDark">@color/material_primary_700</item>
<item name="preferenceTheme">@style/Theme.Material.PreferenceThemeOverlay</item>
<item name="windowNoTitle">true</item>
</style>
<!-- Fix for wrong padding on v17-v20 based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/blob/a6082cb/preference-v7/src/main/res/values-v17/styles.xml -->
<style name="Theme.Material" parent="Theme.MaterialBase">
<item name="android:listPreferredItemPaddingStart">16dp</item>
<item name="android:listPreferredItemPaddingEnd">16dp</item>
</style>
<!-- This is needed because the default PreferenceCategory appearance doesn't use bold text style -->
<style name="Preference_TextAppearanceMaterialBody2">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="Theme.Material.PreferenceThemeOverlay" parent="@style/PreferenceThemeOverlay.v14.Material">
<item name="preferenceFragmentListStyle">@style/Theme.Material.PreferenceFragmentList</item>
</style>
<style name="Theme.Material.PreferenceFragmentList" parent="@style/PreferenceFragmentList.Material">
<item name="android:fadeScrollbars">true</item>
<item name="android:scrollbars">vertical</item>
</style>
</resources>
......@@ -2,39 +2,40 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<PreferenceCategory
<tk.mygod.preference.PreferenceCategory
android:title="@string/proxy_cat">
<com.github.shadowsocks.preferences.SummaryEditTextPreference
<tk.mygod.preference.EditTextPreference
android:key="profileName"
android:persistent="false"
android:summary="%s"
android:title="@string/profile_name"/>
<com.github.shadowsocks.preferences.SummaryEditTextPreference
<tk.mygod.preference.EditTextPreference
android:key="proxy"
android:persistent="false"
android:summary="@string/proxy_summary"
android:summary="%s"
android:title="@string/proxy"/>
<com.github.shadowsocks.preferences.NumberPickerPreference
<tk.mygod.preference.NumberPickerPreference
app:min="1"
app:max="65535"
android:key="remotePortNum"
android:persistent="false"
android:summary="@string/remote_port_summary"
android:summary="%d"
android:title="@string/remote_port"/>
<com.github.shadowsocks.preferences.NumberPickerPreference
<tk.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
android:key="localPortNum"
android:persistent="false"
android:summary="@string/port_summary"
android:summary="%d"
android:title="@string/port"/>
<com.github.shadowsocks.preferences.PasswordEditTextPreference
<tk.mygod.preference.EditTextPreference
android:inputType="textPassword"
android:key="sitekey"
android:persistent="false"
android:summary="@string/sitekey_summary"
android:summary="%s"
android:title="@string/sitekey"/>
<com.github.shadowsocks.preferences.DropDownPreference
<android.support.v7.preference.DropDownPreference
android:key="encMethod"
android:persistent="false"
android:entries="@array/enc_method_entry"
......@@ -47,12 +48,12 @@
android:summary="@string/onetime_auth_summary"
android:title="@string/onetime_auth"/>
</PreferenceCategory>
</tk.mygod.preference.PreferenceCategory>
<PreferenceCategory
<tk.mygod.preference.PreferenceCategory
android:title="@string/feature_cat">
<com.github.shadowsocks.preferences.DropDownPreference
<android.support.v7.preference.DropDownPreference
android:key="route"
android:persistent="false"
android:entries="@array/route_entry"
......@@ -75,9 +76,9 @@
android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns"/>
</PreferenceCategory>
</tk.mygod.preference.PreferenceCategory>
<PreferenceCategory
<tk.mygod.preference.PreferenceCategory
android:title="@string/kcp">
<SwitchPreference
......@@ -86,7 +87,7 @@
android:summary="@string/kcp_summary"
android:title="@string/kcp"/>
<com.github.shadowsocks.preferences.NumberPickerPreference
<tk.mygod.preference.NumberPickerPreference
app:min="1"
app:max="65535"
android:key="kcpPort"
......@@ -94,15 +95,15 @@
android:summary="@string/kcp_port_summary"
android:title="@string/kcp_port"/>
<com.github.shadowsocks.preferences.KcpCliPreference
<tk.mygod.preference.EditTextPreference
android:key="kcpcli"
android:persistent="false"
android:summary="@string/kcpcli_summary"
android:summary="%s"
android:title="@string/kcpcli"/>
</PreferenceCategory>
</tk.mygod.preference.PreferenceCategory>
<PreferenceCategory android:title="@string/misc_cat">
<tk.mygod.preference.PreferenceCategory android:title="@string/misc_cat">
<SwitchPreference
android:key="isAutoConnect"
......@@ -125,7 +126,7 @@
android:summary="@string/flush_dnscache_summary"/>
<Preference android:key="about" android:title="@string/about"/>
</PreferenceCategory>
</tk.mygod.preference.PreferenceCategory>
</PreferenceScreen>
......@@ -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])
......
......@@ -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
......
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)
}
}
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)
......
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2013 <max.c.lv@gmail.com>
*
* 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/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (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
}
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2013 <max.c.lv@gmail.com>
*
* 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/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (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
}
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)
}
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
}
}
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)
}
}
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)
......
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)
}
}
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
}
}
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)
}
}
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)
}
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)
}
}
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