Commit 202bd2c9 authored by Mygod's avatar Mygod Committed by Max Lv

Add support for Android TV

This commit adds an app banner in Android TV and introduces several changes including refactoring AppManager to make all features accessible with D-pad navigation.

Currently this app has the exact same user interface on Android TV as on normal Android devices but one should create a TV-specific user interface in the future.

P.S. Only tested in Android emulator. More testing might be needed.
parent 3c14ff67
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
<uses-feature <uses-feature
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false"/> android:required="false"/>
<uses-feature android:name="android.software.leanback"
android:required="false" />
<uses-feature android:name="android.hardware.nfc" android:required="false" /> <uses-feature android:name="android.hardware.nfc" android:required="false" />
...@@ -25,7 +27,8 @@ ...@@ -25,7 +27,8 @@
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:backupAgent=".ShadowsocksBackupAgent" android:backupAgent=".ShadowsocksBackupAgent"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.Material"> android:theme="@style/Theme.Material"
android:banner="@drawable/ic_start_connected">
<meta-data android:name="com.google.android.gms.version" <meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/> android:value="@integer/google_play_services_version"/>
...@@ -43,6 +46,7 @@ ...@@ -43,6 +46,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
......
...@@ -45,10 +45,10 @@ ...@@ -45,10 +45,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" /> android:layout_gravity="center" />
<ListView <android.support.v7.widget.RecyclerView
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:id="@+id/applistview" android:id="@+id/applistview"
android:visibility="gone"/> android:visibility="gone"/>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="fill_parent" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"> android:focusable="true"
android:background="?android:attr/selectableItemBackground">
<ImageView <ImageView
android:id="@+id/itemicon" android:id="@+id/itemicon"
...@@ -15,20 +16,19 @@ ...@@ -15,20 +16,19 @@
android:paddingTop="2dp" android:paddingTop="2dp"
android:paddingBottom="2dp"/> android:paddingBottom="2dp"/>
<TextView <Switch
android:id="@+id/itemtext" android:id="@+id/itemcheck"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:ellipsize="end" android:ellipsize="end"
android:textSize="18sp"/> android:textSize="18sp"
android:textColor="?android:attr/textColorSecondary"
<Switch android:focusable="false"
android:id="@+id/itemcheck" android:focusableInTouchMode="false"
android:layout_width="wrap_content" android:clickable="false"
android:layout_height="fill_parent" android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"/> android:layout_marginRight="8dp"/>
</LinearLayout> </LinearLayout>
...@@ -3,11 +3,14 @@ ...@@ -3,11 +3,14 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/container"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:paddingStart="20dp" android:paddingStart="20dp"
android:paddingEnd="?attr/dialogPreferredPadding" android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingLeft="20dp" android:paddingLeft="20dp"
android:paddingRight="?attr/dialogPreferredPadding"> android:paddingRight="?attr/dialogPreferredPadding"
android:focusable="true"
android:nextFocusRight="@+id/share">
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android" <CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1" android:id="@android:id/text1"
android:layout_width="0dp" android:layout_width="0dp"
...@@ -30,5 +33,7 @@ ...@@ -30,5 +33,7 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:src="@drawable/ic_social_share" android:src="@drawable/ic_social_share"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/share"/> android:contentDescription="@string/share"
android:focusable="true"
android:nextFocusLeft="@+id/container"/>
</LinearLayout> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="80dp" android:maxWidth="80dp" android:gravity="center"
android:textSize="70sp" android:textColor="#ffffffff"
android:background="#99000088" android:padding="10dp"
android:visibility="invisible"/>
\ No newline at end of file
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
<!-- profile --> <!-- profile -->
<string name="profile_manager_dialog">配置文件</string> <string name="profile_manager_dialog">配置文件</string>
<string name="profile_manager_dialog_content">&#8226; 点击选择配置文件;\n&#8226; 滑动删除;\n&#8226; <string name="profile_manager_dialog_content">&#8226; 点击选择配置文件;\n&#8226; 向左/右滑动或按左删除;\n&#8226;
长按拖动改变顺序。</string> 长按拖动改变顺序。</string>
<string name="gotcha">好的</string> <string name="gotcha">好的</string>
<string name="copy_url">复制 URL</string> <string name="copy_url">复制 URL</string>
......
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
<!-- profile --> <!-- profile -->
<string name="profile_manager_dialog">設定檔</string> <string name="profile_manager_dialog">設定檔</string>
<string name="profile_manager_dialog_content">&#8226; 輕觸選擇一個設定檔;\n&#8226;右撥動移除;\n&#8226; <string name="profile_manager_dialog_content">&#8226; 輕觸選擇一個設定檔;\n&#8226;左/右撥動或按左移除;\n&#8226;
按住後上下撥動設定檔重新排序。</string> 按住後上下撥動設定檔重新排序。</string>
<string name="gotcha">我懂了!</string> <string name="gotcha">我懂了!</string>
<string name="copy_url">複製 URL</string> <string name="copy_url">複製 URL</string>
......
...@@ -110,8 +110,8 @@ ...@@ -110,8 +110,8 @@
<!-- profile --> <!-- profile -->
<string name="profile_manager_dialog">Profiles</string> <string name="profile_manager_dialog">Profiles</string>
<string name="profile_manager_dialog_content">&#8226; Tap to select a profile;\n&#8226; Swipe to remove;\n&#8226; <string name="profile_manager_dialog_content">&#8226; Tap to select a profile;\n&#8226; Swipe left/right or press left
Long press to reorder profiles.</string> to remove;\n&#8226; Long press to reorder profiles.</string>
<string name="gotcha">Gotcha!</string> <string name="gotcha">Gotcha!</string>
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string> <string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
<string name="add_profile_methods_scan_qr_code">Scan QR code</string> <string name="add_profile_methods_scan_qr_code">Scan QR code</string>
......
...@@ -44,18 +44,13 @@ import java.util.concurrent.atomic.AtomicBoolean ...@@ -44,18 +44,13 @@ import java.util.concurrent.atomic.AtomicBoolean
import android.Manifest.permission import android.Manifest.permission
import android.content._ import android.content._
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.{Bundle, Handler} import android.os.{Bundle, Handler}
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.support.v7.widget.Toolbar.OnMenuItemClickListener import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.view.View.OnClickListener import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
import android.view.ViewGroup.LayoutParams
import android.view._ import android.view._
import android.widget.AbsListView.OnScrollListener
import android.widget.CompoundButton.OnCheckedChangeListener
import android.widget._ import android.widget._
import com.github.shadowsocks.utils.{Key, Utils} import com.github.shadowsocks.utils.{Key, Utils}
...@@ -95,39 +90,56 @@ object AppManager { ...@@ -95,39 +90,56 @@ object AppManager {
} }
} }
class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnClickListener class AppManager extends AppCompatActivity with OnMenuItemClickListener {
with OnMenuItemClickListener {
import AppManager._ import AppManager._
private var apps: Array[ProxiedApp] = _ private final class AppViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
private var proxiedApps: mutable.HashSet[String] = _ private val icon = itemView.findViewById(R.id.itemicon).asInstanceOf[ImageView]
private var toolbar: Toolbar = _ private val check = itemView.findViewById(R.id.itemcheck).asInstanceOf[Switch]
private var appListView: ListView = _ private var app: ProxiedApp = _
private var loadingView: View = _ itemView.setOnClickListener(this)
private var overlay: TextView = _
private var adapter: ListAdapter = _
private val appsLoading = new AtomicBoolean
private def setProxied(pn: String, proxied: Boolean) = if (proxied) proxiedApps.add(pn) else proxiedApps.remove(pn) private def proxied = proxiedApps.contains(app.packageName)
/** Called an application is check/unchecked */ def bind(app: ProxiedApp) {
def onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { this.app = app
val app: ProxiedApp = buttonView.getTag.asInstanceOf[ProxiedApp] icon.setImageDrawable(app.icon)
if (app != null) setProxied(app.packageName, isChecked) check.setText(app.name)
saveAppSettings(this) check.setChecked(proxied)
} }
def onClick(v: View) { def onClick(v: View) {
val switch = v.getTag.asInstanceOf[ListEntry].switch if (proxied) {
val app: ProxiedApp = switch.getTag.asInstanceOf[ProxiedApp] proxiedApps.remove(app.packageName)
if (app != null) { check.setChecked(false)
val proxied = !proxiedApps.contains(app.packageName) } else {
setProxied(app.packageName, proxied) proxiedApps.add(app.packageName)
switch.setChecked(proxied) check.setChecked(true)
}
if (!appsLoading.get)
ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
} }
saveAppSettings(this)
} }
private final class AppsAdapter extends RecyclerView.Adapter[AppViewHolder] {
private val apps = getApps(getPackageManager).sortWith((a, b) => {
val aProxied = proxiedApps.contains(a.packageName)
if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
})
def getItemCount = apps.length
def onBindViewHolder(vh: AppViewHolder, i: Int) = vh.bind(apps(i))
def onCreateViewHolder(vg: ViewGroup, i: Int) =
new AppViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_apps_item, vg, false))
}
private val proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
private var toolbar: Toolbar = _
private var appListView: RecyclerView = _
private var loadingView: View = _
private val appsLoading = new AtomicBoolean
private var handler: Handler = null
override def onDestroy() { override def onDestroy() {
instance = null instance = null
super.onDestroy() super.onDestroy()
...@@ -135,10 +147,6 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -135,10 +147,6 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
handler = null handler = null
} }
if (overlay != null) {
getWindowManager.removeViewImmediate(overlay)
overlay = null
}
} }
def onMenuItemClick(item: MenuItem): Boolean = { def onMenuItemClick(item: MenuItem): Boolean = {
...@@ -200,44 +208,23 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -200,44 +208,23 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
toolbar.inflateMenu(R.menu.app_manager_menu) toolbar.inflateMenu(R.menu.app_manager_menu)
toolbar.setOnMenuItemClickListener(this) toolbar.setOnMenuItemClickListener(this)
overlay = View.inflate(this, R.layout.overlay, null).asInstanceOf[TextView]
getWindowManager.addView(overlay, new
WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT))
ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, true).commit() ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, true).commit()
findViewById(R.id.onSwitch).asInstanceOf[Switch] findViewById(R.id.onSwitch).asInstanceOf[Switch]
.setOnCheckedChangeListener((button: CompoundButton, checked: Boolean) => { .setOnCheckedChangeListener((_, checked) => {
ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, checked).commit() ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, checked).commit()
finish() finish()
}) })
val bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch] val bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch]
bypassSwitch.setOnCheckedChangeListener((button: CompoundButton, checked: Boolean) => bypassSwitch.setOnCheckedChangeListener((_, checked) =>
ShadowsocksApplication.settings.edit().putBoolean(Key.isBypassApps, checked).apply()) ShadowsocksApplication.settings.edit().putBoolean(Key.isBypassApps, checked).apply())
bypassSwitch.setChecked(ShadowsocksApplication.settings.getBoolean(Key.isBypassApps, false)) bypassSwitch.setChecked(ShadowsocksApplication.settings.getBoolean(Key.isBypassApps, false))
loadingView = findViewById(R.id.loading) loadingView = findViewById(R.id.loading)
appListView = findViewById(R.id.applistview).asInstanceOf[ListView] appListView = findViewById(R.id.applistview).asInstanceOf[RecyclerView]
appListView.setOnScrollListener(new AbsListView.OnScrollListener { appListView.setLayoutManager(new LinearLayoutManager(this))
var visible = false appListView.setItemAnimator(new DefaultItemAnimator)
def onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int,
totalItemCount: Int) {
if (visible) {
val name = apps(firstVisibleItem).name
overlay.setText(if (name != null && name.length > 1) name(0).toUpper.toString else "*")
overlay.setVisibility(View.VISIBLE)
}
}
def onScrollStateChanged(view: AbsListView, scrollState: Int) {
visible = true
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
overlay.setVisibility(View.INVISIBLE)
}
}
})
instance = this instance = this
loadAppsAsync() loadAppsAsync()
} }
...@@ -246,40 +233,10 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -246,40 +233,10 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
def loadAppsAsync() { def loadAppsAsync() {
if (!appsLoading.compareAndSet(false, true)) return if (!appsLoading.compareAndSet(false, true)) return
ThrowableFuture { ThrowableFuture {
var adapter: AppsAdapter = null
do { do {
proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
appsLoading.set(true) appsLoading.set(true)
apps = getApps(getPackageManager).sortWith((a, b) => { adapter = new AppsAdapter
val aProxied = proxiedApps.contains(a.packageName)
if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
})
adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
override def getView(position: Int, view: View, parent: ViewGroup): View = {
var convertView = view
var entry: ListEntry = null
if (convertView == null) {
convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch],
convertView.findViewById(R.id.itemtext).asInstanceOf[TextView],
convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView])
convertView.setOnClickListener(AppManager.this)
convertView.setTag(entry)
entry.switch.setOnCheckedChangeListener(AppManager.this)
} else {
entry = convertView.getTag.asInstanceOf[ListEntry]
}
val app: ProxiedApp = apps(position)
entry.text.setText(app.name)
entry.icon.setImageDrawable(app.icon)
val switch = entry.switch
switch.setTag(app)
switch.setChecked(proxiedApps.contains(app.packageName))
entry.text.setTag(switch)
convertView
}
}
} while (!appsLoading.compareAndSet(true, false)) } while (!appsLoading.compareAndSet(true, false))
handler.post(() => { handler.post(() => {
appListView.setAdapter(adapter) appListView.setAdapter(adapter)
...@@ -288,12 +245,6 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -288,12 +245,6 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
} }
} }
def saveAppSettings(context: Context) {
if (!appsLoading.get) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
}
var handler: Handler = null
override def onKeyUp(keyCode: Int, event: KeyEvent) = keyCode match { override def onKeyUp(keyCode: Int, event: KeyEvent) = keyCode match {
case KeyEvent.KEYCODE_MENU => case KeyEvent.KEYCODE_MENU =>
if (toolbar.isOverflowMenuShowing) toolbar.hideOverflowMenu else toolbar.showOverflowMenu if (toolbar.isOverflowMenuShowing) toolbar.hideOverflowMenu else toolbar.showOverflowMenu
......
...@@ -15,9 +15,9 @@ import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback ...@@ -15,9 +15,9 @@ import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar} import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
import android.text.style.TextAppearanceSpan import android.text.style.TextAppearanceSpan
import android.text.{SpannableStringBuilder, Spanned, TextUtils} import android.text.{SpannableStringBuilder, Spanned, TextUtils}
import android.view.{LayoutInflater, MenuItem, View, ViewGroup} import android.view._
import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast} import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast}
import com.github.clans.fab.{FloatingActionMenu, FloatingActionButton} import com.github.clans.fab.{FloatingActionButton, FloatingActionMenu}
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils.{Key, Parser, TrafficMonitor, Utils} import com.github.shadowsocks.utils.{Key, Parser, TrafficMonitor, Utils}
...@@ -27,12 +27,14 @@ import net.glxn.qrgen.android.QRCode ...@@ -27,12 +27,14 @@ import net.glxn.qrgen.android.QRCode
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext with View.OnClickListener final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext
with CreateNdefMessageCallback { with View.OnClickListener with CreateNdefMessageCallback {
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener { private final class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view)
with View.OnClickListener with View.OnKeyListener {
var item: Profile = _ var item: Profile = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView] private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
itemView.setOnClickListener(this) itemView.setOnClickListener(this)
itemView.setOnKeyListener(this)
{ {
val shareBtn = itemView.findViewById(R.id.share) val shareBtn = itemView.findViewById(R.id.share)
...@@ -107,6 +109,15 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -107,6 +109,15 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
ShadowsocksApplication.switchProfile(item.id) ShadowsocksApplication.switchProfile(item.id)
finish finish
} }
def onKey(v: View, keyCode: Int, event: KeyEvent) = if (event.getAction == KeyEvent.ACTION_UP) keyCode match {
case KeyEvent.KEYCODE_DPAD_LEFT =>
val index = getAdapterPosition
profilesAdapter.remove(index)
undoManager.remove(index, item)
true
case _ => false
} else false
} }
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] { private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
......
...@@ -134,9 +134,9 @@ class Shadowsocks ...@@ -134,9 +134,9 @@ class Shadowsocks
preferences.setEnabled(false) preferences.setEnabled(false)
stat.setVisibility(View.VISIBLE) stat.setVisibility(View.VISIBLE)
if (ShadowsocksApplication.isVpnEnabled) { if (ShadowsocksApplication.isVpnEnabled) {
connectionTest.setVisibility(View.VISIBLE) connectionTestText.setVisibility(View.VISIBLE)
connectionTest.setText(getString(R.string.connection_test_pending)) connectionTestText.setText(getString(R.string.connection_test_pending))
} else connectionTest.setVisibility(View.GONE) } else connectionTestText.setVisibility(View.GONE)
case State.STOPPED => case State.STOPPED =>
fab.setBackgroundTintList(greyTint) fab.setBackgroundTintList(greyTint)
fabProgressCircle.postDelayed(hideCircle, 1000) fabProgressCircle.postDelayed(hideCircle, 1000)
...@@ -217,7 +217,7 @@ class Shadowsocks ...@@ -217,7 +217,7 @@ class Shadowsocks
private var testCount: Int = _ private var testCount: Int = _
private lazy val stat = findViewById(R.id.stat) private lazy val stat = findViewById(R.id.stat)
private lazy val connectionTest = findViewById(R.id.connection_test).asInstanceOf[TextView] private var connectionTestText: TextView = _
private var txText: TextView = _ private var txText: TextView = _
private var rxText: TextView = _ private var rxText: TextView = _
private var txRateText: TextView = _ private var txRateText: TextView = _
...@@ -371,6 +371,7 @@ class Shadowsocks ...@@ -371,6 +371,7 @@ class Shadowsocks
val field = classOf[Toolbar].getDeclaredField("mTitleTextView") val field = classOf[Toolbar].getDeclaredField("mTitleTextView")
field.setAccessible(true) field.setAccessible(true)
val title = field.get(toolbar).asInstanceOf[TextView] val title = field.get(toolbar).asInstanceOf[TextView]
title.setFocusable(true)
title.setOnClickListener(_ => startActivity(new Intent(this, classOf[ProfileManagerActivity]))) title.setOnClickListener(_ => startActivity(new Intent(this, classOf[ProfileManagerActivity])))
val typedArray = obtainStyledAttributes(Array(R.attr.selectableItemBackgroundBorderless)) val typedArray = obtainStyledAttributes(Array(R.attr.selectableItemBackgroundBorderless))
title.setBackgroundResource(typedArray.getResourceId(0, 0)) title.setBackgroundResource(typedArray.getResourceId(0, 0))
...@@ -378,11 +379,12 @@ class Shadowsocks ...@@ -378,11 +379,12 @@ class Shadowsocks
val tf = Typefaces.get(this, "fonts/Iceland.ttf") val tf = Typefaces.get(this, "fonts/Iceland.ttf")
if (tf != null) title.setTypeface(tf) if (tf != null) title.setTypeface(tf)
val connectionTestText = findViewById(R.id.connection_test).asInstanceOf[TextView] connectionTestText = findViewById(R.id.connection_test).asInstanceOf[TextView]
txText = findViewById(R.id.tx).asInstanceOf[TextView] txText = findViewById(R.id.tx).asInstanceOf[TextView]
txRateText = findViewById(R.id.txRate).asInstanceOf[TextView] txRateText = findViewById(R.id.txRate).asInstanceOf[TextView]
rxText = findViewById(R.id.rx).asInstanceOf[TextView] rxText = findViewById(R.id.rx).asInstanceOf[TextView]
rxRateText = findViewById(R.id.rxRate).asInstanceOf[TextView] rxRateText = findViewById(R.id.rxRate).asInstanceOf[TextView]
connectionTestText.setFocusable(true)
connectionTestText.setOnClickListener(_ => { connectionTestText.setOnClickListener(_ => {
val id = synchronized { val id = synchronized {
testCount += 1 testCount += 1
...@@ -472,9 +474,9 @@ class Shadowsocks ...@@ -472,9 +474,9 @@ class Shadowsocks
fabProgressCircle.postDelayed(hideCircle, 100) fabProgressCircle.postDelayed(hideCircle, 100)
stat.setVisibility(View.VISIBLE) stat.setVisibility(View.VISIBLE)
if (ShadowsocksApplication.isVpnEnabled) { if (ShadowsocksApplication.isVpnEnabled) {
connectionTest.setVisibility(View.VISIBLE) connectionTestText.setVisibility(View.VISIBLE)
connectionTest.setText(getString(R.string.connection_test_pending)) connectionTestText.setText(getString(R.string.connection_test_pending))
} else connectionTest.setVisibility(View.GONE) } else connectionTestText.setVisibility(View.GONE)
case State.STOPPING => case State.STOPPING =>
fab.setBackgroundTintList(greyTint) fab.setBackgroundTintList(greyTint)
serviceStarted = false serviceStarted = false
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment