[WIP] refactor UI
This commit is contained in:
10 files changed
+256
-121
No files matched your search
+2
-1
@@ -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"
|
||||
}
|
||||
Reference in new issue
Block a user