一键连

1.更换内核
2.新增节点组选择
This commit is contained in:
level 2025-12-10 11:42:53 +08:00
parent 3d9c1b4585
commit 2feb4d7ead
11 changed files with 702 additions and 128 deletions

View File

@ -140,6 +140,10 @@
android:process=":background" android:process=":background"
android:screenOrientation="portrait" android:screenOrientation="portrait"
/> />
<activity
android:name=".act.NodeGroupActivity"
android:process=":background"
android:screenOrientation="portrait" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"

View File

@ -1,7 +1,10 @@
package com.yingmao.clash.act package com.yingmao.clash.act
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
@ -9,12 +12,15 @@ import android.widget.TextView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView import com.afollestad.materialdialogs.customview.getCustomView
import com.baidu.mobads.action.ActionType import com.baidu.mobads.action.ActionType
import com.baidu.mobads.action.BaiduAction import com.baidu.mobads.action.BaiduAction
import com.cncat.vpn.common.constants.Intents
import com.cncat.vpn.common.log.Log import com.cncat.vpn.common.log.Log
import com.cncat.vpn.core.model.LogMessage import com.cncat.vpn.core.model.LogMessage
import com.cncat.vpn.service.ClashManager import com.cncat.vpn.service.ClashManager
@ -27,6 +33,7 @@ import com.cncat.vpn.service.remote.wrap
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.yingmao.clash.BuildConfig import com.yingmao.clash.BuildConfig
@ -117,6 +124,9 @@ class MainActivity : BaseActivity() {
var newMainActGlo: Boolean = false var newMainActGlo: Boolean = false
var newUuid: String? = null var newUuid: String? = null
var clashBinder: IClashManager? = null var clashBinder: IClashManager? = null
private var mainBroadcastReceiver: BroadcastReceiver? = null
val trafficStatisticsData = MutableLiveData<String>()
suspend fun setSub2(url: String) { suspend fun setSub2(url: String) {
profileBinder?.let { pm -> profileBinder?.let { pm ->
pm.queryAll().forEach { itemPro -> pm.queryAll().forEach { itemPro ->
@ -191,8 +201,51 @@ class MainActivity : BaseActivity() {
startLog() startLog()
firstEntry() firstEntry()
mainAtyVM.getExpensesRecord(mPageInfo) mainAtyVM.getExpensesRecord(mPageInfo)
val intentFilter = IntentFilter(Intents.ACTION_NODE_LIST)
mainBroadcastReceiver = NodeListBR()
if (Build.VERSION.SDK_INT >= 34 && applicationInfo.targetSdkVersion >= 34) {
registerReceiver(
mainBroadcastReceiver,
intentFilter,
RECEIVER_EXPORTED
)
} else {
registerReceiver(
mainBroadcastReceiver,
intentFilter
)
}
} }
inner class NodeListBR : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intents.ACTION_NODE_LIST -> {
Log.d("ACTION_NODE_LIST ACTION_NODE_LIST")
val userTraffic=serStore.userTraffic
Log.d("userTraffic::::${userTraffic}")
userTrafficMethod(userTraffic)
if (TextUtils.isEmpty(userTraffic)) {
return
}
}
else -> {
}
}
}
}
fun userTrafficMethod(userTraffic: String) {
val jo = JsonParser.parseString(userTraffic).asJsonObject
val t = jo.get("total").asLong
val d = jo.get("download").asLong
val u = jo.get("upload").asLong
val trafficUsedInGB = String.format("%.2f", (d + u) / 1024.0 / 1024.0 / 1024.0)
val trafficTolTelInGB = String.format("%.2f", t / 1024.0 / 1024.0 / 1024.0)
trafficStatisticsData.value = "流量统计:${trafficUsedInGB}GB / ${trafficTolTelInGB}GB"
}
private fun initData() { private fun initData() {
clashBinder = MainApplication.getApp()?.let { ClashManager(it).wrap() as IClashManager } clashBinder = MainApplication.getApp()?.let { ClashManager(it).wrap() as IClashManager }
profileBinder = profileBinder =
@ -268,24 +321,24 @@ class MainActivity : BaseActivity() {
if (data.code != "1000") { if (data.code != "1000") {
RxToast.error(resources.getString(R.string.NodeAcquisitionFailed) + "${data.msg}") RxToast.error(resources.getString(R.string.NodeAcquisitionFailed) + "${data.msg}")
} }
data.data.let { url -> data.data.let { netSubUrl ->
android.util.Log.e("Data", "机场URL=${url}") if (netSubUrl.isNotEmpty()) {
if (url.isNotEmpty()) { val proSubUrl =
//todo 判断订阅地址是否修改过,未修改就用接口返回的地址反之使用本地修改的地址
var pro_sub_url =
MMKV.defaultMMKV().decodeString(BZYConstants.PROFILE_SUB_URL, "") MMKV.defaultMMKV().decodeString(BZYConstants.PROFILE_SUB_URL, "")
// Log.d("pro_sub_url==>${pro_sub_url}") subUrl = if (!TextUtils.isEmpty(proSubUrl)) {
if (!TextUtils.isEmpty(pro_sub_url)) { if (proSubUrl == netSubUrl) {
if (pro_sub_url == url) { netSubUrl
subUrl = url
} else { } else {
subUrl = pro_sub_url proSubUrl
} }
} else { } else {
subUrl = url netSubUrl
}
lifecycleScope.launch(Dispatchers.IO) {
subUrl?.let {
Log.i("netSubUrl:${netSubUrl}::保存机场URL=${proSubUrl}::subUrl:${subUrl}")
setSub2(it)
} }
launch(Dispatchers.IO) {
subUrl?.let { setSub2(it) }
} }
} }
} }

View File

@ -0,0 +1,395 @@
package com.yingmao.clash.act
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.cncat.vpn.common.constants.Intents
import com.cncat.vpn.common.log.Log
import com.cncat.vpn.service.model.Profile
import com.cncat.vpn.service.remote.IRemoteService
import com.google.gson.Gson
import com.google.gson.JsonParser
import com.tencent.mmkv.MMKV
import com.yingmao.clash.BuildConfig
import com.yingmao.clash.R
import com.yingmao.clash.act.MainActivity.Companion.profileBinder
import com.yingmao.clash.act.MainActivity.Companion.setSub2
import com.yingmao.clash.act.MainActivity.Companion.subUrl
import com.yingmao.clash.app.MainApplication
import com.yingmao.clash.base.BaseActivity
import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.conf.Conf
import com.yingmao.clash.entity.NodeGroupItem
import com.yingmao.clash.fragment.home.HomeFragment
import com.yingmao.clash.fragment.home.HomeFragment.Companion.CHECK_NODE_REQUEST_CODE
import com.yingmao.clash.remote.Resource
import com.yingmao.clash.view.RxToast
import com.yingmao.clash.view.adapter.NodeGroupAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.UUID
import kotlin.collections.forEach
import kotlin.collections.withIndex
import kotlin.jvm.java
import kotlin.let
import kotlin.ranges.until
import kotlin.text.isNotEmpty
import kotlin.text.replace
class NodeGroupActivity : BaseActivity(){
private var currentNode: MutableList<NodeGroupItem> = mutableListOf()
private lateinit var swipeLayout: SwipeRefreshLayout
private lateinit var changeNodeVM: ChangeNodeVM
private lateinit var nodeListRv: RecyclerView
private lateinit var aiBack: AppCompatImageView
private var broadcastReceiver: BroadcastReceiver? = null
private var cacheNodeStr: String? = ""
private lateinit var userTrafficTv: TextView
private var uuid: UUID? = null
var nodeListAdapter: NodeGroupAdapter = NodeGroupAdapter()
private var timerJob: Job? = null
val remote = Resource<IRemoteService>()
var getNodeTimeOutNum = 5
var isRunning = false
var nowSubUrl: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_change_node)
changeNodeVM = ViewModelProvider(this)[ChangeNodeVM::class.java]
initView()
initData()
}
override fun onBackPressed() {
intent.putExtra(BZYConstants.EXTRA_KEY_IS_RUNING, isRunning)
setResult(CHECK_NODE_REQUEST_CODE, intent)
super.onBackPressed()
}
private fun initData() {
subUrl?.let {
nowSubUrl = it
}
cacheNodeStr = MMKV.defaultMMKV().decodeString(BZYConstants.NODE_All_ITEM_NAME_KEY, "")
val intentFilter = IntentFilter(Intents.ACTION_NODE_LIST)
broadcastReceiver = NodeListBR()
if (Build.VERSION.SDK_INT >= 34 && applicationInfo.targetSdkVersion >= 34) {
registerReceiver(
broadcastReceiver,
intentFilter,
RECEIVER_EXPORTED
)
} else {
registerReceiver(
broadcastReceiver,
intentFilter
)
}
doProfile()
}
@SuppressLint("SetTextI18n", "NotifyDataSetChanged")
private fun initView() {
aiBack = findViewById(R.id.aiBack)
swipeLayout = findViewById(R.id.swipeLayout)
nodeListRv = findViewById(R.id.rv_node_list)
userTrafficTv= findViewById(R.id.node_user_traffic)
aiBack.setOnClickListener {
onBackPressed()
}
swipeLayout.setColorSchemeColors(Color.rgb(47, 223, 189))
swipeLayout.setOnRefreshListener {
swipeLayout.isRefreshing = true
updateProfile(false)
swipeLayout.isRefreshing = false
}
nodeListRv.layoutManager = LinearLayoutManager(this)
nodeListRv.adapter=nodeListAdapter
nodeListAdapter.setListener(object : NodeGroupAdapter.ItemClickListener{
override fun onItemClick(nodeStr: String, isPosition: Int) {
MMKV.defaultMMKV().encode(BZYConstants.NODE_SELECTED_NAME_KEY, nodeStr)
val intent = Intent()
intent.putExtra(BZYConstants.MMKV_KEY_SELECT_NODE_NAME, nodeStr)
intent.putExtra(BZYConstants.EXTRA_KEY_IS_RUNING, isRunning)
setResult(CHECK_NODE_REQUEST_CODE, intent)
finish()
}
})
// if (!TextUtils.isEmpty(serStore.userTraffic)) {
// userTrafficTv.visibility=View.VISIBLE
// var jo = JsonParser.parseString(serStore.userTraffic).asJsonObject
// var t = jo.get("total")
// var d = jo.get("download").asLong
// var u = jo.get("upload").asLong
// val trafficUsedInGB = String.format("%.2f", (d + u) / 1024.0 / 1024.0 / 1024.0)
// userTrafficTv.text="已用流量:${trafficUsedInGB}GB"
// }
}
private fun getNodesInfo(json: String): MutableList<NodeGroupItem> {
val mList: MutableList<NodeGroupItem> = mutableListOf()
if (TextUtils.isEmpty(json)) {
return mList
}
val gson = Gson()
val jsonArray = JsonParser.parseString(json).asJsonArray
for (i in 0 until jsonArray.size()) {
val bean = gson.fromJson(jsonArray.get(i).asString, NodeGroupItem::class.java)
mList.add(bean)
}
return mList
}
inner class NodeListBR : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intents.ACTION_NODE_LIST -> {
Log.d("ACTION_NODE_LIST ACTION_NODE_LIST")
hideLoading()
timerJob?.cancel()
val json = serStore.nodes
val trsss=serStore.userTraffic
Log.d("AAAAAAAAAAAAAAAAAAAA${trsss}")
if (TextUtils.isEmpty(json)) {
return
}
MMKV.defaultMMKV().encode(BZYConstants.PROFILE_SUB_URL, nowSubUrl)
//缓存节点
MMKV.defaultMMKV().encode(BZYConstants.NODE_All_ITEM_NAME_KEY, json)
currentNode.clear()
currentNode = getNodesInfo(json)
setNewData(currentNode)
swipeLayout.isRefreshing = false
}
else -> {
}
}
}
}
override fun onDestroy() {
super.onDestroy()
broadcastReceiver?.let {
unregisterReceiver(it)
}
}
override fun onResume() {
super.onResume()
isRunning = intent.getBooleanExtra(BZYConstants.EXTRA_KEY_IS_RUNING, false)
}
private fun updateProfile(isDelete: Boolean) {
try {
launch(Dispatchers.IO) {
profileBinder?.let { profileManager ->
var mProfile: Profile? = null
Log.d("queryAll().size ==>${profileManager.queryAll().size}")
profileManager.queryAll().forEach { it_pf ->
val confName =
Conf.getClashConfName(this@NodeGroupActivity.applicationContext)
Log.d(" it_pf.name ==>${it_pf.name}, confName==>${confName}")
if (confName == it_pf.name) {
mProfile = it_pf
}
}
if (mProfile == null) {
//为空的情况下选择第一个
profileManager.queryAll().forEach { profile ->
mProfile = profile
return@forEach
}
}
mProfile?.let { profile ->
uuid = profile.uuid
if (isDelete) {
MMKV.defaultMMKV()
.removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY)
} else {
profileManager.setActive(profile)
if (!TextUtils.isEmpty(profile.source)) {
if (profile.source != nowSubUrl) {
profileManager.patch(
profile.uuid,
profile.name,
nowSubUrl,
1800000L
)
profileManager.commit(profile.uuid) { status ->
Log.d("updateProfile updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress)
}
}
} else {
profileManager.patch(
profile.uuid,
profile.name,
nowSubUrl,
1800000L
)
profileManager.commit(profile.uuid) { status ->
Log.d("updateProfile new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress)
}
}
profileManager.update(profile.uuid)
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
fun doProfile() {
showLoading("正在获取...", this@NodeGroupActivity)
startTimer()
launch(Dispatchers.IO) {
setSub2(nowSubUrl)
}
}
private fun changeSubUrl(originalUrl: String, newUrl: String) {
if (originalUrl.isNotEmpty() && newUrl.isNotEmpty()) {
nowSubUrl = originalUrl.replace(Regex("https://[^/]+/"), "$newUrl/")
subUrl = nowSubUrl
MMKV.defaultMMKV().encode(BZYConstants.PROFILE_SUB_URL, nowSubUrl)
}
}
private fun setNewData(nodes: MutableList<NodeGroupItem>?) {
runOnUiThread {
if (nodes != null) {
val proxies = nodes[0].proxies
nodeListAdapter.setDataMethod(proxies,this@NodeGroupActivity)
val selectedName =
MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "")
for ((index, node) in proxies.withIndex()) {
if (node== selectedName) {
nodeListAdapter.selectedPosition = index
}
}
nodeListAdapter.notifyDataSetChanged()
}
}
}
private fun startTimer() {
getNodeTimeOutNum = 15
timerJob = launch {
repeat(16) {
Log.d("GetNodeTimeOutNum==>$getNodeTimeOutNum")
if (getNodeTimeOutNum == 0) {
hideLoading()
RxToast.error("获取节点失败...正在切换其他地址")
if (setNewData()) {
timerJob?.cancel()
MMKV.defaultMMKV()
.removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY)
saveSubUrl()
MainApplication.restartApp(this@NodeGroupActivity, object :
MainApplication.OnRestartButtonListener {
override fun enterButton() {
}
override fun cancelButton() {
doProfile()
}
})
} else {
timerJob?.cancel()
RxToast.error("获取全部线路失败...请切换网络重试")
}
}
getNodeTimeOutNum--
delay(1000)
}
}
}
private fun setNewData(): Boolean {
val mmkv = MMKV.defaultMMKV()
val originalUrl =
mmkv.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, BuildConfig.service_url)
var newUrlString = originalUrl
var isChange = false
val routerHost = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST,
kotlin.collections.HashSet()
)
val switchFinSet = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_Switch_Finsh_Set,
kotlin.collections.HashSet()
)
routerHost?.let {
if (it.isEmpty()) {
android.util.Log.d(Conf.TAG, "routerHost isEmpty")
return false
}
Log.d("switchFinshSet.size==>${switchFinSet?.size!!} , routerHost.size==>${routerHost.size}")
//次数已满
if (switchFinSet.size >= routerHost.size) {
return false
}
for (url in it) {
if (url == originalUrl) {
switchFinSet.add(url)
mmkv.encode(BZYConstants.MMKV_KEY_Switch_Finsh_Set, switchFinSet)
continue
}
if (switchFinSet.contains(url)) {
continue
} else {
isChange = true
android.util.Log.e("Retry", "切换到新url:$url")
newUrlString = url
switchFinSet.add(newUrlString)
mmkv.encode(BZYConstants.MMKV_KEY_Switch_Finsh_Set, switchFinSet)
break
}
}
mmkv.encode(BZYConstants.MMKV_KEY_CURRENT_HOST, newUrlString)
}
return isChange
}
private fun saveSubUrl() {
val currentHost = MMKV.defaultMMKV()
.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "")
val oldUrl = nowSubUrl
currentHost?.let { changeSubUrl(oldUrl, it) }
}
}

View File

@ -15,3 +15,10 @@ data class ClashNodebBean(
var delay: Int, var delay: Int,
val udp: Boolean val udp: Boolean
) : Parcelable,Serializable ) : Parcelable,Serializable
@Parcelize
data class NodeGroupItem(
val name: String,
val type: String,
val proxies: MutableList<String>
) : Parcelable,Serializable

