Migrate to preference support library

Fix #875. Step 1 for #762.

Note: This will make main UI very different on 4.4.
This commit is contained in:
Mygod
2016-09-23 00:30:39 +08:00
parent dd63bda2cd
commit 20506eeda8
29 changed files with 345 additions and 452 deletions
@@ -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
@@ -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)
}
}
@@ -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)
@@ -1,67 +0,0 @@
package com.github.shadowsocks.preferences
import android.content.Context
import android.content.res.TypedArray
import android.os.Bundle
import android.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)
extends DialogPreference(context, attrs) with SummaryPreference {
private val picker = new NumberPicker(context)
private var value: Int = _
{
val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference)
setMin(a.getInt(R.styleable.NumberPickerPreference_min, 0))
setMax(a.getInt(R.styleable.NumberPickerPreference_max, Int.MaxValue - 1))
a.recycle
}
def getValue = value
def getMin = if (picker == null) 0 else picker.getMinValue
def getMax = picker.getMaxValue
def setValue(i: Int) {
if (i == getValue) return
picker.setValue(i)
value = picker.getValue
persistInt(value)
notifyChanged
}
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 onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
val default = defaultValue.asInstanceOf[Int]
setValue(if (restorePersistedValue) getPersistedInt(default) else default)
}
protected def getSummaryValue: AnyRef = getValue.asInstanceOf[AnyRef]
}
@@ -1,68 +0,0 @@
/*
* 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
}
@@ -1,69 +0,0 @@
/*
* 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
}
@@ -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)
}