From 20506eeda8095416c19eb4102f8dc786dc465c35 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 23 Sep 2016 00:30:39 +0800 Subject: [PATCH] Migrate to preference support library Fix #875. Step 1 for #762. Note: This will make main UI very different on 4.4. --- .gitignore | 1 + build.sbt | 1 + src/main/res/drawable-v21/background_stat.xml | 2 +- src/main/res/drawable/background_stat.xml | 2 +- src/main/res/layout/layout_profiles.xml | 20 +-- src/main/res/values-ru/strings.xml | 5 - src/main/res/values-v21/styles.xml | 4 + src/main/res/values-zh-rCN/strings.xml | 5 - src/main/res/values-zh/strings.xml | 5 - src/main/res/values/attrs.xml | 4 - src/main/res/values/colors.xml | 7 +- src/main/res/values/strings.xml | 5 - src/main/res/values/styles.xml | 41 ++++- src/main/res/xml/pref_all.xml | 45 ++--- .../shadowsocks/ShadowsocksNotification.scala | 2 +- .../shadowsocks/ShadowsocksSettings.scala | 36 ++-- .../preferences/DropDownPreference.scala | 169 ------------------ ...a => KcpCliPreferenceDialogFragment.scala} | 13 +- .../PasswordEditTextPreference.scala | 68 ------- .../SummaryEditTextPreference.scala | 69 ------- .../preferences/SummaryPreference.scala | 21 --- .../mygod/preference/EditTextPreference.scala | 32 ++++ .../EditTextPreferenceDialogFragment.scala | 30 ++++ .../preference}/NumberPickerPreference.scala | 39 +--- ...NumberPickerPreferenceDialogFragment.scala | 36 ++++ .../mygod/preference/PreferenceCategory.scala | 18 ++ .../mygod/preference/PreferenceFragment.scala | 27 +++ .../preference/PreferenceGroupAdapter.scala | 65 +++++++ .../mygod/preference/SummaryPreference.scala | 25 +++ 29 files changed, 345 insertions(+), 452 deletions(-) create mode 100644 src/main/res/values-v21/styles.xml delete mode 100644 src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala rename src/main/scala/com/github/shadowsocks/preferences/{KcpCliPreference.scala => KcpCliPreferenceDialogFragment.scala} (70%) delete mode 100644 src/main/scala/com/github/shadowsocks/preferences/PasswordEditTextPreference.scala delete mode 100644 src/main/scala/com/github/shadowsocks/preferences/SummaryEditTextPreference.scala delete mode 100644 src/main/scala/com/github/shadowsocks/preferences/SummaryPreference.scala create mode 100644 src/main/scala/tk/mygod/preference/EditTextPreference.scala create mode 100644 src/main/scala/tk/mygod/preference/EditTextPreferenceDialogFragment.scala rename src/main/scala/{com/github/shadowsocks/preferences => tk/mygod/preference}/NumberPickerPreference.scala (52%) create mode 100644 src/main/scala/tk/mygod/preference/NumberPickerPreferenceDialogFragment.scala create mode 100644 src/main/scala/tk/mygod/preference/PreferenceCategory.scala create mode 100644 src/main/scala/tk/mygod/preference/PreferenceFragment.scala create mode 100644 src/main/scala/tk/mygod/preference/PreferenceGroupAdapter.scala create mode 100755 src/main/scala/tk/mygod/preference/SummaryPreference.scala 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/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) + } +}