[WIP] refactor UI

This commit is contained in:
Kr328 committed 2020-01-22 13:23:27 +08:00
1 parent cca7ec81e1
commit 114456a1f2
10 files changed
+256 -121

No files matched your search

+2 -1
View File
@@ -40,7 +40,8 @@ dependencies {
implementation project(":core")
implementation project(":service")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0"
implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.preference:preference:1.1.0'
@@ -1,103 +1,65 @@
package com.github.kr328.clash
import android.content.ComponentName
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import com.github.kr328.clash.core.event.*
import com.github.kr328.clash.core.utils.Log
import com.github.kr328.clash.remote.Broadcasts
import com.github.kr328.clash.service.ClashService
import com.github.kr328.clash.service.IClashEventObserver
import com.github.kr328.clash.service.IClashService
import java.util.concurrent.LinkedBlockingQueue
import kotlin.concurrent.thread
import com.github.kr328.clash.service.data.ClashProfileEntity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
abstract class BaseActivity : AppCompatActivity(), IClashEventObserver {
companion object {
private var activityCount: Int = 0
abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() {
class EmptyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {}
}
private val paddingRequest = LinkedBlockingQueue<(IClashService) -> Unit>()
private var clashConnection: ClashConnection? = null
class ClashConnection : ServiceConnection {
private var requestHandler: Thread? = null
private var clash: IClashService? = null
override fun onServiceDisconnected(name: ComponentName?) {
synchronized(BaseActivity::class.java) {
Log.i("ClashService disconnected")
requestHandler?.interrupt()
requestHandler = null
}
private val receiver = object : Broadcasts.Receiver {
override fun onStarted() {
launch {
onClashStarted()
}
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
synchronized(BaseActivity::class) {
Log.i("ClashService connected")
override fun onStopped(cause: String?) {
launch {
onClashStopped(cause)
}
}
clash = IClashService.Stub.asInterface(service)
requestHandler = thread {
try {
while (!Thread.currentThread().isInterrupted) {
val block = paddingRequest.take()
clash?.run(block)
}
} catch (e: InterruptedException) {
}
Log.i("ClashConnect exited")
synchronized(BaseActivity::class.java) {
clash = null
requestHandler = null
}
}
}
override fun onProfileChanged(active: ClashProfileEntity?) {
launch {
onClashProfileChanged(active)
}
}
}
protected fun runClash(block: (IClashService) -> Unit) {
paddingRequest.offer(block)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
synchronized(BaseActivity::class.java) {
if (clashConnection == null) {
clashConnection = ClashConnection()
applicationContext.bindService(
Intent(this, ClashService::class.java),
clashConnection!!,
Context.BIND_AUTO_CREATE
)
}
activityCount++
}
}
override fun onDestroy() {
synchronized(BaseActivity::class.java) {
if (--activityCount <= 0) {
if (clashConnection != null) {
applicationContext.unbindService(clashConnection!!)
clashConnection?.onServiceDisconnected(null)
clashConnection = null
}
}
val isClashRunning: Boolean
get() {
return EmptyBroadcastReceiver().peekService(
this,
Intent(this, ClashService::class.java)
) != null
}
super.onDestroy()
open suspend fun onClashStarted() {}
open suspend fun onClashStopped(reason: String?) {}
open suspend fun onClashProfileChanged(active: ClashProfileEntity?) {}
override fun onStart() {
super.onStart()
Broadcasts.register(receiver)
}
override fun onStop() {
super.onStop()
Broadcasts.unregister(receiver)
}
override fun setSupportActionBar(toolbar: Toolbar?) {
@@ -116,39 +78,9 @@ abstract class BaseActivity : AppCompatActivity(), IClashEventObserver {
return true
}
private val observerBinder = object : IClashEventObserver.Stub() {
override fun onLogEvent(event: LogEvent?) =
this@BaseActivity.onLogEvent(event)
override fun onDestroy() {
cancel()
override fun onProcessEvent(event: ProcessEvent?) =
this@BaseActivity.onProcessEvent(event)
override fun onErrorEvent(event: ErrorEvent?) =
this@BaseActivity.onErrorEvent(event)
override fun onTrafficEvent(event: TrafficEvent?) =
this@BaseActivity.onTrafficEvent(event)
override fun onBandwidthEvent(event: BandwidthEvent?) {
this@BaseActivity.onBandwidthEvent(event)
}
override fun onProfileChanged(event: ProfileChangedEvent?) =
this@BaseActivity.onProfileChanged(event)
override fun onProfileReloaded(event: ProfileReloadEvent?) {
this@BaseActivity.onProfileReloaded(event)
}
}
override fun onLogEvent(event: LogEvent?) {}
override fun onErrorEvent(event: ErrorEvent?) {}
override fun onProfileChanged(event: ProfileChangedEvent?) {}
override fun onProcessEvent(event: ProcessEvent?) {}
override fun onTrafficEvent(event: TrafficEvent?) {}
override fun onBandwidthEvent(event: BandwidthEvent?) {}
override fun onProfileReloaded(event: ProfileReloadEvent?) {}
override fun asBinder(): IBinder {
return observerBinder
super.onDestroy()
}
}
@@ -7,6 +7,7 @@ import com.crashlytics.android.Crashlytics
import com.github.kr328.clash.core.Constants
import com.github.kr328.clash.core.utils.ByteFormatter
import com.github.kr328.clash.core.utils.Log
import com.github.kr328.clash.remote.ClashClient
import com.google.firebase.FirebaseApp
import io.fabric.sdk.android.Fabric
import java.io.File
@@ -15,7 +16,6 @@ import java.security.MessageDigest
import java.util.zip.ZipFile
import kotlin.concurrent.thread
class MainApplication : Application() {
companion object {
const val KEY_PROXY_MODE = "key_proxy_mode"
@@ -68,6 +68,8 @@ class MainApplication : Application() {
Crashlytics.setBool(CRASHLYTICS_SPLIT_APK_KEY, detectSplitArchive())
Crashlytics.setString(CRASHLYTICS_BASE_SIZE_KEY, getBaseApkSize())
Crashlytics.setUserIdentifier(userIdentifier)
ClashClient.init(this)
}
private fun detectFromPlay(): Boolean {
@@ -0,0 +1,65 @@
package com.github.kr328.clash.remote
import android.app.Application
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.github.kr328.clash.service.Intents
import com.github.kr328.clash.service.data.ClashProfileEntity
object Broadcasts {
interface Receiver {
fun onStarted()
fun onStopped(cause: String?)
fun onProfileChanged(active: ClashProfileEntity?)
}
private val receivers = mutableListOf<Receiver>()
private val broadcastReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when ( intent?.action ) {
Intents.INTENT_ACTION_CLASH_STARTED ->
receivers.forEach {
it.onStarted()
}
Intents.INTENT_ACTION_CLASH_STOPPED ->
receivers.forEach {
it.onStopped(intent.getStringExtra(Intents.INTENT_EXTRA_CLASH_STOP_REASON))
}
Intents.INTENT_ACTION_PROFILE_CHANGED ->
receivers.forEach {
it.onProfileChanged(intent.getParcelableExtra(Intents.INTENT_EXTRA_PROFILE_ACTIVE))
}
}
}
}
fun register(receiver: Receiver) {
receivers.add(receiver)
}
fun unregister(receiver: Receiver) {
receivers.remove(receiver)
}
fun init(application: Application) {
ProcessLifecycleOwner.get().lifecycle.addObserver(object: DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
application.registerReceiver(broadcastReceiver, IntentFilter().apply {
addAction(Intents.INTENT_ACTION_PROFILE_CHANGED)
addAction(Intents.INTENT_ACTION_CLASH_STOPPED)
addAction(Intents.INTENT_ACTION_CLASH_STARTED)
})
}
override fun onStop(owner: LifecycleOwner) {
application.unregisterReceiver(broadcastReceiver)
}
})
}
}
@@ -0,0 +1,7 @@
package com.github.kr328.clash.remote
suspend fun withClash(block: suspend (ClashClient) -> Unit) {
val client = ClashClient.instance ?: return
block(client)
}
@@ -0,0 +1,33 @@
package com.github.kr328.clash.remote
import com.github.kr328.clash.core.event.BandwidthEvent
import com.github.kr328.clash.core.event.LogEvent
import com.github.kr328.clash.service.ipc.ParcelablePipe
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.channels.Channel
class LogChannel(private val pipe: ParcelablePipe): Channel<LogEvent> by Channel(Channel.CONFLATED) {
init {
pipe.onReceive {
offer(it as LogEvent)
}
}
override fun cancel(cause: CancellationException?) {
pipe.close()
close(cause)
}
}
class BandwidthChannel(private val pipe: ParcelablePipe): Channel<BandwidthEvent> by Channel(Channel.CONFLATED) {
init {
pipe.onReceive {
offer(it as BandwidthEvent)
}
}
override fun cancel(cause: CancellationException?) {
pipe.close()
close(cause)
}
}
@@ -0,0 +1,95 @@
package com.github.kr328.clash.remote
import android.app.Application
import android.content.*
import android.os.IBinder
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.github.kr328.clash.MainApplication
import com.github.kr328.clash.core.event.BandwidthEvent
import com.github.kr328.clash.core.event.LogEvent
import com.github.kr328.clash.core.model.General
import com.github.kr328.clash.core.model.ProxyGroup
import com.github.kr328.clash.service.ClashManagerService
import com.github.kr328.clash.service.IClashManager
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.withContext
class ClashClient(val service: IClashManager) {
companion object {
var instance: ClashClient? = null
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
instance = null
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
if (service != null)
instance = ClashClient(IClashManager.Stub.asInterface(service))
}
}
fun init(application: Application) {
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
application.bindService(
Intent(application, ClashManagerService::class.java),
connection,
Context.BIND_AUTO_CREATE
)
}
override fun onStop(owner: LifecycleOwner) {
application.unbindService(connection)
}
})
}
}
private val openedChannel: MutableList<Channel<*>> = mutableListOf()
suspend fun setSelectProxy(name: String, proxy: String): Boolean = withContext(Dispatchers.IO) {
return@withContext service.setSelectProxy(name, proxy)
}
suspend fun startHealthCheck(group: String) = withContext(Dispatchers.IO) {
CompletableDeferred<Unit>().apply {
service.startHealthCheck(group).whenComplete { _, u ->
if (u != null)
completeExceptionally(u)
else
complete(Unit)
}
}
}.await()
suspend fun queryAllProxyGroups(): Array<ProxyGroup> = withContext(Dispatchers.IO) {
service.queryAllProxies()
}
suspend fun queryGeneral(): General = withContext(Dispatchers.IO) {
service.queryGeneral()
}
suspend fun openLogChannel(): ReceiveChannel<LogEvent> = withContext(Dispatchers.IO) {
LogChannel(service.openLogEvent()).also {
openedChannel.add(it)
}
}
suspend fun openBandwidthChannel(): ReceiveChannel<BandwidthEvent> =
withContext(Dispatchers.IO) {
BandwidthChannel(service.openBandwidthEvent()).also {
openedChannel.add(it)
}
}
fun close() {
for (channel in openedChannel)
channel.cancel()
}
}
@@ -125,7 +125,7 @@ class ClashProfileManager(private val context: Context, private val database: Cl
context.sendBroadcastSelf(
Intent(Intents.INTENT_ACTION_PROFILE_CHANGED)
.putExtra(Intents.INTENT_ACTION_PROFILE_ACTIVE_NAME, active?.name)
.putExtra(Intents.INTENT_EXTRA_PROFILE_ACTIVE, active)
)
}
}
@@ -85,7 +85,7 @@ class ClashService : Service() {
sendBroadcastSelf(
Intent(Intents.INTENT_ACTION_CLASH_STOPPED)
.putExtra(Intents.INTENT_ACTION_CLASH_STOP_REASON, stopReason)
.putExtra(Intents.INTENT_EXTRA_CLASH_STOP_REASON, stopReason)
)
unregisterReceiver(profileObserver)
@@ -10,8 +10,8 @@ object Intents {
const val INTENT_ACTION_PROFILE_CHANGED =
"${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.profile.changed"
const val INTENT_ACTION_CLASH_STOP_REASON =
const val INTENT_EXTRA_CLASH_STOP_REASON =
"${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.extra.clash.stop.reason"
const val INTENT_ACTION_PROFILE_ACTIVE_NAME =
const val INTENT_EXTRA_PROFILE_ACTIVE =
"${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.extra.profile.active.name"
}