Add some sweet animation to replace old circular progress

This commit is contained in:
Mygod
2017-12-19 17:01:11 -08:00
parent 6d6682d4a7
commit 540b1d3710
18 changed files with 212 additions and 37 deletions
@@ -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<CrossFadeSlidingPaneLayout>? = 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()
@@ -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
@@ -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)
@@ -0,0 +1,87 @@
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
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<AnimatedVectorDrawableCompat>()
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()
}
}