一键连

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:screenOrientation="portrait"
/>
<activity
android:name=".act.NodeGroupActivity"
android:process=":background"
android:screenOrientation="portrait" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"

View File

@ -1,7 +1,10 @@
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.os.Build
import android.os.Bundle
import android.text.TextUtils
@ -9,12 +12,15 @@ import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatImageView
import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import com.baidu.mobads.action.ActionType
import com.baidu.mobads.action.BaiduAction
import com.cncat.vpn.common.constants.Intents
import com.cncat.vpn.common.log.Log
import com.cncat.vpn.core.model.LogMessage
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.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.orhanobut.logger.Logger
import com.tencent.mmkv.MMKV
import com.yingmao.clash.BuildConfig
@ -117,6 +124,9 @@ class MainActivity : BaseActivity() {
var newMainActGlo: Boolean = false
var newUuid: String? = null
var clashBinder: IClashManager? = null
private var mainBroadcastReceiver: BroadcastReceiver? = null
val trafficStatisticsData = MutableLiveData<String>()
suspend fun setSub2(url: String) {
profileBinder?.let { pm ->
pm.queryAll().forEach { itemPro ->
@ -191,8 +201,51 @@ class MainActivity : BaseActivity() {
startLog()
firstEntry()
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() {
clashBinder = MainApplication.getApp()?.let { ClashManager(it).wrap() as IClashManager }
profileBinder =
@ -268,24 +321,24 @@ class MainActivity : BaseActivity() {
if (data.code != "1000") {
RxToast.error(resources.getString(R.string.NodeAcquisitionFailed) + "${data.msg}")
}
data.data.let { url ->
android.util.Log.e("Data", "机场URL=${url}")
if (url.isNotEmpty()) {
//todo 判断订阅地址是否修改过,未修改就用接口返回的地址反之使用本地修改的地址
var pro_sub_url =
data.data.let { netSubUrl ->
if (netSubUrl.isNotEmpty()) {
val proSubUrl =
MMKV.defaultMMKV().decodeString(BZYConstants.PROFILE_SUB_URL, "")
// Log.d("pro_sub_url==>${pro_sub_url}")
if (!TextUtils.isEmpty(pro_sub_url)) {
if (pro_sub_url == url) {
subUrl = url
subUrl = if (!TextUtils.isEmpty(proSubUrl)) {
if (proSubUrl == netSubUrl) {
netSubUrl
} else {
subUrl = pro_sub_url
proSubUrl
}
} 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,
val udp: Boolean
) : 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.core.Clash
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.tencent.mmkv.MMKV
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.newMainActGlo
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.Companion.ScreenFlag
import com.yingmao.clash.conf.Conf
import com.yingmao.clash.entity.ClashNodebBean
import com.yingmao.clash.entity.NodeGroupItem
import com.yingmao.clash.fragment.AccelerateViewModel
import com.yingmao.clash.fragment.BaseFragment
import com.yingmao.clash.listener.AsyncCallBackListener
@ -66,6 +74,7 @@ import com.yingmao.clash.view.dialog.RxDialogSureCancel
import com.yingmao.clash.webview.CustomerServiceWebView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -74,6 +83,7 @@ import okhttp3.Response
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.get
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@ -118,6 +128,8 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
private lateinit var tvNodeName: TextView
private lateinit var clLinks: ConstraintLayout
val serStore by lazy { ServiceStore(requireActivity()) }
private var currentNode: MutableList<NodeGroupItem> = mutableListOf()
private val mPageInfo = ExpensesRecordActivity.PageInfo()
private var reConnection = false
@ -221,7 +233,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
RxToast.info(resources.getString(R.string.AccountExpired))
return@setOnClickListener
}
val intent = Intent(CommonUtils.getApp(), ChangeNodeActivity::class.java)
val intent = Intent(CommonUtils.getApp(), NodeGroupActivity::class.java)
if (!TextUtils.isEmpty(newUuid)) {
intent.putExtra(BZYConstants.MMKV_KEY_CURRENT_NODE, newUuid)
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) {
launch {
MainActivity.profileBinder?.let { pm ->
profileBinder?.let { pm ->
val active = pm.queryActive()
if (active == null) {
if (isStart) {
@ -390,7 +402,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
}
delay(500)
}
refreshNode()
val vpnRequest = context?.startClashService()
try {
startUTimer()
@ -415,7 +427,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
tvClick.text=resources.getString(R.string.Accelerating)
ivAccelerate.setImageResource(R.mipmap.yjl_home_clink_select_bg)
hideLoading()
checkGlo()
// checkGlo()
}
}
@ -697,14 +709,15 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "")
Log.d("getNodes selected_name==>${selectedName}")
if (TextUtils.isEmpty(selectedName)) {
MainActivity.subUrl?.let {
accelerateViewModel.getNodeOther(it, object : AsyncCallBackListener {
override fun suc(obj: Any?) {
proList = obj as MutableList<ClashNodebBean>
proList?.let { proListItem ->
val nodeName = proListItem[0].name
if (!TextUtils.isEmpty(nodeName)) {
launch(Dispatchers.IO) {
subUrl?.let {
val json = serStore.nodes
Log.d("getNodes json==>${json}")
currentNode=getNodesInfo(json)
Log.d("getNodes currentNode==>${currentNode} dataname:${currentNode[0].proxies[0]}")
val baseNodeName=currentNode[0].proxies[0]
if (!TextUtils.isEmpty(baseNodeName)) {
GlobalScope.launch(Dispatchers.IO) {
clashBinder?.let { cm ->
val o = cm.queryOverride(Clash.OverrideSlot.Session)
@ -729,22 +742,11 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
delay(1000)
}
cm.patchSelector("GLOBAL", nodeName)
cm.patchSelector("GLOBAL", baseNodeName)
}
}
}
}
}
override fun faild(response: Response) {
}
override fun err(msg: String, exception: Exception?) {
}
})
}
} else {
clashBinder?.let { cm ->
val o = cm.queryOverride(Clash.OverrideSlot.Session)
@ -903,6 +905,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
tvIntelligentMode.setTextColor("#FF006AFB".toColorInt())
tvGlobalMode.setTextColor("#FF000000".toColorInt())
sendMode(false)
checkGlo()
}
ProxyMode.GLOBAL -> {
@ -913,6 +916,7 @@ open class HomeFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispa
tvIntelligentMode.setTextColor("#FF000000".toColorInt())
tvGlobalMode.setTextColor("#FF006AFB".toColorInt())
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.setSub2
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.ShareActivity
import com.yingmao.clash.act.SplashHelper
@ -107,6 +108,8 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
private var baseLoad: RxDialogShapeLoading? = null
private lateinit var mainAtyVM: MainAtyVM
private lateinit var tvUseNet: TextView
private var settingPageList = mutableListOf<SettingPageBean>()
@RequiresApi(Build.VERSION_CODES.O)
override fun init(
@ -386,6 +389,15 @@ class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatc
rvSettingTab=view.findViewById(R.id.rvSettingTab)
tvUseNet=view.findViewById(R.id.tvUseNet)
trafficStatisticsData.observe(requireActivity()){
it.let {
tvUseNet.text=it
}
}
clVipCard.setOnClickListener {
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_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
android:id="@+id/rvSettingTab"

View File

@ -64,8 +64,8 @@ subprojects {
}
minSdk = 21
targetSdk = 34
versionName = "1.0.4.8"
versionCode = 1048
versionName = "1.0.5.2"
versionCode = 1052
resValue("string", "release_name", "v$versionName")
resValue("integer", "release_code", "$versionCode")
externalNativeBuild {
@ -93,22 +93,6 @@ subprojects {
}
productFlavors {
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") {
isDefault = true
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 {
getByName("2210") {
getByName("2211") {
java.srcDirs("src/foss/java")
}
}