View File

@ -32,6 +32,10 @@ import com.cncat.vpn.common.constants.Intents
import com.cncat.vpn.common.log.Log import com.cncat.vpn.common.log.Log
import com.cncat.vpn.core.Clash import com.cncat.vpn.core.Clash
import com.cncat.vpn.core.model.TunnelState import com.cncat.vpn.core.model.TunnelState
import com.cncat.vpn.service.model.Profile
import com.cncat.vpn.service.store.ServiceStore
import com.google.gson.Gson
import com.google.gson.JsonParser
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.yingmao.clash.R import com.yingmao.clash.R
@ -43,10 +47,14 @@ import com.yingmao.clash.act.MainActivity.Companion.clashBinder
import com.yingmao.clash.act.MainActivity.Companion.isSubbed import com.yingmao.clash.act.MainActivity.Companion.isSubbed
import com.yingmao.clash.act.MainActivity.Companion.newMainActGlo import com.yingmao.clash.act.MainActivity.Companion.newMainActGlo
import com.yingmao.clash.act.MainActivity.Companion.newUuid import com.yingmao.clash.act.MainActivity.Companion.newUuid
import com.yingmao.clash.act.MainActivity.Companion.profileBinder
import com.yingmao.clash.act.MainActivity.Companion.subUrl
import com.yingmao.clash.act.NodeGroupActivity
import com.yingmao.clash.conf.BZYConstants import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.conf.BZYConstants.Companion.ScreenFlag import com.yingmao.clash.conf.BZYConstants.Companion.ScreenFlag
import com.yingmao.clash.conf.Conf import com.yingmao.clash.conf.Conf
import com.yingmao.clash.entity.ClashNodebBean import com.yingmao.clash.entity.ClashNodebBean
import com.yingmao.clash.entity.NodeGroupItem
import com.yingmao.clash.fragment.AccelerateViewModel import com.yingmao.clash.fragment.AccelerateViewModel
import com.yingmao.clash.fragment.BaseFragment import com.yingmao.clash.fragment.BaseFragment
import com.yingmao.clash.listener.AsyncCallBackListener import com.yingmao.clash.listener.AsyncCallBackListener
@ -66,6 +74,7 @@ import com.yingmao.clash.view.dialog.RxDialogSureCancel
import com.yingmao.clash.webview.CustomerServiceWebView import com.yingmao.clash.webview.CustomerServiceWebView
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -74,6 +83,7 @@ import okhttp3.Response
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.get
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@ -118,6 +128,8 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
private lateinit var tvNodeName: TextView private lateinit var tvNodeName: TextView
private lateinit var clLinks: ConstraintLayout private lateinit var clLinks: ConstraintLayout
val serStore by lazy { ServiceStore(requireActivity()) }
private var currentNode: MutableList<NodeGroupItem> = mutableListOf()
private val mPageInfo = ExpensesRecordActivity.PageInfo() private val mPageInfo = ExpensesRecordActivity.PageInfo()
private var reConnection = false private var reConnection = false
@ -221,7 +233,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
RxToast.info(resources.getString(R.string.AccountExpired)) RxToast.info(resources.getString(R.string.AccountExpired))
return@setOnClickListener return@setOnClickListener
} }
val intent = Intent(CommonUtils.getApp(), ChangeNodeActivity::class.java) val intent = Intent(CommonUtils.getApp(), NodeGroupActivity::class.java)
if (!TextUtils.isEmpty(newUuid)) { if (!TextUtils.isEmpty(newUuid)) {
intent.putExtra(BZYConstants.MMKV_KEY_CURRENT_NODE, newUuid) intent.putExtra(BZYConstants.MMKV_KEY_CURRENT_NODE, newUuid)
intent.putExtra(BZYConstants.EXTRA_KEY_IS_RUNING, clashRunning) intent.putExtra(BZYConstants.EXTRA_KEY_IS_RUNING, clashRunning)
@ -361,7 +373,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
private fun startClash(isStart: Boolean) { private fun startClash(isStart: Boolean) {
launch { launch {
MainActivity.profileBinder?.let { pm -> profileBinder?.let { pm ->
val active = pm.queryActive() val active = pm.queryActive()
if (active == null) { if (active == null) {
if (isStart) { if (isStart) {
@ -390,7 +402,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
} }
delay(500) delay(500)
} }
refreshNode()
val vpnRequest = context?.startClashService() val vpnRequest = context?.startClashService()
try { try {
startUTimer() startUTimer()
@ -415,7 +427,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
tvClick.text=resources.getString(R.string.Accelerating) tvClick.text=resources.getString(R.string.Accelerating)
ivAccelerate.setImageResource(R.mipmap.yjl_home_clink_select_bg) ivAccelerate.setImageResource(R.mipmap.yjl_home_clink_select_bg)
hideLoading() hideLoading()
checkGlo() // checkGlo()
} }
} }
@ -697,14 +709,15 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "") MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "")
Log.d("getNodes selected_name==>${selectedName}") Log.d("getNodes selected_name==>${selectedName}")
if (TextUtils.isEmpty(selectedName)) { if (TextUtils.isEmpty(selectedName)) {
MainActivity.subUrl?.let { subUrl?.let {
accelerateViewModel.getNodeOther(it, object : AsyncCallBackListener { val json = serStore.nodes
override fun suc(obj: Any?) { Log.d("getNodes json==>${json}")
proList = obj as MutableList<ClashNodebBean> currentNode=getNodesInfo(json)
proList?.let { proListItem -> Log.d("getNodes currentNode==>${currentNode} dataname:${currentNode[0].proxies[0]}")
val nodeName = proListItem[0].name val baseNodeName=currentNode[0].proxies[0]
if (!TextUtils.isEmpty(nodeName)) {
launch(Dispatchers.IO) { if (!TextUtils.isEmpty(baseNodeName)) {
GlobalScope.launch(Dispatchers.IO) {
clashBinder?.let { cm -> clashBinder?.let { cm ->
val o = cm.queryOverride(Clash.OverrideSlot.Session) val o = cm.queryOverride(Clash.OverrideSlot.Session)
@ -729,22 +742,11 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
delay(1000) delay(1000)
} }
cm.patchSelector("GLOBAL", nodeName) cm.patchSelector("GLOBAL", baseNodeName)
} }
} }
} }
} }
}
override fun faild(response: Response) {
}
override fun err(msg: String, exception: Exception?) {
}
})
}
} else { } else {
clashBinder?.let { cm -> clashBinder?.let { cm ->
val o = cm.queryOverride(Clash.OverrideSlot.Session) val o = cm.queryOverride(Clash.OverrideSlot.Session)
@ -903,6 +905,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
tvIntelligentMode.setTextColor("#FF006AFB".toColorInt()) tvIntelligentMode.setTextColor("#FF006AFB".toColorInt())
tvGlobalMode.setTextColor("#FF000000".toColorInt()) tvGlobalMode.setTextColor("#FF000000".toColorInt())
sendMode(false) sendMode(false)
checkGlo()
} }
ProxyMode.GLOBAL -> { ProxyMode.GLOBAL -> {
@ -913,6 +916,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
tvIntelligentMode.setTextColor("#FF000000".toColorInt()) tvIntelligentMode.setTextColor("#FF000000".toColorInt())
tvGlobalMode.setTextColor("#FF006AFB".toColorInt()) tvGlobalMode.setTextColor("#FF006AFB".toColorInt())
sendMode(true) sendMode(true)
checkGlo()
} }
} }
} }
@ -1005,5 +1009,46 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
} }
} }
fun refreshNode(){
try {
launch(Dispatchers.IO) {
profileBinder?.let { profileManager ->
var mProfile: Profile? = null
profileManager.queryAll().forEach { it_pf ->
val confName =
Conf.getClashConfName(requireActivity())
Log.d("refreshNode::==>${it_pf.name}, confName==>${confName}")
if (confName == it_pf.name) {
mProfile = it_pf
}
}
if (mProfile == null) {
profileManager.queryAll().forEach { profile ->
mProfile = profile
return@forEach
}
}
mProfile?.let { profile ->
profileManager.update(profile.uuid)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getNodesInfo(json: String): MutableList<NodeGroupItem> {
val mList: MutableList<NodeGroupItem> = mutableListOf()
if (TextUtils.isEmpty(json)) {
return mList
}
val gson = Gson()
val jsonArray = JsonParser.parseString(json).asJsonArray
for (i in 0 until jsonArray.size()) {
val bean = gson.fromJson(jsonArray.get(i).asString, NodeGroupItem::class.java)
mList.add(bean)
}
return mList
}
} }

View File

@ -45,6 +45,7 @@ import com.yingmao.clash.act.MainActivity
import com.yingmao.clash.act.MainActivity.Companion.profileBinder import com.yingmao.clash.act.MainActivity.Companion.profileBinder
import com.yingmao.clash.act.MainActivity.Companion.setSub2 import com.yingmao.clash.act.MainActivity.Companion.setSub2
import com.yingmao.clash.act.MainActivity.Companion.subUrl import com.yingmao.clash.act.MainActivity.Companion.subUrl
import com.yingmao.clash.act.MainActivity.Companion.trafficStatisticsData
import com.yingmao.clash.act.SettingActivity import com.yingmao.clash.act.SettingActivity
import com.yingmao.clash.act.ShareActivity import com.yingmao.clash.act.ShareActivity
import com.yingmao.clash.act.SplashHelper import com.yingmao.clash.act.SplashHelper
@ -107,6 +108,8 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
private var baseLoad: RxDialogShapeLoading? = null private var baseLoad: RxDialogShapeLoading? = null
private lateinit var mainAtyVM: MainAtyVM private lateinit var mainAtyVM: MainAtyVM
private lateinit var tvUseNet: TextView
private var settingPageList = mutableListOf<SettingPageBean>() private var settingPageList = mutableListOf<SettingPageBean>()
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
override fun init( override fun init(
@ -386,6 +389,15 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
rvSettingTab=view.findViewById(R.id.rvSettingTab) rvSettingTab=view.findViewById(R.id.rvSettingTab)
tvUseNet=view.findViewById(R.id.tvUseNet)
trafficStatisticsData.observe(requireActivity()){
it.let {
tvUseNet.text=it
}
}
clVipCard.setOnClickListener { clVipCard.setOnClickListener {
goNewRecharge() goNewRecharge()
} }

View File

@ -0,0 +1,111 @@
package com.yingmao.clash.view.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.cncat.vpn.common.log.Log
import com.yingmao.clash.R
import kotlin.collections.mutableListOf
import kotlin.text.contains
class NodeGroupAdapter : RecyclerView.Adapter<NodeGroupAdapter.NodeGroupAdapterViewHolder>() {
private var nodeGroupList = mutableListOf<String>()
//设置回调监听
private var listener: ItemClickListener? = null
private var context: Context? = null
var selectedPosition: Int =0
//设置监听器
fun setListener(listener: ItemClickListener?) {
this.listener = listener
}
@SuppressLint("NotifyDataSetChanged")
fun setDataMethod(infoList: MutableList<String>, context: Context) {
nodeGroupList=infoList
this.context=context
notifyDataSetChanged()
}
inner class NodeGroupAdapterViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val nodeitemItem = itemView.findViewById<ConstraintLayout>(R.id.rl_node_list_item)
var nodeName = itemView.findViewById<TextView>(R.id.node_list_item_tv_node_name)
var chekImg = itemView.findViewById<AppCompatImageView>(R.id.mrb_node_checked)
var countryImg = itemView.findViewById<AppCompatImageView>(R.id.node_list_item_iv_country)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = NodeGroupAdapterViewHolder(
LayoutInflater.from(context)
.inflate(R.layout.node_list_item_layout, parent, false)
)
override fun getItemCount() = nodeGroupList.size
override fun onBindViewHolder(holder: NodeGroupAdapterViewHolder, position: Int) {
holder.nodeitemItem.setOnClickListener {
listener?.onItemClick(nodeGroupList[position],position)
}
holder.nodeName.text = nodeGroupList[position]
holder.chekImg.isSelected = holder.layoutPosition == selectedPosition
val nodeGroupName = nodeGroupList[position]
if(nodeGroupName.contains("中国")){
holder.countryImg.setImageResource(R.drawable.flag_china)
}else if(nodeGroupName.contains("香港")){
holder.countryImg.setImageResource(R.drawable.flag_china)
}else if(nodeGroupName.contains("日本")){
holder.countryImg.setImageResource(R.drawable.flag_japan)
}else if(nodeGroupName.contains("韩国")){
holder.countryImg.setImageResource(R.drawable.flag_korea)
}else if(nodeGroupName.contains("美国")){
holder.countryImg.setImageResource(R.drawable.flag_usa)
}else if(nodeGroupName.contains("德国")){
holder.countryImg.setImageResource(R.drawable.flag_germany)
}else if(nodeGroupName.contains("法国")){
holder.countryImg.setImageResource(R.drawable.flag_france)
}else if(nodeGroupName.contains("俄罗斯")){
holder.countryImg.setImageResource(R.drawable.flag_russia)
}else if(nodeGroupName.contains("新加坡")){
holder.countryImg.setImageResource(R.drawable.flag_singapore)
}else if(nodeGroupName.contains("英国")){
holder.countryImg.setImageResource(R.drawable.flag_uk)
}else if(nodeGroupName.contains("澳大利亚")){
holder.countryImg.setImageResource(R.drawable.flag_australia)
}else if(nodeGroupName.contains("印度")){
holder.countryImg.setImageResource(R.drawable.flag_india)
}else if(nodeGroupName.contains("加拿大")){
holder.countryImg.setImageResource(R.drawable.flag_canada)
}else if(nodeGroupName.contains("以色列")){
holder.countryImg.setImageResource(R.drawable.ic_s_il)
}else if(nodeGroupName.contains("西班牙")){
holder.countryImg.setImageResource(R.drawable.ic_xibanya)
}else if(nodeGroupName.contains("西班牙")){
holder.countryImg.setImageResource(R.drawable.ic_xibanya)
}else if(nodeGroupName.contains("荷兰")){
holder.countryImg.setImageResource(R.drawable.ic_henan)
}else if(nodeGroupName.contains("意大利")){
holder.countryImg.setImageResource(R.drawable.ic_yidali)
}else if(nodeGroupName.contains("塞尔维亚")){
holder.countryImg.setImageResource(R.drawable.ic_sewy)
}else{
holder.countryImg.setImageResource(R.drawable.ic_s_location)
}
}
/*
* 内置监听回调
* */
interface ItemClickListener {
fun onItemClick(nodeStr: String, isPosition:Int)
}
}

Binary file not shown.

Binary file not shown.

View File

@ -288,6 +288,17 @@
app:layout_constraintStart_toStartOf="@+id/shareCard" app:layout_constraintStart_toStartOf="@+id/shareCard"
app:layout_constraintTop_toBottomOf="@+id/shareCard" /> app:layout_constraintTop_toBottomOf="@+id/shareCard" />
<TextView
android:id="@+id/tvUseNet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="#FF000000"
android:layout_marginTop="10dp"
android:textSize="13sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/shareCard"
app:layout_constraintTop_toBottomOf="@+id/shareCard" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSettingTab" android:id="@+id/rvSettingTab"

View File

@ -64,8 +64,8 @@ subprojects {
} }
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionName = "1.0.4.8" versionName = "1.0.5.2"
versionCode = 1048 versionCode = 1052
resValue("string", "release_name", "v$versionName") resValue("string", "release_name", "v$versionName")
resValue("integer", "release_code", "$versionCode") resValue("integer", "release_code", "$versionCode")
externalNativeBuild { externalNativeBuild {
@ -93,22 +93,6 @@ subprojects {
} }
productFlavors { productFlavors {
flavorDimensions("feature") flavorDimensions("feature")
create("2210") {
isDefault = true
dimension = flavorDimensionList[0]
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
buildConfigField("boolean", "is_free", "false")
buildConfigField("boolean", "is_back_cn", "false")
buildConfigField("String", "applicationId", "\"com.jsq.wufu\"")
buildConfigField("String", "service_url", "\"https://api.yijianlianjsq.com/\"")
buildConfigField("String", "productId", "\"2210\"")
manifestPlaceholders(
mapOf(
"APP_NAME" to "@string/wf",
"APP_ICON" to "@mipmap/oneclickconnection"
)
)
}
create("2211") { create("2211") {
isDefault = true isDefault = true
dimension = flavorDimensionList[0] dimension = flavorDimensionList[0]
@ -125,57 +109,9 @@ subprojects {
) )
) )
} }
create("2212") {
isDefault = true
dimension = flavorDimensionList[0]
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
buildConfigField("boolean", "is_free", "false")
buildConfigField("boolean", "is_back_cn", "false")
buildConfigField("String", "applicationId", "\"com.jsq.wufu\"")
buildConfigField("String", "service_url", "\"https://api.yijianlianjsq.com/\"")
buildConfigField("String", "productId", "\"2212\"")
manifestPlaceholders(
mapOf(
"APP_NAME" to "@string/wf",
"APP_ICON" to "@mipmap/oneclickconnection"
)
)
}
create("2213") {
isDefault = true
dimension = flavorDimensionList[0]
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
buildConfigField("boolean", "is_free", "false")
buildConfigField("boolean", "is_back_cn", "false")
buildConfigField("String", "applicationId", "\"com.jsq.wufu\"")
buildConfigField("String", "service_url", "\"https://api.yijianlianjsq.com/\"")
buildConfigField("String", "productId", "\"2213\"")
manifestPlaceholders(
mapOf(
"APP_NAME" to "@string/wf",
"APP_ICON" to "@mipmap/oneclickconnection"
)
)
}
create("2214") {
isDefault = true
dimension = flavorDimensionList[0]
buildConfigField("boolean", "PREMIUM", "Boolean.parseBoolean(\"false\")")
buildConfigField("boolean", "is_free", "false")
buildConfigField("boolean", "is_back_cn", "false")
buildConfigField("String", "applicationId", "\"com.jsq.wufu\"")
buildConfigField("String", "service_url", "\"https://api.yijianlianjsq.com/\"")
buildConfigField("String", "productId", "\"2214\"")
manifestPlaceholders(
mapOf(
"APP_NAME" to "@string/wf",
"APP_ICON" to "@mipmap/oneclickconnection"
)
)
}
} }
sourceSets { sourceSets {
getByName("2210") { getByName("2211") {
java.srcDirs("src/foss/java") java.srcDirs("src/foss/java")
} }
} }