yijianlianUI/app/src/main/java/com/yingmao/clash/act/ChangeNodeActivity.kt

598 lines
22 KiB
Kotlin

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<ClashNodebBean> = 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<ImageView>(R.id.iv_back).setOnClickListener {
onBackPressed()
}
initAdapter()
if (!TextUtils.isEmpty(cacheNodeStr)) {
currentNode = getNodesInfo(cacheNodeStr!!)
setNewData(currentNode)
}
}
private fun getNodesInfo(json: String): MutableList<ClashNodebBean> {
val mList: MutableList<ClashNodebBean> = 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<IRemoteService>()
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<ClashNodebBean>();
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<ClashNodebBean>?) {
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)
)
}
}