New UI polishment
* Refine exceptions handling when parsing per-app config string * Remove redundant profiles Preference * Move Network Traffic to title bar * Add ripple to title text * Animate using animateLayoutChanges * Hide connection test in NAT mode
This commit is contained in:
@@ -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">
|
||||
<include layout="@layout/toolbar_light_dark" />
|
||||
<LinearLayout android:id="@+id/title_bar" android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:orientation="vertical" android:layout_below="@+id/toolbar"
|
||||
android:animateLayoutChanges="true">
|
||||
<include layout="@layout/toolbar_light_dark" />
|
||||
<LinearLayout android:id="@+id/stat" android:elevation="4dp" android:visibility="gone"
|
||||
android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight" android:gravity="center_vertical"
|
||||
android:paddingStart="16dip" android:paddingEnd="88dip"
|
||||
android:paddingLeft="16dip" android:paddingRight="88dip"
|
||||
android:paddingTop="16dip" android:paddingBottom="16dip"
|
||||
android:background="@color/material_accent_300" android:orientation="vertical">
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content">
|
||||
<TextView android:text="@string/stat" android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorPrimary" android:ellipsize="marquee"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
|
||||
<TextView android:id="@+id/connection_test"
|
||||
android:textSize="14sp" android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee" android:gravity="end"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"/>
|
||||
</LinearLayout>
|
||||
<android.support.v7.widget.GridLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" app:columnCount="4">
|
||||
<TextView app:layout_column="0" style="@style/TextAppearance.AppCompat.Body2"
|
||||
android:ellipsize="marquee" android:text="@string/sent"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp" android:layout_marginEnd="24dp" android:gravity="end"/>
|
||||
<TextView app:layout_column="1" android:id="@+id/tx" android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" android:ellipsize="marquee"
|
||||
android:layout_width="0dp" android:layout_height="wrap_content"
|
||||
app:layout_columnWeight="1" app:layout_gravity="fill_horizontal"/>
|
||||
<TextView app:layout_column="2" android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee" android:text="▲"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
|
||||
<TextView app:layout_column="3" android:id="@+id/txRate" android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" android:ellipsize="marquee"
|
||||
android:layout_width="0dp" android:layout_height="wrap_content" app:layout_columnWeight="1"
|
||||
app:layout_gravity="fill_horizontal" android:gravity="end"/>
|
||||
<TextView app:layout_row="1" app:layout_column="0" style="@style/TextAppearance.AppCompat.Body2"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp" android:layout_marginEnd="24dp" android:gravity="end"
|
||||
android:ellipsize="marquee" android:text="@string/received"/>
|
||||
<TextView app:layout_row="1" app:layout_column="1" android:id="@+id/rx" android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" android:ellipsize="marquee"
|
||||
android:layout_width="0dp" android:layout_height="wrap_content" app:layout_columnWeight="1"
|
||||
app:layout_gravity="fill_horizontal" />
|
||||
<TextView app:layout_row="1" app:layout_column="2" android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" android:ellipsize="marquee"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="▼"/>
|
||||
<TextView app:layout_row="1" app:layout_column="3" android:id="@+id/rxRate" android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" android:ellipsize="marquee"
|
||||
android:layout_width="0dp" android:layout_height="wrap_content" app:layout_columnWeight="1"
|
||||
app:layout_gravity="fill_horizontal" android:gravity="end"/>
|
||||
</android.support.v7.widget.GridLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
<fragment android:name="com.github.shadowsocks.ShadowsocksSettings"
|
||||
android:id="@android:id/content" android:layout_below="@+id/toolbar"
|
||||
android:id="@android:id/content" android:layout_below="@+id/title_bar"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent"/>
|
||||
<com.github.jorgecastilloprz.FABProgressCircle android:id="@+id/fabProgressCircle" app:reusable="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" android:elevation="6dp"
|
||||
android:layout_alignBottom="@+id/toolbar"
|
||||
android:layout_alignBottom="@+id/title_bar"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginRight="16dp"
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@android:id/widget_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="15dip"
|
||||
android:paddingRight="15dip"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content">
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"/>
|
||||
<TextView
|
||||
android:id="@android:id/summary"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end" />
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.v7.widget.GridLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:columnCount="4"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
app:layout_column="0"
|
||||
style="@style/TextAppearance.AppCompat.Body2"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/sent" />
|
||||
<TextView
|
||||
app:layout_column="1"
|
||||
android:id="@+id/tx"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_columnWeight="1"
|
||||
app:layout_gravity="fill_horizontal" />
|
||||
<TextView
|
||||
app:layout_column="2"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="▲" />
|
||||
<TextView
|
||||
app:layout_column="3"
|
||||
android:id="@+id/txRate"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_columnWeight="1"
|
||||
app:layout_gravity="fill_horizontal"
|
||||
android:gravity="end" />
|
||||
|
||||
<TextView
|
||||
app:layout_row="1"
|
||||
app:layout_column="0"
|
||||
style="@style/TextAppearance.AppCompat.Body2"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/received"/>
|
||||
<TextView
|
||||
app:layout_row="1"
|
||||
app:layout_column="1"
|
||||
android:id="@+id/rx"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_columnWeight="1"
|
||||
app:layout_gravity="fill_horizontal" />
|
||||
<TextView
|
||||
app:layout_row="1"
|
||||
app:layout_column="2"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="▼" />
|
||||
<TextView
|
||||
app:layout_row="1"
|
||||
app:layout_column="3"
|
||||
android:id="@+id/rxRate"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_columnWeight="1"
|
||||
app:layout_gravity="fill_horizontal"
|
||||
android:gravity="end" />
|
||||
|
||||
</android.support.v7.widget.GridLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">影梭</string>
|
||||
<string name="global_cat">全局设置</string>
|
||||
<string name="misc_cat">其他</string>
|
||||
<string name="proxy_cat">服务器设置</string>
|
||||
<string name="profile_name">配置名称</string>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Shadowsocks</string>
|
||||
<string name="global_cat">全域設定</string>
|
||||
<string name="misc_cat">其他</string>
|
||||
<string name="proxy_cat">伺服器設定</string>
|
||||
<string name="profile_name">設定檔名稱</string>
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Shadowsocks</string>
|
||||
<string name="screen_name" translatable="false">shadowsocks</string>
|
||||
<string name="screen_name" translatable="false">shadowsocks ▾</string>
|
||||
|
||||
<!-- global/misc category -->
|
||||
<string name="global_cat">Global Settings</string>
|
||||
<string name="misc_cat">Misc</string>
|
||||
<string name="profile">Profile</string>
|
||||
<string name="profile_summary">Switch to another profile or add new profiles</string>
|
||||
|
||||
@@ -2,19 +2,6 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
|
||||
|
||||
<PreferenceCategory android:title="@string/global_cat">
|
||||
|
||||
<Preference android:key="profiles" android:summary="@string/profile_summary" android:title="@string/profiles">
|
||||
<intent android:action="com.github.shadowsocks.ProfileManagerActivity"/>
|
||||
</Preference>
|
||||
|
||||
<com.github.shadowsocks.preferences.StatusPreference
|
||||
android:key="stat"
|
||||
android:summary="@string/connection_test_pending"
|
||||
android:title="@string/stat"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/proxy_cat">
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 _ =>
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,6 @@ object Key {
|
||||
|
||||
val proxied = "Proxyed"
|
||||
|
||||
val profiles = "profiles"
|
||||
val isNAT = "isNAT"
|
||||
val route = "route"
|
||||
val stat = "stat"
|
||||
|
||||
Reference in New Issue
Block a user