diff --git a/src/main/res/layout/layout_main.xml b/src/main/res/layout/layout_main.xml index 14858f5c..c42b69e8 100644 --- a/src/main/res/layout/layout_main.xml +++ b/src/main/res/layout/layout_main.xml @@ -3,14 +3,70 @@ 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:clipChildren="false"> - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index b794e265..e70d0301 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -1,7 +1,6 @@ 影梭 - 全局设置 其他 服务器设置 配置名称 diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index e5e022d9..a5c98be5 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -2,7 +2,6 @@ Shadowsocks - 全域設定 其他 伺服器設定 設定檔名稱 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 0dbca08b..d5c7ccc9 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -2,10 +2,9 @@ Shadowsocks - shadowsocks + shadowsocks ▾ - Global Settings Misc Profile Switch to another profile or add new profiles diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml index 2064a822..8cb10dde 100644 --- a/src/main/res/xml/pref_all.xml +++ b/src/main/res/xml/pref_all.xml @@ -2,19 +2,6 @@ - - - - - - - - - - diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index 318dab8d..41fec006 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -172,7 +172,8 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC reloadApps() return true } catch { - case _: java.lang.IllegalArgumentException => // ignore + case _: IllegalArgumentException => + Toast.makeText(this, R.string.action_import_err, Toast.LENGTH_SHORT).show } } } diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 0f985a50..a20f771e 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -39,6 +39,8 @@ package com.github.shadowsocks import java.io.{FileOutputStream, IOException, InputStream, OutputStream} +import java.lang.System.currentTimeMillis +import java.net.{HttpURLConnection, URL} import java.util import java.util.Locale @@ -59,6 +61,7 @@ import android.widget._ import com.github.jorgecastilloprz.FABProgressCircle import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.database._ +import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils._ import com.google.android.gms.ads.{AdRequest, AdSize, AdView} @@ -105,7 +108,6 @@ class Shadowsocks var progressTag = -1 var state = State.STOPPED var currentProfile = new Profile - var vpnEnabled = -1 // Services var currentServiceName = classOf[ShadowsocksNatService].getName @@ -119,6 +121,7 @@ class Shadowsocks fab.setEnabled(false) fabProgressCircle.show() preferences.setEnabled(false) + stat.setVisibility(View.GONE) case State.CONNECTED => fab.setBackgroundTintList(greenTint) if (state == State.CONNECTING) { @@ -129,6 +132,11 @@ class Shadowsocks fab.setEnabled(true) changeSwitch(checked = true) preferences.setEnabled(false) + stat.setVisibility(View.VISIBLE) + if (ShadowsocksApplication.isVpnEnabled) { + connectionTest.setVisibility(View.VISIBLE) + connectionTest.setText(getString(R.string.connection_test_pending)) + } else connectionTest.setVisibility(View.GONE) case State.STOPPED => fab.setBackgroundTintList(greyTint) fabProgressCircle.postDelayed(hideCircle, 1000) @@ -143,12 +151,14 @@ class Shadowsocks Log.e(Shadowsocks.TAG, "Error to start VPN service: " + m) } preferences.setEnabled(true) + stat.setVisibility(View.GONE) case State.STOPPING => fab.setBackgroundTintList(greyTint) fab.setImageResource(R.drawable.ic_start_busy) fab.setEnabled(false) if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped preferences.setEnabled(false) + stat.setVisibility(View.GONE) } state = s }) @@ -158,9 +168,12 @@ class Shadowsocks } } - def updateTraffic(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) = preferences.stat - .setRate(TrafficMonitor.formatTraffic(txTotal), TrafficMonitor.formatTraffic(rxTotal), - TrafficMonitor.formatTraffic(txRate) + "/s", TrafficMonitor.formatTraffic(rxRate) + "/s") + def updateTraffic(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { + txText.setText(TrafficMonitor.formatTraffic(txTotal)) + rxText.setText(TrafficMonitor.formatTraffic(rxTotal)) + txRateText.setText(TrafficMonitor.formatTraffic(txRate)) + rxRateText.setText(TrafficMonitor.formatTraffic(rxRate)) + } def attachService: Unit = attachService(callback) @@ -202,11 +215,18 @@ class Shadowsocks if (fab != null) fab.setEnabled(false) } - private lazy val preferences = - getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings] - private var adView: AdView = _ + private var testCount: Int = _ + private lazy val stat = findViewById(R.id.stat) + private lazy val connectionTest = findViewById(R.id.connection_test).asInstanceOf[TextView] + private var txText: TextView = _ + private var rxText: TextView = _ + private var txRateText: TextView = _ + private var rxRateText: TextView = _ private lazy val greyTint = ContextCompat.getColorStateList(this, R.color.material_blue_grey_700) private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_700) + private var adView: AdView = _ + private lazy val preferences = + getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings] var handler = new Handler() @@ -350,13 +370,65 @@ class Shadowsocks toolbar.setTitleTextAppearance(toolbar.getContext, R.style.Toolbar_Logo) val field = classOf[Toolbar].getDeclaredField("mTitleTextView") field.setAccessible(true) - val title: TextView = field.get(toolbar).asInstanceOf[TextView] - val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf") + val title = field.get(toolbar).asInstanceOf[TextView] + title.setOnClickListener(_ => startActivity(new Intent(this, classOf[ProfileManagerActivity]))) + val typedArray = obtainStyledAttributes(Array(R.attr.selectableItemBackgroundBorderless)) + title.setBackgroundResource(typedArray.getResourceId(0, 0)) + typedArray.recycle + val tf = Typefaces.get(this, "fonts/Iceland.ttf") if (tf != null) title.setTypeface(tf) + val connectionTestText = findViewById(R.id.connection_test).asInstanceOf[TextView] + txText = findViewById(R.id.tx).asInstanceOf[TextView] + txRateText = findViewById(R.id.txRate).asInstanceOf[TextView] + rxText = findViewById(R.id.rx).asInstanceOf[TextView] + rxRateText = findViewById(R.id.rxRate).asInstanceOf[TextView] + connectionTestText.setOnClickListener(_ => { + val id = synchronized { + testCount += 1 + handler.post(() => connectionTestText.setText(R.string.connection_test_testing)) + testCount + } + ThrowableFuture { + // Based on: https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/connectivity/NetworkMonitor.java#640 + autoDisconnect(new URL("https", "www.google.com", "/generate_204").openConnection() + .asInstanceOf[HttpURLConnection]) { conn => + conn.setConnectTimeout(5 * 1000) + conn.setReadTimeout(5 * 1000) + conn.setInstanceFollowRedirects(false) + conn.setUseCaches(false) + if (testCount == id) { + var result: String = null + var success = true + try { + val start = currentTimeMillis + conn.getInputStream + val elapsed = currentTimeMillis - start + val code = conn.getResponseCode + if (code == 204 || code == 200 && conn.getContentLength == 0) + result = getString(R.string.connection_test_available, elapsed: java.lang.Long) + else throw new Exception(getString(R.string.connection_test_error_status_code, code: Integer)) + } catch { + case e: Exception => + success = false + result = getString(R.string.connection_test_error, e.getMessage) + } + synchronized(if (testCount == id) { + if (ShadowsocksApplication.isVpnEnabled) handler.post(() => { + if (success) connectionTestText.setText(result) else { + connectionTestText.setText(R.string.connection_test_fail) + Snackbar.make(findViewById(android.R.id.content), result, Snackbar.LENGTH_LONG).show + } + }) + }) + } + } + } + }) + fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton] fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle] - fab.setOnClickListener((v: View) => if (serviceStarted) serviceStop() + fab.setOnClickListener(_ => if (serviceStarted) serviceStop() else if (checkText(Key.proxy) && checkText(Key.sitekey) && bgService != null) prepareStartService() else changeSwitch(checked = false)) fab.setOnLongClickListener((v: View) => { @@ -391,24 +463,32 @@ class Shadowsocks fab.setImageResource(R.drawable.ic_start_busy) preferences.setEnabled(false) fabProgressCircle.show() + stat.setVisibility(View.GONE) case State.CONNECTED => fab.setBackgroundTintList(greenTint) serviceStarted = true fab.setImageResource(R.drawable.ic_start_connected) preferences.setEnabled(false) fabProgressCircle.postDelayed(hideCircle, 100) + stat.setVisibility(View.VISIBLE) + if (ShadowsocksApplication.isVpnEnabled) { + connectionTest.setVisibility(View.VISIBLE) + connectionTest.setText(getString(R.string.connection_test_pending)) + } else connectionTest.setVisibility(View.GONE) case State.STOPPING => fab.setBackgroundTintList(greyTint) serviceStarted = false fab.setImageResource(R.drawable.ic_start_busy) preferences.setEnabled(false) fabProgressCircle.show() + stat.setVisibility(View.GONE) case _ => fab.setBackgroundTintList(greyTint) serviceStarted = false fab.setImageResource(R.drawable.ic_start_idle) preferences.setEnabled(true) fabProgressCircle.postDelayed(hideCircle, 100) + stat.setVisibility(View.GONE) } state = bgService.getState } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala index 5e9218fc..c4d56bf9 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala @@ -1,7 +1,5 @@ package com.github.shadowsocks -import java.lang.System.currentTimeMillis -import java.net.{HttpURLConnection, URL} import java.util.Locale import android.content.SharedPreferences.OnSharedPreferenceChangeListener @@ -9,12 +7,10 @@ import android.content.{DialogInterface, Intent, SharedPreferences} import android.net.Uri import android.os.{Build, Bundle} import android.preference.{Preference, PreferenceFragment, SwitchPreference} -import android.support.design.widget.Snackbar import android.support.v7.app.AlertDialog import android.webkit.{WebView, WebViewClient} import com.github.shadowsocks.database.Profile import com.github.shadowsocks.preferences._ -import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.{Key, Utils} object ShadowsocksSettings { @@ -69,62 +65,14 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan private def activity = getActivity.asInstanceOf[Shadowsocks] lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference] - var stat: StatusPreference = _ private var isProxyApps: SwitchPreference = _ - private var testCount: Int = _ override def onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) addPreferencesFromResource(R.xml.pref_all) getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this) - stat = findPreference(Key.stat).asInstanceOf[StatusPreference] - stat.setOnPreferenceClickListener(_ => { - val id = synchronized { - testCount += 1 - activity.handler.post(() => stat.setSummary(R.string.connection_test_testing)) - testCount - } - ThrowableFuture { - // Based on: https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/connectivity/NetworkMonitor.java#640 - autoDisconnect(new URL("https", "www.google.com", "/generate_204").openConnection() - .asInstanceOf[HttpURLConnection]) { conn => - conn.setConnectTimeout(5 * 1000) - conn.setReadTimeout(5 * 1000) - conn.setInstanceFollowRedirects(false) - conn.setUseCaches(false) - if (testCount == id) { - var result: String = null - var success = true - try { - val start = currentTimeMillis - conn.getInputStream - val elapsed = currentTimeMillis - start - val code = conn.getResponseCode - if (code == 204 || code == 200 && conn.getContentLength == 0) - result = getString(R.string.connection_test_available, elapsed: java.lang.Long) - else throw new Exception(getString(R.string.connection_test_error_status_code, code: Integer)) - } catch { - case e: Exception => - success = false - result = getString(R.string.connection_test_error, e.getMessage) - } - synchronized(if (testCount == id) { - if (ShadowsocksApplication.isVpnEnabled) activity.handler.post(() => { - if (success) stat.setSummary(result) else { - stat.setSummary(R.string.connection_test_fail) - Snackbar.make(activity.findViewById(android.R.id.content), result, Snackbar.LENGTH_LONG).show - } - }) - }) - } - } - } - true - }) - stat.setSummary(if (ShadowsocksApplication.isVpnEnabled) getString(R.string.connection_test_pending) else null) - isProxyApps = findPreference(Key.isProxyApps).asInstanceOf[SwitchPreference] isProxyApps.setOnPreferenceClickListener((preference: Preference) => { startActivity(new Intent(activity, classOf[AppManager])) @@ -189,7 +137,6 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan activity.deattachService activity.attachService }) - stat.setSummary(if (ShadowsocksApplication.isVpnEnabled) getString(R.string.connection_test_pending) else null) case _ => } diff --git a/src/main/scala/com/github/shadowsocks/preferences/StatusPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/StatusPreference.scala deleted file mode 100644 index 0db2bd09..00000000 --- a/src/main/scala/com/github/shadowsocks/preferences/StatusPreference.scala +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.shadowsocks.preferences - -import android.content.Context -import android.preference.Preference -import android.util.AttributeSet -import android.view.{LayoutInflater, View, ViewGroup} -import android.widget.TextView -import com.github.shadowsocks.R - -/** - * @author madeye - */ -final class StatusPreference(context: Context, attrs: AttributeSet = null) - extends Preference(context, attrs) { - var tx: String = _ - var rx: String = _ - var txRate: String = _ - var rxRate: String = _ - var txView: TextView = _ - var rxView: TextView = _ - var txRateView: TextView = _ - var rxRateView: TextView = _ - - override def onCreateView(parent: ViewGroup): View = { - val view = getContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater] - .inflate(R.layout.status_pref, parent, false) - - txView = view.findViewById(R.id.tx).asInstanceOf[TextView] - rxView = view.findViewById(R.id.rx).asInstanceOf[TextView] - txRateView = view.findViewById(R.id.txRate).asInstanceOf[TextView] - rxRateView = view.findViewById(R.id.rxRate).asInstanceOf[TextView] - - view - } - - override def onBindView(view: View) = { - super.onBindView(view) - txView.setText(tx) - rxView.setText(rx) - txRateView.setText(txRate) - rxRateView.setText(rxRate) - } - - def setRate(tx: String, rx: String, txRate: String, rxRate: String) { - this.tx = tx - if (txView != null) { - txView.setText(tx) - } - this.rx = rx - if (rxView != null) { - rxView.setText(rx) - } - this.txRate = txRate - if (txRateView != null) { - txRateView.setText(txRate) - } - this.rxRate = rxRate - if (rxRateView != null) { - rxRateView.setText(rxRate) - } - } -} diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/src/main/scala/com/github/shadowsocks/utils/Constants.scala index 5aeb7959..c6a0d811 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -61,7 +61,6 @@ object Key { val proxied = "Proxyed" - val profiles = "profiles" val isNAT = "isNAT" val route = "route" val stat = "stat"