diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 6f06ec37..64276304 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -25,7 +25,7 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
- android:banner="@drawable/ic_start_connected">
+ android:banner="@drawable/ic_service_active">
@@ -139,7 +139,7 @@
diff --git a/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt b/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
index 8c8125c8..665910dc 100644
--- a/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
+++ b/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
@@ -57,6 +57,7 @@ import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.responseLength
import com.github.shadowsocks.utils.thread
+import com.github.shadowsocks.widget.ServiceButton
import com.mikepenz.crossfader.Crossfader
import com.mikepenz.crossfader.view.CrossFadeSlidingPaneLayout
import com.mikepenz.materialdrawer.Drawer
@@ -87,6 +88,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
// UI
private lateinit var fab: FloatingActionButton
+ private val fabHelper by lazy { ServiceButton(fab) }
internal var crossfader: Crossfader? = null
internal lateinit var drawer: Drawer
private var previousSelectedDrawer: Long = 0 // it's actually lateinit
@@ -116,7 +118,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override val serviceCallback: IShadowsocksServiceCallback.Stub by lazy {
object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) {
- app.handler.post { changeState(state, msg) }
+ app.handler.post { changeState(state, msg, true) }
}
override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
app.handler.post { updateTraffic(profileId, txRate, rxRate, txTotal, rxTotal) }
@@ -127,22 +129,13 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
}
}
- fun changeState(state: Int, msg: String? = null) {
+ fun changeState(state: Int, msg: String? = null, animate: Boolean = false) {
+ fabHelper.changeState(state, animate)
when (state) {
- BaseService.CONNECTING -> {
- fab.setImageResource(R.drawable.ic_start_busy)
- statusText.setText(R.string.connecting)
- }
- BaseService.CONNECTED -> {
- fab.setImageResource(R.drawable.ic_start_connected)
- statusText.setText(R.string.vpn_connected)
- }
- BaseService.STOPPING -> {
- fab.setImageResource(R.drawable.ic_start_busy)
- statusText.setText(R.string.stopping)
- }
+ BaseService.CONNECTING -> statusText.setText(R.string.connecting)
+ BaseService.CONNECTED -> statusText.setText(R.string.vpn_connected)
+ BaseService.STOPPING -> statusText.setText(R.string.stopping)
else -> {
- fab.setImageResource(R.drawable.ic_start_idle)
if (msg != null) {
Snackbar.make(findViewById(R.id.snackbar),
getString(R.string.vpn_error).format(Locale.ENGLISH, msg), Snackbar.LENGTH_LONG).show()
diff --git a/mobile/src/main/java/com/github/shadowsocks/bg/ServiceNotification.kt b/mobile/src/main/java/com/github/shadowsocks/bg/ServiceNotification.kt
index 3338c115..5d00b6d2 100644
--- a/mobile/src/main/java/com/github/shadowsocks/bg/ServiceNotification.kt
+++ b/mobile/src/main/java/com/github/shadowsocks/bg/ServiceNotification.kt
@@ -69,7 +69,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
.setContentTitle(profileName)
.setContentIntent(PendingIntent.getActivity(service, 0, Intent(service, MainActivity::class.java)
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0))
- .setSmallIcon(R.drawable.ic_start_connected)
+ .setSmallIcon(R.drawable.ic_service_active)
private val style = NotificationCompat.BigTextStyle(builder)
private var isVisible = true
diff --git a/mobile/src/main/java/com/github/shadowsocks/bg/TileService.kt b/mobile/src/main/java/com/github/shadowsocks/bg/TileService.kt
index 39c04852..939e1432 100644
--- a/mobile/src/main/java/com/github/shadowsocks/bg/TileService.kt
+++ b/mobile/src/main/java/com/github/shadowsocks/bg/TileService.kt
@@ -33,9 +33,9 @@ import android.service.quicksettings.TileService as BaseTileService
@RequiresApi(Build.VERSION_CODES.N)
class TileService : BaseTileService(), ShadowsocksConnection.Interface {
- private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_start_idle).setTint(0x79ffffff) }
- private val iconBusy by lazy { Icon.createWithResource(this, R.drawable.ic_start_busy) }
- private val iconConnected by lazy { Icon.createWithResource(this, R.drawable.ic_start_connected) }
+ private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_service_idle).setTint(0x79ffffff) }
+ private val iconBusy by lazy { Icon.createWithResource(this, R.drawable.ic_service_busy) }
+ private val iconConnected by lazy { Icon.createWithResource(this, R.drawable.ic_service_active) }
override val serviceCallback: IShadowsocksServiceCallback.Stub by lazy {
@RequiresApi(Build.VERSION_CODES.N)
diff --git a/mobile/src/main/java/com/github/shadowsocks/widget/ServiceButton.kt b/mobile/src/main/java/com/github/shadowsocks/widget/ServiceButton.kt
new file mode 100644
index 00000000..8bdda3cb
--- /dev/null
+++ b/mobile/src/main/java/com/github/shadowsocks/widget/ServiceButton.kt
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * *
+ * Copyright (C) 2017 by Max Lv *
+ * Copyright (C) 2017 by Mygod Studio *
+ * *
+ * 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 . *
+ * *
+ *******************************************************************************/
+
+package com.github.shadowsocks.widget
+
+import android.graphics.drawable.Drawable
+import android.support.annotation.DrawableRes
+import android.support.design.widget.FloatingActionButton
+import android.support.graphics.drawable.Animatable2Compat
+import android.support.graphics.drawable.AnimatedVectorDrawableCompat
+import com.github.shadowsocks.R
+import com.github.shadowsocks.bg.BaseService
+import java.util.*
+
+class ServiceButton(private val fab: FloatingActionButton) : Animatable2Compat.AnimationCallback() {
+ private fun createIcon(@DrawableRes resId: Int): AnimatedVectorDrawableCompat {
+ val result = AnimatedVectorDrawableCompat.create(fab.context, resId)!!
+ result.registerAnimationCallback(this)
+ return result
+ }
+
+ private val iconStopped by lazy { createIcon(R.drawable.ic_service_stopped) }
+ private val iconConnecting by lazy { createIcon(R.drawable.ic_service_connecting) }
+ private val iconConnected by lazy { createIcon(R.drawable.ic_service_connected) }
+ private val iconStopping by lazy { createIcon(R.drawable.ic_service_stopping) }
+ private val animationQueue = ArrayDeque()
+
+ fun changeState(state: Int, animate: Boolean) {
+ when (state) {
+ BaseService.CONNECTING -> changeState(iconConnecting, animate)
+ BaseService.CONNECTED -> changeState(iconConnected, animate)
+ BaseService.STOPPING -> changeState(iconStopping, animate)
+ else -> changeState(iconStopped, animate)
+ }
+ }
+
+ private fun counters(a: AnimatedVectorDrawableCompat, b: AnimatedVectorDrawableCompat): Boolean =
+ a == iconStopped && b == iconConnecting ||
+ a == iconConnecting && b == iconStopped ||
+ a == iconConnected && b == iconStopping ||
+ a == iconStopping && b == iconConnected
+
+ private fun changeState(icon: AnimatedVectorDrawableCompat, animate: Boolean) {
+ if (animate) {
+ if (animationQueue.size < 2 || !counters(animationQueue.last, icon)) {
+ animationQueue.add(icon)
+ if (animationQueue.size == 1) {
+ fab.setImageDrawable(icon)
+ icon.start()
+ }
+ } else animationQueue.removeLast()
+ } else {
+ animationQueue.peekFirst()?.stop()
+ animationQueue.clear()
+ fab.setImageDrawable(icon)
+ icon.stop()
+ }
+ }
+
+ override fun onAnimationEnd(drawable: Drawable) {
+ super.onAnimationEnd(drawable)
+ var next = animationQueue.peek() ?: return
+ if (next.current == drawable) {
+ animationQueue.pop()
+ next = animationQueue.peek() ?: return
+ }
+ fab.setImageDrawable(next)
+ next.start()
+ }
+}
diff --git a/mobile/src/main/res/drawable-anydpi-v21/ic_start_connected.xml b/mobile/src/main/res/drawable-anydpi-v21/ic_service_active.xml
similarity index 93%
rename from mobile/src/main/res/drawable-anydpi-v21/ic_start_connected.xml
rename to mobile/src/main/res/drawable-anydpi-v21/ic_service_active.xml
index 9b59c9ea..33062676 100755
--- a/mobile/src/main/res/drawable-anydpi-v21/ic_start_connected.xml
+++ b/mobile/src/main/res/drawable-anydpi-v21/ic_service_active.xml
@@ -5,6 +5,7 @@
android:viewportHeight="24">
diff --git a/mobile/src/main/res/drawable-hdpi/ic_start_connected.webp b/mobile/src/main/res/drawable-hdpi/ic_service_active.webp
similarity index 100%
rename from mobile/src/main/res/drawable-hdpi/ic_start_connected.webp
rename to mobile/src/main/res/drawable-hdpi/ic_service_active.webp
diff --git a/mobile/src/main/res/drawable-mdpi/ic_start_connected.webp b/mobile/src/main/res/drawable-mdpi/ic_service_active.webp
similarity index 100%
rename from mobile/src/main/res/drawable-mdpi/ic_start_connected.webp
rename to mobile/src/main/res/drawable-mdpi/ic_service_active.webp
diff --git a/mobile/src/main/res/drawable-xhdpi/ic_start_connected.webp b/mobile/src/main/res/drawable-xhdpi/ic_service_active.webp
similarity index 100%
rename from mobile/src/main/res/drawable-xhdpi/ic_start_connected.webp
rename to mobile/src/main/res/drawable-xhdpi/ic_service_active.webp
diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_start_connected.webp b/mobile/src/main/res/drawable-xxhdpi/ic_service_active.webp
similarity index 100%
rename from mobile/src/main/res/drawable-xxhdpi/ic_start_connected.webp
rename to mobile/src/main/res/drawable-xxhdpi/ic_service_active.webp
diff --git a/mobile/src/main/res/drawable/ic_start_busy.xml b/mobile/src/main/res/drawable/ic_service_busy.xml
similarity index 94%
rename from mobile/src/main/res/drawable/ic_start_busy.xml
rename to mobile/src/main/res/drawable/ic_service_busy.xml
index 9ff8f251..910b5350 100755
--- a/mobile/src/main/res/drawable/ic_start_busy.xml
+++ b/mobile/src/main/res/drawable/ic_service_busy.xml
@@ -5,6 +5,7 @@
android:viewportHeight="24">
diff --git a/mobile/src/main/res/drawable/ic_service_connected.xml b/mobile/src/main/res/drawable/ic_service_connected.xml
new file mode 100644
index 00000000..e5a739f3
--- /dev/null
+++ b/mobile/src/main/res/drawable/ic_service_connected.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/drawable/ic_service_connecting.xml b/mobile/src/main/res/drawable/ic_service_connecting.xml
new file mode 100644
index 00000000..45f8f155
--- /dev/null
+++ b/mobile/src/main/res/drawable/ic_service_connecting.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/drawable/ic_service_idle.xml b/mobile/src/main/res/drawable/ic_service_idle.xml
new file mode 100755
index 00000000..205b81d4
--- /dev/null
+++ b/mobile/src/main/res/drawable/ic_service_idle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/mobile/src/main/res/drawable/ic_service_stopped.xml b/mobile/src/main/res/drawable/ic_service_stopped.xml
new file mode 100644
index 00000000..3d2e28bc
--- /dev/null
+++ b/mobile/src/main/res/drawable/ic_service_stopped.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/drawable/ic_service_stopping.xml b/mobile/src/main/res/drawable/ic_service_stopping.xml
new file mode 100644
index 00000000..8fa85ae2
--- /dev/null
+++ b/mobile/src/main/res/drawable/ic_service_stopping.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/drawable/ic_start_idle.xml b/mobile/src/main/res/drawable/ic_start_idle.xml
deleted file mode 100755
index 2239050e..00000000
--- a/mobile/src/main/res/drawable/ic_start_idle.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
diff --git a/mobile/src/main/res/layout/layout_main.xml b/mobile/src/main/res/layout/layout_main.xml
index 39d4912f..d3c12937 100644
--- a/mobile/src/main/res/layout/layout_main.xml
+++ b/mobile/src/main/res/layout/layout_main.xml
@@ -84,7 +84,7 @@
app:backgroundTint="@color/material_primary_500"
app:fabSize="normal"
app:pressedTranslationZ="6dp"
- app:srcCompat="@drawable/ic_start_idle"/>
+ app:srcCompat="@drawable/ic_service_idle"/>