Add some sweet animation to replace old circular progress
@@ -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">
|
||||
|
||||
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
|
||||
android:value="true" />
|
||||
@@ -139,7 +139,7 @@
|
||||
<service android:name=".bg.TileService" android:label="@string/quick_toggle"
|
||||
android:process=":bg"
|
||||
android:directBootAware="true"
|
||||
android:icon="@drawable/ic_start_connected"
|
||||
android:icon="@drawable/ic_service_active"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:name="path"
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 21.25 2.28 L 17.55 18.55 L 9.26 15.89 L 16.58 7.16 L 6.83 15.37 L 0 12.8 L 21.25 2.28 ZM 9.45 17.56 L 12.09 18.41 L 9.46 22 L 9.45 17.56 Z" />
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 400 B |
|
Before Width: | Height: | Size: 298 B After Width: | Height: | Size: 298 B |
|
Before Width: | Height: | Size: 510 B After Width: | Height: | Size: 510 B |
|
Before Width: | Height: | Size: 748 B After Width: | Height: | Size: 748 B |
@@ -5,6 +5,7 @@
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:name="path"
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M17.68,9l-1.59,7L12.7,14.89l5-5.93M10,10.08l-3.57,3L5,12.55l5-2.47M21.25,2.28L0,12.8l6.83,2.57,9.76-8.21L9.26,15.89l8.29,2.67,3.7-16.27h0ZM 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<animated-vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:drawable="@drawable/ic_service_active">
|
||||
<target android:name="path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:valueFrom="M 17.68 9 L 16.09 16 L 12.7 14.89 L 17.7 8.96 M 10 10.08 L 6.43 13.08 L 5 12.55 L 10 10.08 M 21.25 2.28 L 0 12.8 L 6.83 15.37 L 16.59 7.16 L 9.26 15.89 L 17.55 18.56 L 21.25 2.29 L 21.25 2.29 Z M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z"
|
||||
android:valueTo="M 15.5 13.28 L 15.5 13.28 L 15.5 13.28 L 15.5 13.28 M 7.14 11.9 L 7.14 11.9 L 7.14 11.9 L 7.14 11.9 M 21.25 2.28 L 0 12.8 L 6.83 15.37 L 16.59 7.16 L 9.26 15.89 L 17.55 18.56 L 21.25 2.29 L 21.25 2.29 Z M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z"
|
||||
android:valueType="pathType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<animated-vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:drawable="@drawable/ic_service_idle">
|
||||
<target android:name="strike_thru_path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:valueFrom="M 19.73 22 L 21 20.73 L 3.27 3 L 2 4.27 Z"
|
||||
android:valueTo="M 2 4.27 L 3.27 3 L 3.27 3 L 2 4.27 Z"
|
||||
android:valueType="pathType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="strike_thru_mask">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:valueFrom="M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 21 20.73 L 22.27 19.46 Z"
|
||||
android:valueTo="M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 3.27 3 L 4.54 1.73 Z"
|
||||
android:valueType="pathType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:name="strike_thru_path"
|
||||
android:pathData="M 19.73 22 L 21 20.73 L 3.27 3 L 2 4.27 Z"
|
||||
android:fillColor="#fff"
|
||||
android:strokeWidth="1"/>
|
||||
<clip-path
|
||||
android:name="strike_thru_mask"
|
||||
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 21 20.73 L 22.27 19.46 Z"/>
|
||||
<path
|
||||
android:name="holey_icon"
|
||||
android:pathData="M17.68,9l-1.59,7L12.7,14.89l5-5.93M10,10.08l-3.57,3L5,12.55l5-2.47M21.25,2.28L0,12.8l6.83,2.57,9.76-8.21L9.26,15.89l8.29,2.67,3.7-16.27h0ZM 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z"
|
||||
android:fillColor="#fff"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<animated-vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:drawable="@drawable/ic_service_idle">
|
||||
<target android:name="strike_thru_path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:valueFrom="M 2 4.27 L 3.27 3 L 3.27 3 L 2 4.27 Z"
|
||||
android:valueTo="M 19.73 22 L 21 20.73 L 3.27 3 L 2 4.27 Z"
|
||||
android:valueType="pathType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="strike_thru_mask">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:valueFrom="M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 3.27 3 L 4.54 1.73 Z"
|
||||
android:valueTo="M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 21 20.73 L 22.27 19.46 Z"
|
||||
android:valueType="pathType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<animated-vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:drawable="@drawable/ic_service_active">
|
||||
<target android:name="path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:valueFrom="M 15.5 13.28 L 15.5 13.28 L 15.5 13.28 L 15.5 13.28 M 7.14 11.9 L 7.14 11.9 L 7.14 11.9 L 7.14 11.9 M 21.25 2.28 L 0 12.8 L 6.83 15.37 L 16.59 7.16 L 9.26 15.89 L 17.55 18.56 L 21.25 2.29 L 21.25 2.29 Z M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z"
|
||||
android:valueTo="M 17.68 9 L 16.09 16 L 12.7 14.89 L 17.7 8.96 M 10 10.08 L 6.43 13.08 L 5 12.55 L 10 10.08 M 21.25 2.28 L 0 12.8 L 6.83 15.37 L 16.59 7.16 L 9.26 15.89 L 17.55 18.56 L 21.25 2.29 L 21.25 2.29 Z M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z"
|
||||
android:valueType="pathType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -1,15 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 ZM 13 10.17 L 16.58 7.16 L 13.57 10.75 L 14.99 12.16 L 17.68 8.96 L 16.59 13.76 L 18.26 15.43 L 21.25 2.28 L 10.45 7.62 L 13 10.17 ZM 15.93 15.93 L 12.71 14.89 L 13.7 13.7 L 12.28 12.28 L 9.26 15.89 L 17.55 18.55 L 17.74 17.74 L 15.93 15.93 ZM8.56,8.56L0,12.8l6.83,2.57,4.64-3.9ZM6.44,13.08L5,12.55l5-2.47Z" />
|
||||
<path
|
||||
android:strokeColor="#fff"
|
||||
android:strokeWidth="2"
|
||||
android:strokeMiterLimit="10"
|
||||
android:pathData="M0.71,2.12L21.88,23.29Z" />
|
||||
</vector>
|
||||
@@ -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"/>
|
||||
</FrameLayout>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/snackbar"
|
||||
|
||||