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.view.View import android.widget.ImageView 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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import com.chad.library.adapter.base.BaseQuickAdapter 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.IProfileManager 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.R import com.yingmao.clash.act.ProxyModeActivity.ProxyMode import com.yingmao.clash.act.NewMainActivity.Companion.clashBinder import com.yingmao.clash.act.NewMainActivity.Companion.profileBinder import com.yingmao.clash.act.NewMainActivity.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.ClashNodebBean import com.yingmao.clash.fragment.NewAccFragment.Companion.clashRunning import com.yingmao.clash.fragment.NewAccFragment.Companion.isGlo import com.yingmao.clash.remote.Resource import com.yingmao.clash.util.Utils import com.yingmao.clash.view.RxToast import com.yingmao.clash.view.adapter.NodeListAdapter import com.yingmao.clash.view.dialog.DialogDoMainChoose import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import java.util.Timer import java.util.TimerTask import java.util.UUID class ChangeNodeActivity : BaseActivity() { companion object { private var currentNode: MutableList = 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 var uuid: UUID? = null private var testRuning = false private lateinit var tvProA:TextView private lateinit var tvProj:TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_change_node) changeNodeVM = ViewModelProvider(this)[ChangeNodeVM::class.java] initViews() initData() } override fun onBackPressed() { intent.putExtra(BZYConstants.EXTRA_KEY_IS_RUNING, is_runing) setResult(2, intent) super.onBackPressed() } private fun initData() { val isGlobal = MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false) val px: ProxyMode if (isGlobal) { px = ProxyMode.GLOBAL } else { px = ProxyMode.PAC } setBackground(px) cacheNodeStr = MMKV.defaultMMKV().decodeString(BZYConstants.NODE_All_ITEM_NAME_KEY, "") val intentFilter = IntentFilter(Intents.ACTION_NODE_LIST) android.util.Log.i("NODETA","DAAAAqqqqq::AA") broadcastReceiver = NodeListBR() if (Build.VERSION.SDK_INT >= 34 && applicationInfo.targetSdkVersion >= 34) { registerReceiver( broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED ) } else { registerReceiver( broadcastReceiver, intentFilter ) } } @SuppressLint("SetTextI18n") private fun initViews() { tvProA=findViewById(R.id.tvProA) tvProj=findViewById(R.id.tvProj) aiBack = findViewById(R.id.aiBack) swipeLayout = findViewById(R.id.swipeLayout) nodeListRv = findViewById(R.id.rv_node_list) tvProA.setOnClickListener { setBackground(ProxyMode.PAC) MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false) } tvProj.setOnClickListener { MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_GLOBAL_MODEL, true) setBackground(ProxyMode.GLOBAL) } aiBack.setOnClickListener { onBackPressed() } initRefreshLayout() var userTrafficTv: TextView = findViewById(R.id.node_user_traffic) findViewById(R.id.iv_back).setOnClickListener { onBackPressed() } initAdapter() if (!TextUtils.isEmpty(cacheNodeStr)) { currentNode = getNodesInfo(cacheNodeStr!!) setNewData(currentNode) } } private fun getNodesInfo(json: String): MutableList { val mList: MutableList = mutableListOf() if (TextUtils.isEmpty(json)) { return mList } val gson = Gson() val ja = JsonParser.parseString(json).asJsonArray val cnb = ClashNodebBean("自动选择", "", "", 1, "", "", 0, true); mList.add(cnb) for (i in 0 until ja.size()) { var bean = gson.fromJson(ja.get(i).asString, ClashNodebBean::class.java) bean.delay = Utils.generateRandomTwoDigitNumber(10, 60) mList.add(bean) } return mList } inner class NodeListBR : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { android.util.Log.i("NODETA","DAAAA::"+intent?.action) when (intent?.action) { Intents.ACTION_NODE_LIST -> { Log.d("ACTION_NODE_LIST ACTION_NODE_LIST") hideLoading() timerJob?.cancel() dialogDoMainChoose?.dismiss() val json = serStore.nodes if (TextUtils.isEmpty(json)) { return } android.util.Log.i("NODETA","subUrl::"+subUrl) android.util.Log.i("NODETA","json::"+json) //缓存订阅地址 subUrl = nowSubUrl MMKV.defaultMMKV().encode(BZYConstants.PROFILE_SUB_URL, nowSubUrl) //缓存节点 MMKV.defaultMMKV().encode(BZYConstants.NODE_All_ITEM_NAME_KEY, json) currentNode = getNodesInfo(json) setNewData(currentNode) swipeLayout.isRefreshing = false } else -> { } } } } override fun onDestroy() { super.onDestroy() broadcastReceiver?.let { unregisterReceiver(it) } } override fun onResume() { super.onResume() // uuid = intent.getStringExtra(BZYConstants.MMKV_KEY_CURRENT_NODE) // com.cncat.vpn.common.log.Log.d("ChangeNode uuid==>$uuid") is_runing = intent.getBooleanExtra(BZYConstants.EXTRA_KEY_IS_RUNING, false) } val remote = Resource() var GetNodeTimeOutNum = 5 var is_runing = false private var timer: Timer? = null private var timerTask: TimerTask? = null var nowSubUrl: String = "" private fun updateProfile(isDelete: Boolean) { try { launch(Dispatchers.IO) { profileBinder?.let { profileManager -> var mProfile = profileManager.queryAll().find { it.name == Conf.getClashConfName(this@ChangeNodeActivity.applicationContext) } if (mProfile == null) { //为空的情况下选择第一个 profileManager.queryAll().forEach { profile -> mProfile = profile return@forEach } } mProfile?.let { profile -> uuid = profile.uuid Log.d("ChangeNode22 uuid==>${profile.uuid}") if (isDelete) { Log.d("gogogogogo isDelete") profileManager.delete(profile.uuid) MMKV.defaultMMKV() .removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY) setSub2(profileManager, nowSubUrl) } else { Log.d("gogogogogo update") profileManager.setActive(profile) delay(500) if (!TextUtils.isEmpty(profile.source)) { // Log.d("updateProfile pro.source==>${profile.source}, url==>${nowSubUrl}") if (profile.source != nowSubUrl) { Log.d("updateProfile patch") profileManager.patch( profile.uuid, profile.name, nowSubUrl, 1800000L ) Log.d("updateProfile patch 1") profileManager.commit(profile.uuid) { status -> Log.d("updateProfile updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress) } Log.d("updateProfile patch 2") } } Log.d("updateProfile go update") profileManager.update(profile.uuid) } Log.d("gogogogogo end") } } } } catch (e: Exception) { e.printStackTrace() } } private fun initRefreshLayout() { swipeLayout.setColorSchemeColors(Color.rgb(47, 223, 189)) swipeLayout.setOnRefreshListener(object : OnRefreshListener { override fun onRefresh() { if (testRuning) { RxToast.info("正在测速中."); swipeLayout.isRefreshing = false return } com.cncat.vpn.common.log.Log.d("onRefresh") // getNodesStatus(); // changeNodeVM.getAirNodes() swipeLayout.isRefreshing = true updateProfile(false) com.cncat.vpn.common.log.Log.d("onRefresh end") swipeLayout.isRefreshing = false } }) } override fun onStart() { super.onStart() subUrl?.let { nowSubUrl = it } doProfile() } fun doProfile() { showLoading("正在获取...", this@ChangeNodeActivity) startTimer() updateProfile(false) } private fun changeSubUrl(originalUrl: String, newUrl: String) { if (!originalUrl.isNullOrEmpty() && !newUrl.isNullOrEmpty()) { nowSubUrl = originalUrl.replace(Regex("https://[^/]+/"), "$newUrl/") MMKV.defaultMMKV().encode(BZYConstants.PROFILE_SUB_URL, nowSubUrl) } } val reloadLock = Semaphore(10) private fun setPing() { if (!clashRunning) { RxToast.info("请先开启加速后,再进行测试.") return } if (testRuning) { RxToast.info("正在测速中.") return } testRuning = true launch(Dispatchers.IO) { var group = reloadLock.withPermit { clashBinder?.let { cb -> var names = cb.queryProxyGroupNames(false) if (names.isNotEmpty()) { cb.healthCheck(names[0]) delay(1000) cb.queryProxyGroup(names[0], uiStore.proxySort); } else { null } } } testRuning = false var newDatas = mutableListOf(); group?.proxies?.forEach { proxy -> Log.d("node==>${proxy}") if (currentNode?.isNotEmpty() == true) { for (nodeItem: ClashNodebBean in currentNode!!) { if (nodeItem.name == proxy.name) { if (proxy.name != "自动选择") { nodeItem.delay = proxy.delay; } Log.d("nodeItem==>${nodeItem}") newDatas.add(nodeItem) return@forEach } } } } if (newDatas.isNotEmpty()) { setNewData(newDatas) } } } private fun setNewData(nodes: MutableList?) { runOnUiThread { if (nodes != null) { nodeListAdapter.setNewInstance(nodes) val selectedName = MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, ""); for ((index, node) in nodes.withIndex()) { if (node.name == selectedName) { nodeListAdapter.selectedPosition = index } } nodeListAdapter.notifyDataSetChanged() } } } var nodeListAdapter: NodeListAdapter = NodeListAdapter(R.layout.node_list_item_layout) private fun initAdapter() { nodeListRv.layoutManager = LinearLayoutManager(this) nodeListRv.adapter = nodeListAdapter nodeListAdapter.selectedPosition nodeListAdapter.setOnItemClickListener { baseQuickAdapter: BaseQuickAdapter<*, *>, view: View, position: Int -> (baseQuickAdapter.getItem(position) as ClashNodebBean).let { profile -> Log.i("select_node : ${profile.name}") // checkedNode = profile MMKV.defaultMMKV().encode(BZYConstants.NODE_SELECTED_NAME_KEY, profile.name) nodeListAdapter.notifyItemChanged(nodeListAdapter.selectedPosition) nodeListAdapter.selectedPosition = if (nodeListAdapter.selectedPosition == position) -1 else position nodeListAdapter.notifyItemChanged(position) android.util.Log.i("AAAAAAAAAAAAAA:","profile.name:::"+{profile.name}) launch(Dispatchers.IO) { val intent = Intent() intent.putExtra(BZYConstants.MMKV_KEY_SELECT_NODE_NAME, profile.name) intent.putExtra(BZYConstants.EXTRA_KEY_IS_RUNING, is_runing) setResult(2, intent) finish() } } } } private fun createProfile() { launch(Dispatchers.IO) { profileBinder?.let { pro -> uuid?.let { uu -> pro.delete(uu) delay(500) setSub2(pro, subUrl) } } } } public suspend fun setSub2(pm: IProfileManager, url: String?) { // com.cncat.vpn.common.log.Log.d("setSub2.... url==>$url") if (TextUtils.isEmpty(url)) { com.cncat.vpn.common.log.Log.d("setSub2 url isEmpty") return } com.cncat.vpn.common.log.Log.d("setSub2 2") com.cncat.vpn.common.log.Log.d("setSub2 3") val pro = pm.queryAll().find { it.name == Conf.getClashConfName(this@ChangeNodeActivity.applicationContext) } com.cncat.vpn.common.log.Log.d("setSub2 4") pro?.let { com.cncat.vpn.common.log.Log.d("setSub2 pro 已存在 ==>") // pm.setActive(it) com.cncat.vpn.common.log.Log.d("setSub2 setActive end") } ?: run { var uuid: UUID = pm.create( Profile.Type.Url, Conf.getClashConfName(this@ChangeNodeActivity.applicationContext) ) delay(500) pm.queryByUUID(uuid)?.let { qPro -> pm.setActive(qPro) pm.patch( qPro.uuid, qPro.name, // "https://mojie0201.xn--8stx8olrwkucjq3b.com/api/v1/client/subscribe?token=169110d2674c49604b7e5b5cea341579", // "https://www.otcopusapp.cc/lx3af288h5i8pz380/api/v1/client/subscribe?token=d6bc98c7ea53099fa5bd1a3ea415ebb6", url!!, 1800000L ) // coroutineScope { pm.commit(qPro.uuid) { status -> android.util.Log.d( Conf.TAG, "new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress ) } // } } } } var isCreate: Boolean = false private var timerJob: Job? = null private fun startTimer() { GetNodeTimeOutNum = 15 timerJob = launch { repeat(16) { Log.d("GetNodeTimeOutNum==>$GetNodeTimeOutNum") if (GetNodeTimeOutNum == 0) { hideLoading() Log.d("stopTimer...") RxToast.error("获取节点失败...请尝试切换其他地址") showDomainList() timerJob?.cancel() // delay(1000) // this@ChangeNodeActivity.finish() } GetNodeTimeOutNum-- delay(1000) } } } var dialogDoMainChoose: DialogDoMainChoose? = null private fun showDomainList() { try { SplashHelper(changeNodeVM).getHostWithFallback { success -> MainScope().launch { if (success) { if (dialogDoMainChoose == null) { dialogDoMainChoose = DialogDoMainChoose(this@ChangeNodeActivity) } if (dialogDoMainChoose?.isShowing == false) { dialogDoMainChoose?.setOnTryButtonListener(object : DialogDoMainChoose.OnTryButtonListener { override fun tryButton() { saveSubUrl() doProfile() dialogDoMainChoose?.dismiss() } override fun cancel() { com.cncat.vpn.common.log.Log.d("tryButton CHECK_LOGIN cancel") dialogDoMainChoose?.dismiss() } }) dialogDoMainChoose?.setOnListItemListener(object : DialogDoMainChoose.OnListItemListener { override fun listItemClick() { saveSubUrl() MainApplication.restartApp(this@ChangeNodeActivity, object : MainApplication.OnRestartButtonListener { override fun enterButton() { } override fun cancelButton() { doProfile() dialogDoMainChoose?.dismiss() } }) } }) dialogDoMainChoose?.show() } } } } } catch (e: Exception) { e.printStackTrace() } } private fun saveSubUrl() { var currentHost = MMKV.defaultMMKV() .decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "") var oldUrl = nowSubUrl currentHost?.let { changeSubUrl(oldUrl, it) } } private fun stopTimer() { timerTask?.cancel() timer?.purge() timer?.cancel() timerTask = null timer = null } private fun setBackground(proxyMode: ProxyMode) { when (proxyMode) { ProxyMode.PAC -> { isGlo = false tvProA.setBackgroundResource(R.drawable.oc_proxy_choose_bg) tvProj.setBackgroundResource(0) tvProA.setTextColor(Color.parseColor("#FFFFFF")) tvProj.setTextColor(Color.parseColor("#B36C1B")) sendMode(false) } ProxyMode.GLOBAL -> { isGlo = true tvProA.setBackgroundResource(0) tvProA.setTextColor(Color.parseColor("#B36C1B")) tvProj.setTextColor(Color.parseColor("#FFFFFF")) tvProj.setBackgroundResource(R.drawable.oc_proxy_choose_bg) sendMode(true) } else->{ } } } fun sendMode(type: Boolean) { val intent = Intent(Intents.ACTION_Glo_Mode) intent.putExtra("mode", type) sendBroadcast( intent.setPackage(this.packageName) ) } }