From 8b967a6d88cb344864176c136a8ff5bbd20d9568 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 00:11:00 +0800 Subject: [PATCH 01/14] Change fab color --- src/main/res/layout/layout_main.xml | 9 +++++---- src/main/res/values/colors.xml | 2 +- src/main/res/values/colors_shadow.xml | 23 ----------------------- 3 files changed, 6 insertions(+), 28 deletions(-) delete mode 100644 src/main/res/values/colors_shadow.xml diff --git a/src/main/res/layout/layout_main.xml b/src/main/res/layout/layout_main.xml index fa382e87..74a6db30 100644 --- a/src/main/res/layout/layout_main.xml +++ b/src/main/res/layout/layout_main.xml @@ -1,14 +1,15 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" android:layout_height="match_parent" + tools:ignore="UnknownIdInLayout"> + android:layout_width="match_parent" android:layout_height="match_parent"/> #00000000 #FF555555 - #2196F3 + #43A047 #90A4AE #607D8B #455A64 diff --git a/src/main/res/values/colors_shadow.xml b/src/main/res/values/colors_shadow.xml deleted file mode 100644 index 87e9fcf5..00000000 --- a/src/main/res/values/colors_shadow.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - #CC33B5E5 - #EEE - From 136a15354670de2eac5c04439600d4c53e398a31 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 00:17:25 +0800 Subject: [PATCH 02/14] Add notification color --- .../scala/com/github/shadowsocks/ShadowsocksNatService.scala | 2 ++ .../scala/com/github/shadowsocks/ShadowsocksVpnService.scala | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 8ca63951..6029337c 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -50,6 +50,7 @@ import android.content.pm.{PackageInfo, PackageManager} import android.net.{Network, ConnectivityManager} import android.os._ import android.support.v4.app.NotificationCompat +import android.support.v4.content.ContextCompat import android.util.{SparseArray, Log} import android.widget.Toast import com.github.shadowsocks.aidl.Config @@ -319,6 +320,7 @@ class ShadowsocksNatService extends Service with BaseService { builder .setWhen(0) + .setColor(ContextCompat.getColor(this, R.color.material_accent_500)) .setTicker(title) .setContentTitle(getString(R.string.app_name)) .setContentText(info) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index d7e11665..72e9449d 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -48,6 +48,7 @@ import android.content.pm.{PackageInfo, PackageManager} import android.net.VpnService import android.os._ import android.support.v4.app.NotificationCompat +import android.support.v4.content.ContextCompat import android.util.Log import android.widget.Toast import com.github.shadowsocks.aidl.Config @@ -113,6 +114,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { builder .setWhen(0) + .setColor(ContextCompat.getColor(this, R.color.material_accent_500)) .setTicker(title) .setContentTitle(getString(R.string.app_name)) .setContentText(info) From 46fd69db5a02f69dd8af61bbff9d0ea6c049c590 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 00:42:01 +0800 Subject: [PATCH 03/14] Change checkboxes to switches to match system settings --- src/main/res/layout/layout_apps.xml | 34 ++++++++++--------- src/main/res/layout/layout_apps_item.xml | 2 +- src/main/res/xml/pref_all.xml | 20 +++++------ .../com/github/shadowsocks/AppManager.scala | 22 ++++++------ 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/main/res/layout/layout_apps.xml b/src/main/res/layout/layout_apps.xml index 0f02c21a..f84d4bbd 100644 --- a/src/main/res/layout/layout_apps.xml +++ b/src/main/res/layout/layout_apps.xml @@ -1,35 +1,37 @@ - - + + + android:text="@string/bypass_apps"/> - + android:layout_marginEnd="6dp" + android:layout_marginRight="6dp"/> + - - - - + - + - - - + - - + - + diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index d6c16be8..f75a9c94 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -78,7 +78,7 @@ class ObjectArrayTools[T <: AnyRef](a: Array[T]) { } } -case class ListEntry(box: CheckBox, text: TextView, icon: ImageView) +case class ListEntry(switch: Switch, text: TextView, icon: ImageView) object AppManager { @@ -162,13 +162,13 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe if (convertView == null) { convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false) val icon = convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView] - val box = convertView.findViewById(R.id.itemcheck).asInstanceOf[CheckBox] + val switch = convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch] val text = convertView.findViewById(R.id.itemtext).asInstanceOf[TextView] - entry = new ListEntry(box, text, icon) + entry = new ListEntry(switch, text, icon) entry.text.setOnClickListener(AppManager.this) entry.text.setOnClickListener(AppManager.this) convertView.setTag(entry) - entry.box.setOnCheckedChangeListener(AppManager.this) + entry.switch.setOnCheckedChangeListener(AppManager.this) } else { entry = convertView.getTag.asInstanceOf[ListEntry] } @@ -187,10 +187,10 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe ImageLoader.getInstance().displayImage(Scheme.APP + app.packageName, entry.icon, options) entry.text.setText(app.name) - val box: CheckBox = entry.box - box.setTag(app) - box.setChecked(app.proxied) - entry.text.setTag(box) + val switch = entry.switch + switch.setTag(app) + switch.setChecked(app.proxied) + entry.text.setTag(switch) convertView } } @@ -207,11 +207,11 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe } def onClick(v: View) { - val cbox: CheckBox = v.getTag.asInstanceOf[CheckBox] - val app: ProxiedApp = cbox.getTag.asInstanceOf[ProxiedApp] + val switch = v.getTag.asInstanceOf[Switch] + val app: ProxiedApp = switch.getTag.asInstanceOf[ProxiedApp] if (app != null) { app.proxied = !app.proxied - cbox.setChecked(app.proxied) + switch.setChecked(app.proxied) } saveAppSettings(this) } From 0f0c9edb6822ed736c655ac21660962778c887a2 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 00:48:51 +0800 Subject: [PATCH 04/14] Remove redundant SummaryListPreference --- src/main/res/values-zh/strings.xml | 1 - src/main/res/values/strings.xml | 1 - src/main/res/xml/pref_all.xml | 11 ++-- .../preferences/SummaryListPreference.scala | 66 ------------------- 4 files changed, 6 insertions(+), 73 deletions(-) delete mode 100644 src/main/scala/com/github/shadowsocks/preferences/SummaryListPreference.scala diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index c9457d6f..809d3219 100755 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -26,7 +26,6 @@ 国内路由 绕过所有位于中国的网站 路由 - 绕过指定路由列表中的地址 全局代理 设置系统代理 分应用代理 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 7b8660ee..a7f1f2c7 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -37,7 +37,6 @@ Bypass all sites located in China Route - Bypass IP addresses in the route list Global Proxy Set up system-wide proxy Per-App Proxy diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml index fbac2c70..95738358 100644 --- a/src/main/res/xml/pref_all.xml +++ b/src/main/res/xml/pref_all.xml @@ -37,27 +37,28 @@ android:summary="@string/sitekey_summary" android:title="@string/sitekey"> - - + - - + - * - * 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.preference.ListPreference -import android.content.Context -import android.util.AttributeSet - -class SummaryListPreference(context: Context, attrs: AttributeSet) - extends ListPreference(context, attrs) { - - override def setValue(text: String) { - super.setValue(text) - val entry = getEntry - if (entry != null) - setSummary(entry) - else - setSummary(text) - } - - override def setSummary(summary: CharSequence) { - if (summary == null || summary.toString.isEmpty) { - super.setSummary("") - } else { - super.setSummary(summary) - } - } - -} From 8d4a1ad6a3d625e0ea40a24530ab4775b797dc54 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 01:12:06 +0800 Subject: [PATCH 05/14] DropDownPreference --- src/main/res/values/attrs.xml | 10 + src/main/res/xml/pref_all.xml | 23 +-- .../preferences/DropDownPreference.scala | 179 ++++++++++++++++++ .../preferences/SummaryPreference.scala | 45 +++++ 4 files changed, 246 insertions(+), 11 deletions(-) create mode 100644 src/main/res/values/attrs.xml create mode 100644 src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala create mode 100644 src/main/scala/com/github/shadowsocks/preferences/SummaryPreference.scala diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml new file mode 100644 index 00000000..ca0adbc5 --- /dev/null +++ b/src/main/res/values/attrs.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml index 95738358..c4df91c0 100644 --- a/src/main/res/xml/pref_all.xml +++ b/src/main/res/xml/pref_all.xml @@ -1,5 +1,6 @@ - + @@ -37,28 +38,28 @@ android:summary="@string/sitekey_summary" android:title="@string/sitekey"> - - + - - + { + mSpinner.setDropDownVerticalOffset(Utils.dpToPx(getContext, 8 - 48 * mSelectedIndex).toInt) // TODO: scrolling? + mSpinner.performClick + true + }) + initSummary(mContext, attrs) + val a: TypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.DropDownPreference) + setEntries(a.getTextArray(R.styleable.DropDownPreference_entries)) + mEntryValues = a.getTextArray(R.styleable.DropDownPreference_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(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(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 + + /** + * 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) { + val i = findIndexOfValue(value) + if (i > -1) setValueIndex(i) + } + + /** + * Sets the value to the given index from the entry values. + * + * @param index The index of the value to set. + */ + def setValueIndex(index: Int) { + val changed = mSelectedIndex != index + if (changed || !mValueSet) { + mSelectedIndex = index + mValueSet = true + if (mEntryValues != null) { + val value = mEntryValues(index).toString + persistString(value) + setValue(value) + } + if (changed) notifyChanged + } + mSpinner.setSelection(index) + } + + /** + * 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) 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]) + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + def setDropDownWidth(dimenResId: Int) = + mSpinner.setDropDownWidth(mContext.getResources.getDimensionPixelSize(dimenResId)) + + protected override def onBindView(@NonNull view: View) { + super.onBindView(view) + if (view == mSpinner.getParent) return + if (mSpinner.getParent != null) mSpinner.getParent.asInstanceOf[ViewGroup].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/SummaryPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/SummaryPreference.scala new file mode 100644 index 00000000..56523494 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/preferences/SummaryPreference.scala @@ -0,0 +1,45 @@ +package com.github.shadowsocks.preferences + +import android.content.Context +import android.preference.Preference +import android.util.AttributeSet +import com.github.shadowsocks.R + +/** + * Make your preference support %s in summary. Override getSummaryValue to customize what to put in. + * Based on: https://github.com/Mygod/mygod-lib-android/blob/eb20c57a0da70bb5dbc27104002f20fb1eb31d76/lib/src/main/scala/tk/mygod/preference/SummaryPreference.scala + * @author Mygod + */ +trait SummaryPreference extends Preference { + private var mSummary: String = _ + + protected def initSummary(context: Context, attrs: AttributeSet) { + val a = context.obtainStyledAttributes(attrs, R.styleable.SummaryPreference) + mSummary = a.getString(R.styleable.SummaryPreference_summary) + a.recycle + } + + 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 entry = getSummaryValue + if (mSummary == null || entry == null) super.getSummary else String.format(mSummary, entry) + } + /** + * Sets the summary for this Preference with a CharSequence. 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 when it's retrieved. + * + * @param summary The summary for the preference. + */ + override def setSummary(summary: CharSequence) { + super.setSummary(summary) + if (summary == null && mSummary != null) mSummary = null + else if (summary != null && summary != mSummary) mSummary = summary.toString + } +} From f67695a687ce9ff951a1fce8dce5b0b173541a6f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 01:15:03 +0800 Subject: [PATCH 06/14] Replace CheckBoxes with Switches --- .../scala/com/github/shadowsocks/Shadowsocks.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index a10b19d6..c91edd63 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -151,8 +151,8 @@ object Shadowsocks { pref.asInstanceOf[ProfileEditTextPreference].setText(value) } - def updateCheckBoxPreference(pref: Preference, value: Boolean) { - pref.asInstanceOf[CheckBoxPreference].setChecked(value) + def updateSwitchPreference(pref: Preference, value: Boolean) { + pref.asInstanceOf[SwitchPreference].setChecked(value) } def updatePreference(pref: Preference, name: String, profile: Profile) { @@ -164,10 +164,10 @@ object Shadowsocks { case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password) case Key.encMethod => updateListPreference(pref, profile.method) case Key.route => updateListPreference(pref, profile.route) - case Key.isGlobalProxy => updateCheckBoxPreference(pref, profile.global) - case Key.isUdpDns => updateCheckBoxPreference(pref, profile.udpdns) - case Key.isAuth => updateCheckBoxPreference(pref, profile.auth) - case Key.isIpv6 => updateCheckBoxPreference(pref, profile.ipv6) + case Key.isGlobalProxy => updateSwitchPreference(pref, profile.global) + case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns) + case Key.isAuth => updateSwitchPreference(pref, profile.auth) + case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6) case _ => } } From 65fba3ff51dd2b27fb29a8b19abfe1cac0f8a0dd Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 01:34:55 +0800 Subject: [PATCH 07/14] Bugfix for DropDownPreference --- .../scala/com/github/shadowsocks/Shadowsocks.scala | 14 +++++++------- .../preferences/DropDownPreference.scala | 9 +++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index c91edd63..e9250b23 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -50,17 +50,17 @@ import android.content.res.AssetManager import android.graphics.{Bitmap, Color, Typeface} import android.net.{Uri, VpnService} import android.os._ -import android.preference._ +import android.preference.{PreferenceManager, SwitchPreference, Preference} import android.support.design.widget.{FloatingActionButton, Snackbar} import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.util.{DisplayMetrics, Log} -import android.view._ +import android.view.{View, ViewGroup, ViewParent} import android.webkit.{WebView, WebViewClient} import android.widget._ import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.database._ -import com.github.shadowsocks.preferences.{PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference} +import com.github.shadowsocks.preferences.{DropDownPreference, PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference} import com.github.shadowsocks.utils._ import com.google.android.gms.ads.{AdRequest, AdSize, AdView} import com.google.android.gms.analytics.HitBuilders @@ -132,8 +132,8 @@ object Shadowsocks { val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL, Executable.TUN2SOCKS) // Helper functions - def updateListPreference(pref: Preference, value: String) { - pref.asInstanceOf[ListPreference].setValue(value) + def updateDropDownPreference(pref: Preference, value: String) { + pref.asInstanceOf[DropDownPreference].setValue(value) } def updatePasswordEditTextPreference(pref: Preference, value: String) { @@ -162,8 +162,8 @@ object Shadowsocks { case Key.remotePort => updateSummaryEditTextPreference(pref, profile.remotePort.toString) case Key.localPort => updateSummaryEditTextPreference(pref, profile.localPort.toString) case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password) - case Key.encMethod => updateListPreference(pref, profile.method) - case Key.route => updateListPreference(pref, profile.route) + case Key.encMethod => updateDropDownPreference(pref, profile.method) + case Key.route => updateDropDownPreference(pref, profile.route) case Key.isGlobalProxy => updateSwitchPreference(pref, profile.global) case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns) case Key.isAuth => updateSwitchPreference(pref, profile.auth) diff --git a/src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala index 5df9c1ae..142e384a 100644 --- a/src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala +++ b/src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala @@ -12,7 +12,6 @@ import android.view.{View, ViewGroup} import android.widget.AdapterView.OnItemSelectedListener import android.widget.{AdapterView, ArrayAdapter} import com.github.shadowsocks.R -import com.github.shadowsocks.utils.Utils /** * Based on: https://github.com/Mygod/mygod-lib-android/blob/683c41cb86d10e99ab0f457d41a218c6915fccc8/lib/src/main/scala/tk/mygod/preference/DropDownPreference.scala @@ -35,7 +34,8 @@ final class DropDownPreference(private val mContext: Context, attrs: AttributeSe override def onItemSelected(parent: AdapterView[_], view: View, position: Int, id: Long) = setValueIndex(position) }) setOnPreferenceClickListener((preference: Preference) => { - mSpinner.setDropDownVerticalOffset(Utils.dpToPx(getContext, 8 - 48 * mSelectedIndex).toInt) // TODO: scrolling? + // TODO: not working with scrolling + // mSpinner.setDropDownVerticalOffset(Utils.dpToPx(getContext, -48 * mSelectedIndex).toInt) mSpinner.performClick true }) @@ -169,8 +169,9 @@ final class DropDownPreference(private val mContext: Context, attrs: AttributeSe protected override def onBindView(@NonNull view: View) { super.onBindView(view) - if (view == mSpinner.getParent) return - if (mSpinner.getParent != null) mSpinner.getParent.asInstanceOf[ViewGroup].removeView(mSpinner) + 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 From 2fa97be12de68f9c54629ed203b9df488b84445f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 16:14:45 +0800 Subject: [PATCH 08/14] Add FABProgressCircle --- build.sbt | 1 + src/main/res/layout/layout_main.xml | 29 ++++++------ .../com/github/shadowsocks/Shadowsocks.scala | 46 +++++++++---------- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/build.sbt b/build.sbt index 2194c008..3abae4dd 100644 --- a/build.sbt +++ b/build.sbt @@ -37,6 +37,7 @@ libraryDependencies ++= Seq( "com.google.android.gms" % "play-services-analytics" % "8.1.0", "com.android.support" % "support-v4" % "23.1.0", "com.android.support" % "design" % "23.1.0", + "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01", "com.nostra13.universalimageloader" % "universal-image-loader" % "1.9.4", "com.j256.ormlite" % "ormlite-core" % "4.48", "com.j256.ormlite" % "ormlite-android" % "4.48" diff --git a/src/main/res/layout/layout_main.xml b/src/main/res/layout/layout_main.xml index 74a6db30..e73a5eae 100644 --- a/src/main/res/layout/layout_main.xml +++ b/src/main/res/layout/layout_main.xml @@ -2,21 +2,24 @@ + tools:ignore="UnknownIdInLayout" android:clipChildren="false"> - + + + diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index e9250b23..74ff0780 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -50,7 +50,7 @@ import android.content.res.AssetManager import android.graphics.{Bitmap, Color, Typeface} import android.net.{Uri, VpnService} import android.os._ -import android.preference.{PreferenceManager, SwitchPreference, Preference} +import android.preference.{Preference, PreferenceManager, SwitchPreference} import android.support.design.widget.{FloatingActionButton, Snackbar} import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar @@ -58,6 +58,7 @@ import android.util.{DisplayMetrics, Log} import android.view.{View, ViewGroup, ViewParent} import android.webkit.{WebView, WebViewClient} import android.widget._ +import com.github.jorgecastilloprz.FABProgressCircle import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.database._ import com.github.shadowsocks.preferences.{DropDownPreference, PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference} @@ -186,6 +187,7 @@ class Shadowsocks // Variables private var serviceStarted = false var fab: FloatingActionButton = _ + var fabProgressCircle: FABProgressCircle = _ var progressDialog: ProgressDialog = _ var progressTag = -1 var state = State.INIT @@ -394,7 +396,6 @@ class Shadowsocks } def prepareStartService() { - showProgress(R.string.connecting) Future { if (isVpnEnabled) { val intent = VpnService.prepare(this) @@ -485,6 +486,7 @@ class Shadowsocks val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf") if (tf != null) title.setTypeface(tf) fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton] + fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle] fab.setOnClickListener((v: View) => { serviceStarted = !serviceStarted serviceStarted match { @@ -495,10 +497,12 @@ class Shadowsocks changeSwitch(checked = false) case false => serviceStop() - } - if (fab.isEnabled) { - fab.setEnabled(false) - handler.postDelayed(() => fab.setEnabled(true), 1000) + if (fab.isEnabled) { + fab.setEnabled(false) + handler.postDelayed(() => { + fab.setEnabled(true) + }, 1000) + } } }) toolbar.setNavigationIcon(R.drawable.ic_drawer) @@ -1015,36 +1019,30 @@ class Shadowsocks def onStateChanged(s: Int, m: String) { handler.post(() => if (state != s) { - state = s - state match { + s match { case State.CONNECTING => - if (progressDialog == null) { - progressDialog = ProgressDialog - .show(Shadowsocks.this, "", getString(R.string.connecting), true, true) - progressTag = R.string.connecting - } + fab.setImageResource(R.drawable.ic_cloud_queue) + fab.setEnabled(false) + fabProgressCircle.show() setPreferenceEnabled(enabled = false) case State.CONNECTED => - if (progressTag == R.string.connecting) { - clearDialog() - } + if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation() + fab.setEnabled(true) changeSwitch(checked = true) setPreferenceEnabled(enabled = false) case State.STOPPED => - if (progressTag == R.string.stopping || progressTag == R.string.connecting) { - clearDialog() - } + if (state == State.STOPPING || state == State.CONNECTING) fabProgressCircle.hide() + fab.setEnabled(true) changeSwitch(checked = false) if (m != null) Snackbar.make(getWindow.getDecorView.findViewById(android.R.id.content), getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG).show setPreferenceEnabled(enabled = true) case State.STOPPING => - if (progressDialog == null) { - progressDialog = ProgressDialog - .show(Shadowsocks.this, "", getString(R.string.stopping), true, true) - progressTag = R.string.stopping - } + fab.setImageResource(R.drawable.ic_cloud_queue) + fab.setEnabled(false) + fabProgressCircle.show() } + state = s }) } From 9966368ad64a8e92fe43f6274257567898ef1469 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 16:40:54 +0800 Subject: [PATCH 09/14] Add long press toast for FAB --- src/main/res/values-zh/strings.xml | 2 +- src/main/res/values/strings.xml | 2 +- .../com/github/shadowsocks/Shadowsocks.scala | 5 +++++ .../com/github/shadowsocks/utils/Utils.scala | 22 ++++++++++++++++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index 809d3219..195090d5 100755 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -53,7 +53,7 @@ 端口号不能为空 代理服务器地址不能为空 检测到意外退出,状态已被重置。 - 正在连接... + 连接 正在初始化... 重置中... 正在加载... diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index a7f1f2c7..0fa48668 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -74,7 +74,7 @@ Proxy should not be empty An unexpected exit detected, the context has been reset. - Connecting… + Connect Initializing… Resetting… Loading… diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 74ff0780..3763031b 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -505,6 +505,11 @@ class Shadowsocks } } }) + fab.setOnLongClickListener((v: View) => { + Utils.positionToast(Toast.makeText(this, if (serviceStarted) R.string.stop else R.string.connect, + Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8).toInt).show + true + }) toolbar.setNavigationIcon(R.drawable.ic_drawer) toolbar.setNavigationOnClickListener((v: View) => drawer.toggleMenu()) title.setOnLongClickListener((v: View) => { diff --git a/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/src/main/scala/com/github/shadowsocks/utils/Utils.scala index e455effc..f9f7fc2b 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -50,7 +50,10 @@ import android.graphics.drawable.{BitmapDrawable, Drawable} import android.os.Build import android.provider.Settings import android.support.v4.content.ContextCompat -import android.util.{Base64, Log} +import android.util.{DisplayMetrics, Base64, Log} +import android.view.View.MeasureSpec +import android.view.{Gravity, View, Window} +import android.widget.Toast import com.github.shadowsocks.BuildConfig import org.xbill.DNS._ @@ -126,6 +129,23 @@ object Utils { bitmap } + // Based on: http://stackoverflow.com/a/21026866/2245107 + def positionToast(toast: Toast, view: View, window: Window, offsetX: Int = 0, offsetY: Int = 0) = { + val rect = new Rect + window.getDecorView.getWindowVisibleDisplayFrame(rect) + val viewLocation = new Array[Int](2) + view.getLocationInWindow(viewLocation) + val metrics = new DisplayMetrics + window.getWindowManager.getDefaultDisplay.getMetrics(metrics) + val toastView = toast.getView + toastView.measure(MeasureSpec.makeMeasureSpec(metrics.widthPixels, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(metrics.heightPixels, MeasureSpec.UNSPECIFIED)) + toast.setGravity(Gravity.LEFT | Gravity.TOP, + viewLocation(0) - rect.left + (view.getWidth - toast.getView.getMeasuredWidth) / 2 + offsetX, + viewLocation(1) - rect.top + view.getHeight + offsetY) + toast + } + // Blocked > 3 seconds def toggleAirplaneMode(context: Context) { if (Build.VERSION.SDK_INT >= 17) { From 232846a4162d985c1c9e8e3ba2531d9d0f6c652a Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 16:49:24 +0800 Subject: [PATCH 10/14] Remove old drawer icons --- src/main/res/drawable-hdpi-v21/ic_drawer.png | Bin 2864 -> 0 bytes src/main/res/drawable-hdpi/ic_drawer.png | Bin 963 -> 2864 bytes src/main/res/drawable-mdpi-v21/ic_drawer.png | Bin 2853 -> 0 bytes src/main/res/drawable-mdpi/ic_drawer.png | Bin 955 -> 2853 bytes src/main/res/drawable-xhdpi-v21/ic_drawer.png | Bin 2908 -> 0 bytes src/main/res/drawable-xhdpi/ic_drawer.png | Bin 979 -> 2908 bytes .../ic_drawer.png | Bin 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100755 src/main/res/drawable-hdpi-v21/ic_drawer.png delete mode 100755 src/main/res/drawable-mdpi-v21/ic_drawer.png delete mode 100755 src/main/res/drawable-xhdpi-v21/ic_drawer.png rename src/main/res/{drawable-xxhdpi-v21 => drawable-xxhdpi}/ic_drawer.png (100%) mode change 100755 => 100644 diff --git a/src/main/res/drawable-hdpi-v21/ic_drawer.png b/src/main/res/drawable-hdpi-v21/ic_drawer.png deleted file mode 100755 index 5c2c7f1eaa119c8f67e98b3100afb0e136614a6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2864 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 O0000_8Qu~a3!A@8Z z8$$;E0))CSF|sfe7Nm}>ObiTcObDbRm2;Y;3;!>rf# z^akCpr)%{x{eOwoUv%3f)i!D3E{UxWF*^?KAW-wH9%>-V89h2dWroSHZmUh&=6%J+ z9%rQ(F7X1&W|(p%2`qbn2#OYdcgHMn9hH5MH=!A5sPFELL)0Aax9su2E;(#v z8it{W9ExmD%&R{PfeL@`Sd z*=XbwE6MuN)?7hHksZ1Laj_3lMXQ5{L}h8D=P7u>Le`IFWugV+6D!~a4yGy1fu{L? zsOK%v5ow@{c>gIJwMGHr8z{oV(58*+Zl$gQMGKKdaM;55a9+iF9}^t)aR9Vt9+;Nx z`l&s|o2F9pBVzeBs_80A6*$**6eC}hBq-;0Ww9y#wWaY}n%U5@gPoAD0e0%u5 u__g`z^`)atPdb`>{LnXUPBI(JDrB~wT)lnz;^*7+S=Flh`kUQ@XMX?__$)^N diff --git a/src/main/res/drawable-mdpi-v21/ic_drawer.png b/src/main/res/drawable-mdpi-v21/ic_drawer.png deleted file mode 100755 index c912125793233a6cb0d41595716907fe28bb48a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2853 zcmV+=3)=LFP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0000`Nkl5J zun6G`U>f!vTF~kQw4nSE(E>sU004k5lK=n!|NjF30)uJ6N7oc800000NkvXXu0mjf DW(-0? diff --git a/src/main/res/drawable-mdpi/ic_drawer.png b/src/main/res/drawable-mdpi/ic_drawer.png index 7420a68df97b5f14f3c4256f29e8658f2218ad03..c912125793233a6cb0d41595716907fe28bb48a1 100644 GIT binary patch literal 2853 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 DW(-0? literal 955 zcmaJ=O^ee&7!KBzW!b`l7e5XTSv&~aWRh*#ZD=gJN#hnwDQ(3bJUC6JZRjQwlc`O6 zS`|S-5zqbtFM?+eiU+}yHxHit2ZA2F2zAnJst0ReGV>14^Sqz)xKY2B-MFy9aa^`` zXf)Zrm8|tM?0@OU46*Gzt#)XO^ynCcnA>+q7lWFI`nZXaGr98-mpLxOyX_9`Sl3mX zcmhf=LhJ>M&2i;Q93Xp$Dd^(9>udbCH*b01IvRgdv7i;`c;FsRL)@Cy+xB#5mmI!w z6_jI@33!+y5PL^{q{bRQ*Hzg$xfXdahtQ$Mp9Iyh8bBu@28sZ4HY`9;kOXNrUn~}P zfD9!lLRpmLoK#eIWfelO_;}_mbb4yjFc-d&s(D28e1`kq;kC74gS2C+vT z_G!v>mfRv-$xTMV3z%mEhwfeMm?81NJZ05g9gDG2Z^3m|#!{%RfrYM-yA|FuK)l5 diff --git a/src/main/res/drawable-xhdpi-v21/ic_drawer.png b/src/main/res/drawable-xhdpi-v21/ic_drawer.png deleted file mode 100755 index 56f01f3ea78f42ca506d7ade1cd5e2fd034c9fd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2908 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 Date: Sun, 8 Nov 2015 17:10:23 +0800 Subject: [PATCH 11/14] Fix some layout issues on 4.x --- src/main/res/layout/layout_main.xml | 2 +- src/main/res/values-v21/dimen.xml | 4 ++++ src/main/res/values/dimen.xml | 5 +++++ src/main/scala/com/github/shadowsocks/Shadowsocks.scala | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 src/main/res/values-v21/dimen.xml create mode 100644 src/main/res/values/dimen.xml diff --git a/src/main/res/layout/layout_main.xml b/src/main/res/layout/layout_main.xml index e73a5eae..037f041f 100644 --- a/src/main/res/layout/layout_main.xml +++ b/src/main/res/layout/layout_main.xml @@ -15,7 +15,7 @@ android:layout_alignParentEnd="true" android:layout_marginRight="16dp" android:layout_marginEnd="16dp" - android:layout_marginBottom="-28dp"> + android:layout_marginBottom="@dimen/fab_margin_bottom"> + + -28dp + diff --git a/src/main/res/values/dimen.xml b/src/main/res/values/dimen.xml new file mode 100644 index 00000000..f8e782e7 --- /dev/null +++ b/src/main/res/values/dimen.xml @@ -0,0 +1,5 @@ + + + + -44dp + diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 3763031b..07e7f322 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -1031,12 +1031,12 @@ class Shadowsocks fabProgressCircle.show() setPreferenceEnabled(enabled = false) case State.CONNECTED => - if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation() + if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation() else fabProgressCircle.hide() fab.setEnabled(true) changeSwitch(checked = true) setPreferenceEnabled(enabled = false) case State.STOPPED => - if (state == State.STOPPING || state == State.CONNECTING) fabProgressCircle.hide() + fabProgressCircle.hide() fab.setEnabled(true) changeSwitch(checked = false) if (m != null) Snackbar.make(getWindow.getDecorView.findViewById(android.R.id.content), From 7b7614db423103892f51a4f009286c91a0b1dd50 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 17:26:04 +0800 Subject: [PATCH 12/14] Misc bugfixes for FAB Progress Circle --- .../scala/com/github/shadowsocks/Shadowsocks.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 07e7f322..a1bf2f68 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -786,11 +786,17 @@ class Shadowsocks super.onResume() if (bgService != null) { bgService.getState match { - case State.CONNECTED => - changeSwitch(checked = true) case State.CONNECTING => + fabProgressCircle.show() changeSwitch(checked = true) + case State.CONNECTED => + fabProgressCircle.hide() + changeSwitch(checked = true) + case State.STOPPING => + fabProgressCircle.show() + changeSwitch(checked = false) case _ => + fabProgressCircle.hide() changeSwitch(checked = false) } state = bgService.getState @@ -1036,7 +1042,7 @@ class Shadowsocks changeSwitch(checked = true) setPreferenceEnabled(enabled = false) case State.STOPPED => - fabProgressCircle.hide() + handler.post(() => fabProgressCircle.hide()) fab.setEnabled(true) changeSwitch(checked = false) if (m != null) Snackbar.make(getWindow.getDecorView.findViewById(android.R.id.content), From 1f0f92113d427da30440f2872d2c938f469e2a01 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 17:28:18 +0800 Subject: [PATCH 13/14] Remove padding for QR code --- src/main/scala/com/github/shadowsocks/Shadowsocks.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index a1bf2f68..fd6ece5b 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -901,7 +901,6 @@ class Shadowsocks private def showQrCode() { val image = new ImageView(this) - image.setPadding(0, dp2px(20), 0, dp2px(20)) image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1)) val qrcode = QRCode.from(Parser.generate(currentProfile)) .withSize(dp2px(250), dp2px(250)).asInstanceOf[QRCode] From 4906ce8af58a87ea4b3536aa9cf7a6bf68c55d56 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 8 Nov 2015 18:43:07 +0800 Subject: [PATCH 14/14] Support switching to NAT/VPN without restarting the app --- .../com/github/shadowsocks/AppManager.scala | 8 ++-- .../com/github/shadowsocks/Shadowsocks.scala | 38 ++++++++----------- .../ShadowsocksRunnerActivity.scala | 17 +++------ .../ShadowsocksRunnerService.scala | 14 ++----- .../shadowsocks/database/ProfileManager.scala | 2 +- .../github/shadowsocks/utils/Constants.scala | 1 - .../com/github/shadowsocks/utils/Utils.scala | 9 +---- 7 files changed, 31 insertions(+), 58 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index f75a9c94..88004783 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -247,8 +247,8 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe val array = proxiedAppString.split(" ") val bypass = array(0).toBoolean val apps = if (array.size > 1) array(1) else "" - prefs.edit.putBoolean(Key.isBypassApps, bypass).commit() - prefs.edit.putString(Key.proxied, apps).commit() + prefs.edit.putBoolean(Key.isBypassApps, bypass).apply() + prefs.edit.putString(Key.proxied, apps).apply() Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show() // Restart activity val intent = getIntent @@ -299,7 +299,7 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe val bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch] val prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext) bypassSwitch.setOnCheckedChangeListener((button: CompoundButton, checked: Boolean) => - prefs.edit().putBoolean(Key.isBypassApps, checked).commit()) + prefs.edit().putBoolean(Key.isBypassApps, checked).apply()) bypassSwitch.setChecked(prefs.getBoolean(Key.isBypassApps, false)) appListView = findViewById(R.id.applistview).asInstanceOf[ListView] @@ -325,7 +325,7 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe }) val edit: SharedPreferences.Editor = prefs.edit edit.putString(Key.proxied, proxiedApps.toString()) - edit.commit + edit.apply } val loadStartRunnable = new Runnable { diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index fd6ece5b..82f77202 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -223,7 +223,7 @@ class Shadowsocks state = bgService.getState if (!status.getBoolean(getVersionName, false)) { - status.edit.putBoolean(getVersionName, true).commit() + status.edit.putBoolean(getVersionName, true).apply() recovery() } } @@ -515,13 +515,13 @@ class Shadowsocks title.setOnLongClickListener((v: View) => { if (Utils.isLollipopOrAbove && bgService != null && (bgService.getState == State.INIT || bgService.getState == State.STOPPED)) { - val natEnabled = status.getBoolean(Key.isNAT, false) - status.edit().putBoolean(Key.isNAT, !natEnabled).commit() - if (!natEnabled) { - Toast.makeText(getBaseContext, R.string.enable_nat, Toast.LENGTH_LONG).show() - } else { - Toast.makeText(getBaseContext, R.string.disable_nat, Toast.LENGTH_LONG).show() - } + val natEnabled = !status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove) + status.edit().putBoolean(Key.isNAT, natEnabled).apply() + deattachService() + attachService() + if (!Utils.isLollipopOrAbove) setPreferenceEnabled(State.isAvailable(bgService.getState)) + Toast.makeText(getBaseContext, if (natEnabled) R.string.enable_nat else R.string.disable_nat, Toast.LENGTH_LONG) + .show() true } else { false @@ -532,13 +532,9 @@ class Shadowsocks registerReceiver(preferenceReceiver, new IntentFilter(Action.UPDATE_PREFS)) // Bind to the service - Future { - val isRoot = (!Utils.isLollipopOrAbove || status.getBoolean(Key.isNAT, false)) && Console.isRoot - handler.post(() => { - status.edit.putBoolean(Key.isRoot, isRoot).commit() - attachService() - }) - } + handler.post(() => { + attachService() + }) } def attachService() { @@ -942,15 +938,10 @@ class Shadowsocks } } + var isRoot: Option[Boolean] = None def isVpnEnabled: Boolean = { - if (vpnEnabled < 0) { - vpnEnabled = if (!status.getBoolean(Key.isRoot, false)) { - 1 - } else { - 0 - } - } - if (vpnEnabled == 1) true else false + if (isRoot.isEmpty) isRoot = Some(Console.isRoot) + !(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)) } def serviceStop() { @@ -1051,6 +1042,7 @@ class Shadowsocks fab.setImageResource(R.drawable.ic_cloud_queue) fab.setEnabled(false) fabProgressCircle.show() + setPreferenceEnabled(enabled = false) } state = s }) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala index 133c0add..7b2d3817 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala @@ -65,19 +65,13 @@ class ShadowsocksRunnerActivity extends Activity { } // Variables - var vpnEnabled = -1 - var bgService: IShadowsocksService = null - var receiver:BroadcastReceiver = null + var isRoot: Option[Boolean] = None + var bgService: IShadowsocksService = _ + var receiver:BroadcastReceiver = _ def isVpnEnabled: Boolean = { - if (vpnEnabled < 0) { - vpnEnabled = if ((!Utils.isLollipopOrAbove || status.getBoolean(Key.isNAT, false)) && Console.isRoot) { - 0 - } else { - 1 - } - } - if (vpnEnabled == 1) true else false + if (isRoot.isEmpty) isRoot = Some(Console.isRoot) + !(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)) } @@ -127,6 +121,7 @@ class ShadowsocksRunnerActivity extends Activity { } else { attachService() } + finish } override def onDestroy() { diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala index 981c5d30..e27168f2 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala @@ -64,18 +64,12 @@ class ShadowsocksRunnerService extends Service { } // Variables - var vpnEnabled = -1 - var bgService: IShadowsocksService = null + var isRoot: Option[Boolean] = None + var bgService: IShadowsocksService = _ def isVpnEnabled: Boolean = { - if (vpnEnabled < 0) { - vpnEnabled = if ((!Utils.isLollipopOrAbove || status.getBoolean(Key.isNAT, false)) && Console.isRoot) { - 0 - } else { - 1 - } - } - if (vpnEnabled == 1) true else false + if (isRoot.isEmpty) isRoot = Some(Console.isRoot) + !(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)) } override def onBind(intent: Intent): IBinder = { diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala index 149dead4..08ea17d3 100644 --- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -133,7 +133,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { edit.putString(Key.proxied, profile.individual) edit.putInt(Key.profileId, profile.id) edit.putString(Key.route, profile.route) - edit.commit() + edit.apply() profile } diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/src/main/scala/com/github/shadowsocks/utils/Constants.scala index 83ab3cfb..3d812e9b 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -66,7 +66,6 @@ object Key { val proxied = "Proxyed" val isNAT = "isNAT" - val isRoot = "isRoot" val status = "status" val proxyedApps = "proxyedApps" val route = "route" diff --git a/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/src/main/scala/com/github/shadowsocks/utils/Utils.scala index f9f7fc2b..85b14a47 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -75,20 +75,13 @@ object Utils { val TIME_OUT: Int = -99 var initialized: Boolean = false var hasRedirectSupport: Int = -1 - var isRoot: Int = -1 var shell: String = null var root_shell: String = null var iptables: String = null var data_path: String = null var rootTries = 0 - def isLollipopOrAbove: Boolean = { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - true - } else { - false - } - } + def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP def getSignature(context: Context): String = { val info = context