[WIP] UI refactor
This commit is contained in:
27 files changed
+468
-80
No files matched your search
@@ -6,14 +6,14 @@ import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.*
|
||||
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import com.github.kr328.clash.preference.UiPreferences
|
||||
import com.github.kr328.clash.remote.Broadcasts
|
||||
import com.github.kr328.clash.service.data.ClashProfileEntity
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -53,6 +53,9 @@ abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope()
|
||||
get() = Broadcasts.clashRunning
|
||||
val rootView: View
|
||||
get() = overrideRootView ?: window.decorView
|
||||
var menu: Menu? = null
|
||||
lateinit var uiPreference: UiPreferences
|
||||
private set
|
||||
|
||||
open suspend fun onClashStarted() {}
|
||||
open suspend fun onClashStopped(reason: String?) {}
|
||||
@@ -82,6 +85,8 @@ abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope()
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
uiPreference = UiPreferences(this)
|
||||
|
||||
resetLightNavigationBar()
|
||||
}
|
||||
|
||||
@@ -97,9 +102,27 @@ abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope()
|
||||
Broadcasts.unregister(receiver)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
this.menu = menu
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if ( super.onOptionsItemSelected(item) )
|
||||
return true
|
||||
|
||||
if ( item.itemId == android.R.id.home ) {
|
||||
onSupportNavigateUp()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setSupportActionBar(toolbar: Toolbar?) {
|
||||
super.setSupportActionBar(toolbar)
|
||||
|
||||
supportActionBar?.setDisplayShowHomeEnabled(shouldDisplayHomeAsUpEnabled())
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(shouldDisplayHomeAsUpEnabled())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.kr328.clash.adapter.AbstractProxyAdapter
|
||||
import com.github.kr328.clash.adapter.GridProxyAdapter
|
||||
import com.github.kr328.clash.adapter.ProxyChipAdapter
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import com.github.kr328.clash.preference.UiPreferences
|
||||
import com.github.kr328.clash.remote.withClash
|
||||
import com.github.kr328.clash.utils.ProxySorter
|
||||
import com.github.kr328.clash.view.ProxiesTabMediator
|
||||
@@ -14,15 +19,21 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ProxiesActivity : BaseActivity() {
|
||||
private val activity: ProxiesActivity
|
||||
get() = this
|
||||
private lateinit var mediator: ProxiesTabMediator
|
||||
private val doScrollToLastProxy by lazy {
|
||||
val selected = uiPreference.get(UiPreferences.LAST_SELECT_GROUP)
|
||||
|
||||
launch {
|
||||
mediator.scrollToDirect(selected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_proxies)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
val mediator = ProxiesTabMediator(this, mainList, chipList)
|
||||
mediator = ProxiesTabMediator(this, mainList, chipList)
|
||||
|
||||
GridProxyAdapter(this).apply {
|
||||
mainList.layoutManager = layoutManager
|
||||
@@ -46,28 +57,174 @@ class ProxiesActivity : BaseActivity() {
|
||||
launch {
|
||||
mediator.exec()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
refreshList()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
uiPreference.edit {
|
||||
put(UiPreferences.LAST_SELECT_GROUP, (chipList.adapter!! as ProxyChipAdapter).selected)
|
||||
}
|
||||
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
if (!super.onCreateOptionsMenu(menu))
|
||||
return false
|
||||
|
||||
menuInflater.inflate(R.menu.proxies, menu)
|
||||
|
||||
setupMenu()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (super.onOptionsItemSelected(item))
|
||||
return true
|
||||
|
||||
if (item.itemId == R.id.menuRefresh) {
|
||||
refreshList()
|
||||
return true
|
||||
}
|
||||
|
||||
launch {
|
||||
when (item.itemId) {
|
||||
R.id.modeDirect -> {
|
||||
withClash {
|
||||
setProxyMode(General.Mode.DIRECT)
|
||||
}
|
||||
}
|
||||
R.id.modeGlobal -> {
|
||||
withClash {
|
||||
setProxyMode(General.Mode.GLOBAL)
|
||||
}
|
||||
}
|
||||
R.id.modeRule -> {
|
||||
withClash {
|
||||
setProxyMode(General.Mode.RULE)
|
||||
}
|
||||
}
|
||||
R.id.groupDefault -> {
|
||||
uiPreference.edit {
|
||||
put(UiPreferences.PROXY_GROUP_SORT, UiPreferences.PROXY_SORT_DEFAULT)
|
||||
}
|
||||
}
|
||||
R.id.groupName -> {
|
||||
uiPreference.edit {
|
||||
put(UiPreferences.PROXY_GROUP_SORT, UiPreferences.PROXY_SORT_NAME)
|
||||
}
|
||||
}
|
||||
R.id.groupDelay -> {
|
||||
uiPreference.edit {
|
||||
put(UiPreferences.PROXY_GROUP_SORT, UiPreferences.PROXY_SORT_DELAY)
|
||||
}
|
||||
}
|
||||
R.id.proxyDefault -> {
|
||||
uiPreference.edit {
|
||||
put(UiPreferences.PROXY_PROXY_SORT, UiPreferences.PROXY_SORT_DEFAULT)
|
||||
}
|
||||
}
|
||||
R.id.proxyName -> {
|
||||
uiPreference.edit {
|
||||
put(UiPreferences.PROXY_PROXY_SORT, UiPreferences.PROXY_SORT_NAME)
|
||||
}
|
||||
}
|
||||
R.id.proxyDelay -> {
|
||||
uiPreference.edit {
|
||||
put(UiPreferences.PROXY_PROXY_SORT, UiPreferences.PROXY_SORT_DELAY)
|
||||
}
|
||||
}
|
||||
else -> return@launch
|
||||
}
|
||||
|
||||
item.isChecked = true
|
||||
|
||||
refreshList()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun onClashStarted() {
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun setupMenu() {
|
||||
launch {
|
||||
val general = withClash {
|
||||
queryGeneral()
|
||||
}
|
||||
|
||||
menu?.apply {
|
||||
when (general.mode) {
|
||||
General.Mode.DIRECT ->
|
||||
findItem(R.id.modeDirect).isChecked = true
|
||||
General.Mode.GLOBAL ->
|
||||
findItem(R.id.modeGlobal).isChecked = true
|
||||
General.Mode.RULE ->
|
||||
findItem(R.id.modeRule).isChecked = true
|
||||
}
|
||||
when (uiPreference.get(UiPreferences.PROXY_GROUP_SORT)) {
|
||||
UiPreferences.PROXY_SORT_DEFAULT ->
|
||||
findItem(R.id.groupDefault).isChecked = true
|
||||
UiPreferences.PROXY_SORT_NAME ->
|
||||
findItem(R.id.groupName).isChecked = true
|
||||
UiPreferences.PROXY_SORT_DELAY ->
|
||||
findItem(R.id.proxyDelay).isChecked = true
|
||||
}
|
||||
when (uiPreference.get(UiPreferences.PROXY_PROXY_SORT)) {
|
||||
UiPreferences.PROXY_SORT_DEFAULT ->
|
||||
findItem(R.id.proxyDefault).isChecked = true
|
||||
UiPreferences.PROXY_SORT_NAME ->
|
||||
findItem(R.id.proxyName).isChecked = true
|
||||
UiPreferences.PROXY_SORT_DELAY ->
|
||||
findItem(R.id.proxyDelay).isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshList() {
|
||||
launch {
|
||||
val general = withClash {
|
||||
queryGeneral()
|
||||
}
|
||||
val proxies = withClash {
|
||||
queryAllProxyGroups()
|
||||
}
|
||||
|
||||
val sorter = ProxySorter(ProxySorter.Order.DEFAULT, ProxySorter.Order.DEFAULT)
|
||||
val groupSort = when (uiPreference.get(UiPreferences.PROXY_GROUP_SORT)) {
|
||||
UiPreferences.PROXY_SORT_DEFAULT ->
|
||||
ProxySorter.Order.DEFAULT
|
||||
UiPreferences.PROXY_SORT_NAME ->
|
||||
ProxySorter.Order.NAME_INCREASE
|
||||
UiPreferences.PROXY_SORT_DELAY ->
|
||||
ProxySorter.Order.DELAY_INCREASE
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val proxySort = when (uiPreference.get(UiPreferences.PROXY_PROXY_SORT)) {
|
||||
UiPreferences.PROXY_SORT_DEFAULT ->
|
||||
ProxySorter.Order.DEFAULT
|
||||
UiPreferences.PROXY_SORT_NAME ->
|
||||
ProxySorter.Order.NAME_INCREASE
|
||||
UiPreferences.PROXY_SORT_DELAY ->
|
||||
ProxySorter.Order.DELAY_INCREASE
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val sorter = ProxySorter(groupSort, proxySort)
|
||||
|
||||
val sorted = withContext(Dispatchers.Default) {
|
||||
sorter.sort(proxies.toList())
|
||||
}.run {
|
||||
when (general.mode) {
|
||||
General.Mode.GLOBAL -> this
|
||||
General.Mode.DIRECT -> emptyList()
|
||||
General.Mode.RULE -> this.filter { it.name != "GLOBAL" }
|
||||
}
|
||||
}
|
||||
|
||||
(mainList.adapter!! as AbstractProxyAdapter).apply {
|
||||
@@ -80,6 +237,8 @@ class ProxiesActivity : BaseActivity() {
|
||||
chips = sorted.map { it.name }
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
doScrollToLastProxy
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import com.google.android.material.card.MaterialCardView
|
||||
|
||||
class ProxyChipAdapter(
|
||||
private val context: Context,
|
||||
private val onClick: (String) -> Unit
|
||||
val onClick: (String) -> Unit
|
||||
) :
|
||||
RecyclerView.Adapter<ProxyChipAdapter.Holder>() {
|
||||
var chips = listOf<String>()
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.github.kr328.clash.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class UiPreferences(context: Context) {
|
||||
companion object {
|
||||
private const val FILE_NAME = "ui"
|
||||
|
||||
const val PROXY_SORT_DEFAULT = "default"
|
||||
const val PROXY_SORT_NAME = "name"
|
||||
const val PROXY_SORT_DELAY = "delay"
|
||||
|
||||
val PROXY_GROUP_SORT = StringEntry("proxy_group_sort", PROXY_SORT_DEFAULT)
|
||||
val PROXY_PROXY_SORT = StringEntry("proxy_proxy_sort", PROXY_SORT_DEFAULT)
|
||||
val LAST_SELECT_GROUP = StringEntry("last_select_group", "")
|
||||
}
|
||||
|
||||
interface Entry<T> {
|
||||
fun get(sharedPreferences: SharedPreferences): T
|
||||
fun put(editor: SharedPreferences.Editor, value: T)
|
||||
}
|
||||
|
||||
class StringEntry(private val key: String, private val defaultValue: String? = null) :
|
||||
Entry<String> {
|
||||
override fun get(sharedPreferences: SharedPreferences): String {
|
||||
return sharedPreferences.getString(key, defaultValue)!!
|
||||
}
|
||||
|
||||
override fun put(editor: SharedPreferences.Editor, value: String) {
|
||||
editor.putString(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
class Editor(private val editor: SharedPreferences.Editor) {
|
||||
fun <T, E : Entry<T>> put(e: E, value: T) {
|
||||
e.put(editor, value)
|
||||
}
|
||||
}
|
||||
|
||||
private val sharedPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
fun <T, E : Entry<T>> get(e: E): T {
|
||||
return e.get(sharedPreferences)
|
||||
}
|
||||
|
||||
fun edit(block: Editor.() -> Unit) {
|
||||
val editor = sharedPreferences.edit()
|
||||
|
||||
Editor(editor).apply(block)
|
||||
|
||||
editor.apply()
|
||||
}
|
||||
}
|
||||
@@ -75,13 +75,24 @@ object Broadcasts {
|
||||
null
|
||||
)
|
||||
|
||||
clashRunning = pong != null
|
||||
val current = pong != null
|
||||
if (current != clashRunning) {
|
||||
clashRunning = current
|
||||
|
||||
if (current) {
|
||||
receivers.forEach {
|
||||
it.onStarted()
|
||||
}
|
||||
} else {
|
||||
receivers.forEach {
|
||||
it.onStopped(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
application.unregisterReceiver(broadcastReceiver)
|
||||
|
||||
clashRunning = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,4 +40,8 @@ class ClashClient(private val service: IClashManager) {
|
||||
withContext(Dispatchers.IO) {
|
||||
service.queryBandwidth()
|
||||
}
|
||||
|
||||
suspend fun setProxyMode(mode: General.Mode) = withContext(Dispatchers.IO) {
|
||||
service.setProxyMode(mode.toString())
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
@@ -82,8 +83,12 @@ object Remote {
|
||||
}
|
||||
|
||||
fun init(application: Application) {
|
||||
val handler = Handler()
|
||||
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
handler.removeMessages(0)
|
||||
|
||||
GlobalScope.launch {
|
||||
if (!verifyApk(application)) {
|
||||
application.startActivity(ApkBrokenActivity::class.intent)
|
||||
@@ -109,17 +114,19 @@ object Remote {
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
clashConnection?.also {
|
||||
application.unbindService(it)
|
||||
it.onServiceDisconnected(null)
|
||||
}
|
||||
profileConnection?.also {
|
||||
application.unbindService(it)
|
||||
it.onServiceDisconnected(null)
|
||||
}
|
||||
handler.postDelayed({
|
||||
clashConnection?.also {
|
||||
application.unbindService(it)
|
||||
it.onServiceDisconnected(null)
|
||||
}
|
||||
profileConnection?.also {
|
||||
application.unbindService(it)
|
||||
it.onServiceDisconnected(null)
|
||||
}
|
||||
|
||||
clashConnection = null
|
||||
profileConnection = null
|
||||
clashConnection = null
|
||||
profileConnection = null
|
||||
}, 5000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.github.kr328.clash.view
|
||||
|
||||
import android.util.DisplayMetrics
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.ProxiesActivity
|
||||
import com.github.kr328.clash.adapter.AbstractProxyAdapter
|
||||
import com.github.kr328.clash.adapter.ProxyChipAdapter
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class ProxiesTabMediator(
|
||||
private val context: ProxiesActivity,
|
||||
@@ -38,13 +41,33 @@ class ProxiesTabMediator(
|
||||
|
||||
currentChecked = currentGroup
|
||||
|
||||
(chipView.adapter!! as ProxyChipAdapter).apply {
|
||||
selected = currentChecked
|
||||
val adapter = chipView.adapter!! as ProxyChipAdapter
|
||||
|
||||
if (!preventChipScroll)
|
||||
chipView.smoothScrollToPosition(chips.indexOf(currentChecked))
|
||||
adapter.selected = currentChecked
|
||||
|
||||
if (!preventChipScroll) {
|
||||
val index = adapter.chips.indexOf(currentChecked)
|
||||
|
||||
if (index < 0)
|
||||
continue
|
||||
|
||||
chipView.smoothScrollToPosition(index)
|
||||
}
|
||||
|
||||
delay(200)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun scrollToDirect(group: String) {
|
||||
val position = (proxiesView.adapter!! as AbstractProxyAdapter).getGroupPosition(group) ?: return
|
||||
|
||||
when ( val m = proxiesView.layoutManager ) {
|
||||
is GridLayoutManager ->
|
||||
m.scrollToPositionWithOffset(position, 0)
|
||||
is LinearLayoutManager ->
|
||||
m.scrollToPositionWithOffset(position, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/activity_proxies_root"
|
||||
android:id="@+id/root"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_pofile_popup_remove"
|
||||
android:title="@string/clash_profile_remove" />
|
||||
</menu>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="@string/refresh" android:id="@+id/menuRefresh" />
|
||||
<item android:title="@string/mode">
|
||||
<menu>
|
||||
<group android:checkableBehavior="single">
|
||||
<item android:id="@+id/modeRule" android:title="@string/rule" />
|
||||
<item android:id="@+id/modeDirect" android:title="@string/direct" />
|
||||
<item android:id="@+id/modeGlobal" android:title="@string/global" />
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
<item android:title="@string/sort_group">
|
||||
<menu>
|
||||
<group android:checkableBehavior="single">
|
||||
<item android:id="@+id/groupDefault" android:title="@string/default_" />
|
||||
<item android:id="@+id/groupName" android:title="@string/name" />
|
||||
<item android:id="@+id/groupDelay" android:title="@string/delay" />
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
<item android:title="@string/sort_proxy">
|
||||
<menu>
|
||||
<group android:checkableBehavior="single">
|
||||
<item android:id="@+id/proxyDefault" android:title="@string/default_" />
|
||||
<item android:id="@+id/proxyName" android:title="@string/name" />
|
||||
<item android:id="@+id/proxyDelay" android:title="@string/delay" />
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
@@ -75,13 +75,15 @@
|
||||
<string name="edit_profile">Edit Profile</string>
|
||||
<string name="clash_start_failure">Clash Start Failure</string>
|
||||
<string name="refresh">Refresh</string>
|
||||
<string name="group_order">Group Order</string>
|
||||
<string name="proxy_order">Proxy Order</string>
|
||||
<string name="default_">Default</string>
|
||||
|
||||
<string name="delay">Delay</string>
|
||||
<string name="direct">Direct</string>
|
||||
<string name="global">Global</string>
|
||||
<string name="rule">Rule</string>
|
||||
<string name="mode">Mode</string>
|
||||
<string name="sort_group">Sort Group</string>
|
||||
<string name="sort_proxy">Sort Proxy</string>
|
||||
<string name="default_">Default</string>
|
||||
|
||||
<string name="start_url_provider_failure">Start URL Provider Failure</string>
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ dependencies {
|
||||
afterEvaluate {
|
||||
def ds = tasks.register("downloadMMDB", MMDBDowloadTask.class) {
|
||||
onlyIf {
|
||||
System.currentTimeMillis() - file("$buildDir/intermediates/dynamic_assets/Country.mmdb").lastModified() > 24 * 3600 * 1000L
|
||||
System.currentTimeMillis() - file("$buildDir/intermediates/dynamic_assets/Country.mmdb").lastModified() > 7 * 24 * 3600 * 1000L
|
||||
}
|
||||
|
||||
output = "$buildDir/intermediates/dynamic_assets/Country.mmdb"
|
||||
|
||||
@@ -25,3 +25,14 @@ func QueryGeneral() *TunnelGeneral {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func SetProxyMode(mode string) {
|
||||
switch mode {
|
||||
case "Direct":
|
||||
tunnel.Instance().SetMode(tunnel.Direct)
|
||||
case "Global":
|
||||
tunnel.Instance().SetMode(tunnel.Global)
|
||||
case "Rule":
|
||||
tunnel.Instance().SetMode(tunnel.Rule)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,10 @@ import (
|
||||
)
|
||||
|
||||
func LoadMMDB(data []byte) {
|
||||
mmdb.LoadFromBytes(data)
|
||||
dataClone := make([]byte, len(data))
|
||||
copy(dataClone, data)
|
||||
|
||||
mmdb.LoadFromBytes(dataClone)
|
||||
}
|
||||
|
||||
func SetHome(homeDir string) {
|
||||
|
||||
Submodule core/src/main/golang/clash updated: 8971f2c8a5...1de786635c.
@@ -1,3 +1,33 @@
|
||||
package main
|
||||
|
||||
func main() {}
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := os.Open("./Country.mmdb")
|
||||
if err != nil {
|
||||
println(err)
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
println(err)
|
||||
return
|
||||
}
|
||||
|
||||
mmdb.LoadFromBytes(buf)
|
||||
|
||||
c, err := mmdb.Instance().Country(net.ParseIP("114.114.114.114"))
|
||||
if err != nil {
|
||||
println(err)
|
||||
return
|
||||
}
|
||||
|
||||
println(c.Country.IsoCode)
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/inbound"
|
||||
"github.com/Dreamacro/clash/component/socks5"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
@@ -78,15 +77,3 @@ func SaveAndCheck(data []byte, output, baseDir string) error {
|
||||
|
||||
return ioutil.WriteFile(output, data, defaultFileMode)
|
||||
}
|
||||
|
||||
func parseConfig(data []byte, baseDir string) (*config.Config, error) {
|
||||
raw, err := config.UnmarshalRawConfig(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw.ExternalUI = ""
|
||||
raw.ExternalController = ""
|
||||
|
||||
return config.ParseRawConfig(raw, baseDir)
|
||||
}
|
||||
@@ -1,27 +1,29 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
"github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
"github.com/Dreamacro/clash/hub/executor"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/kr328/cfa/tun"
|
||||
)
|
||||
|
||||
const tunAddress = "172.31.255.253/30"
|
||||
|
||||
const defaultConfig = `
|
||||
log: debug
|
||||
mode: Direct
|
||||
Proxy:
|
||||
- name: "ss1"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
# udp: true
|
||||
- name: "broadcast"
|
||||
type: socks5
|
||||
server: 255.255.255.255
|
||||
port: 1080
|
||||
|
||||
Proxy Group:
|
||||
- name: "select"
|
||||
@@ -34,11 +36,15 @@ Rule:
|
||||
|
||||
// LoadDefault - load default configure
|
||||
func LoadDefault() {
|
||||
defaultC, _ := config.Parse([]byte(defaultConfig))
|
||||
|
||||
tun.ResetDnsRedirect()
|
||||
defaultC, err := parseConfig([]byte(defaultConfig), constant.Path.HomeDir())
|
||||
if err != nil {
|
||||
log.Warnln("Load Default Failure " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
executor.ApplyConfig(defaultC, true)
|
||||
|
||||
tun.ResetDnsRedirect()
|
||||
}
|
||||
|
||||
// LoadFromFile - load file
|
||||
@@ -103,3 +109,16 @@ func LoadFromFile(path, baseDir string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseConfig(data []byte, baseDir string) (*config.Config, error) {
|
||||
raw, err := config.UnmarshalRawConfig(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw.ExternalUI = ""
|
||||
raw.ExternalController = ""
|
||||
raw.Rule = append([]string{fmt.Sprintf("IP-CIDR,%s,REJECT", tunAddress)}, raw.Rule...)
|
||||
|
||||
return config.ParseRawConfig(raw, baseDir)
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import com.github.kr328.clash.core.model.Traffic
|
||||
import com.github.kr328.clash.core.transact.DoneCallbackImpl
|
||||
import com.github.kr328.clash.core.transact.ProxyCollectionImpl
|
||||
import com.github.kr328.clash.core.transact.ProxyGroupCollectionImpl
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
@@ -30,6 +31,8 @@ object Clash {
|
||||
Bridge.loadMMDB(bytes)
|
||||
Bridge.setHome(context.cacheDir.absolutePath)
|
||||
Bridge.reset()
|
||||
|
||||
Log.d("MMDB loaded ${bytes.size}")
|
||||
}
|
||||
|
||||
fun start() {
|
||||
@@ -97,6 +100,10 @@ object Clash {
|
||||
}
|
||||
}
|
||||
|
||||
fun setProxyMode(mode: String) {
|
||||
Bridge.setProxyMode(mode)
|
||||
}
|
||||
|
||||
fun queryGeneral(): General {
|
||||
val t = Bridge.queryGeneral()
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ interface IClashManager {
|
||||
// Control
|
||||
boolean setSelectProxy(String proxy, String selected);
|
||||
void startHealthCheck(String group, IStreamCallback callback);
|
||||
void setProxyMode(String mode);
|
||||
|
||||
// Query
|
||||
ProxyGroupList queryAllProxies();
|
||||
|
||||
@@ -4,14 +4,22 @@ import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.ProxyGroup
|
||||
import com.github.kr328.clash.core.model.ProxyGroupList
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
import com.github.kr328.clash.service.data.ClashProfileProxyEntity
|
||||
import com.github.kr328.clash.service.ipc.IStreamCallback
|
||||
import com.github.kr328.clash.service.ipc.ParcelableContainer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ClashManager(context: Context) : IClashManager.Stub() {
|
||||
class ClashManager(context: Context, parent: CoroutineScope) :
|
||||
IClashManager.Stub(), CoroutineScope by parent {
|
||||
private val settings = context.getSharedPreferences("service", Context.MODE_PRIVATE)
|
||||
private val database = ClashDatabase.getInstance(context)
|
||||
|
||||
override fun setProxyMode(mode: String?) {
|
||||
Clash.setProxyMode(requireNotNull(mode))
|
||||
}
|
||||
|
||||
override fun queryAllProxies(): ProxyGroupList {
|
||||
return ProxyGroupList(Clash.queryProxyGroups())
|
||||
@@ -24,11 +32,18 @@ class ClashManager(context: Context) : IClashManager.Stub() {
|
||||
override fun setSelectProxy(proxy: String?, selected: String?): Boolean {
|
||||
require(proxy != null && selected != null)
|
||||
|
||||
launch {
|
||||
val current = database.openClashProfileDao()
|
||||
.queryActiveProfile() ?: return@launch
|
||||
database.openClashProfileProxyDao()
|
||||
.setSelectedForProfile(ClashProfileProxyEntity(current.id, proxy, selected))
|
||||
}
|
||||
|
||||
return Clash.setSelectedProxy(proxy, selected)
|
||||
}
|
||||
|
||||
override fun putSetting(key: String?, value: String?): Boolean {
|
||||
settings.edit {
|
||||
settings.edit(commit = false) {
|
||||
putString(key, value)
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
|
||||
class ClashManagerService : Service() {
|
||||
class ClashManagerService : BaseService() {
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return ClashManager(this)
|
||||
return ClashManager(this, this)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class ClashService : BaseService() {
|
||||
private lateinit var notification: ClashNotification
|
||||
private var stopReason: String? = null
|
||||
private val reloadChannel = Channel<Unit>(Channel.CONFLATED)
|
||||
private val settings: Settings by lazy { Settings(ClashManager(this)) }
|
||||
private val settings: Settings by lazy { Settings(ClashManager(this, this)) }
|
||||
private val profileObserver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.`package` != packageName)
|
||||
@@ -93,8 +93,8 @@ class ClashService : BaseService() {
|
||||
}
|
||||
|
||||
private suspend fun reloadProfile() = withContext(Dispatchers.IO) {
|
||||
val active = ClashDatabase.getInstance(service).openClashProfileDao().queryActiveProfile()
|
||||
?: return@withContext stopSelf("Empty active profile")
|
||||
val active = ClashDatabase.getInstance(service).openClashProfileDao()
|
||||
.queryActiveProfile() ?: return@withContext stopSelf("Empty active profile")
|
||||
|
||||
try {
|
||||
Clash.loadProfile(
|
||||
@@ -102,6 +102,11 @@ class ClashService : BaseService() {
|
||||
resolveBase(active.id)
|
||||
).await()
|
||||
|
||||
ClashDatabase.getInstance(service).openClashProfileProxyDao()
|
||||
.querySelectedForProfile(active.id).forEach {
|
||||
Clash.setSelectedProxy(it.proxy, it.selected)
|
||||
}
|
||||
|
||||
notification.setProfile(active.name)
|
||||
} catch (e: Exception) {
|
||||
stopSelf("Load profile failure")
|
||||
|
||||
@@ -50,7 +50,9 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
settings = Settings(ClashManager(this))
|
||||
Clash.initialize(this)
|
||||
|
||||
settings = Settings(ClashManager(this, this))
|
||||
|
||||
defaultNetworkChannel = DefaultNetworkChannel(this, this)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ interface ClashProfileProxyDao {
|
||||
suspend fun setSelectedForProfile(item: ClashProfileProxyEntity)
|
||||
|
||||
@Query("SELECT * FROM profile_select_proxies WHERE profile_id = :id")
|
||||
suspend fun querySelectedForProfile(id: Int): List<ClashProfileProxyEntity>
|
||||
suspend fun querySelectedForProfile(id: Long): List<ClashProfileProxyEntity>
|
||||
|
||||
@Query("DELETE FROM profile_select_proxies WHERE profile_id = :id AND proxy in (:selected)")
|
||||
suspend fun removeSelectedForProfile(id: Int, selected: List<String>)
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import androidx.room.*
|
||||
)]
|
||||
)
|
||||
data class ClashProfileProxyEntity(
|
||||
@ColumnInfo(name = "profile_id") val profileId: Int,
|
||||
@ColumnInfo(name = "profile_id") val profileId: Long,
|
||||
@ColumnInfo(name = "proxy") val proxy: String,
|
||||
@ColumnInfo(name = "selected") val selected: String,
|
||||
@ColumnInfo(name = "id") @PrimaryKey(autoGenerate = true) val id: Int = 0
|
||||
|
||||
Reference in new issue
Block a user