diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed13ca2..496ac92 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -140,6 +140,10 @@ android:process=":background" android:screenOrientation="portrait" /> + () + 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 } - launch(Dispatchers.IO) { - subUrl?.let { setSub2(it) } + lifecycleScope.launch(Dispatchers.IO) { + subUrl?.let { + Log.i("netSubUrl:${netSubUrl}::保存机场URL:=${proSubUrl}::subUrl:${subUrl}") + setSub2(it) + } } } } diff --git a/app/src/main/java/com/yingmao/clash/act/NodeGroupActivity.kt b/app/src/main/java/com/yingmao/clash/act/NodeGroupActivity.kt new file mode 100644 index 0000000..1f6ea19 --- /dev/null +++ b/app/src/main/java/com/yingmao/clash/act/NodeGroupActivity.kt @@ -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 = 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() + 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 { + val mList: MutableList = 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?) { + 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) } + } + + + + +} diff --git a/app/src/main/java/com/yingmao/clash/entity/ClashNodebBean.kt b/app/src/main/java/com/yingmao/clash/entity/ClashNodebBean.kt index d48a955..487fa77 100644 --- a/app/src/main/java/com/yingmao/clash/entity/ClashNodebBean.kt +++ b/app/src/main/java/com/yingmao/clash/entity/ClashNodebBean.kt @@ -14,4 +14,11 @@ data class ClashNodebBean( val password: String, var delay: Int, val udp: Boolean +) : Parcelable,Serializable + +@Parcelize +data class NodeGroupItem( + val name: String, + val type: String, + val proxies: MutableList ) : Parcelable,Serializable \ No newline at end of file diff --git a/app/src/main/java/com/yingmao/clash/fragment/home/HomeFragment.kt b/app/src/main/java/com/yingmao/clash/fragment/home/HomeFragment.kt index 89d06ec..90b08a1 100644 --- a/app/src/main/java/com/yingmao/clash/fragment/home/HomeFragment.kt +++ b/app/src/main/java/com/yingmao/clash/fragment/home/HomeFragment.kt @@ -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 = 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,53 +709,43 @@ 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 - proList?.let { proListItem -> - val nodeName = proListItem[0].name - if (!TextUtils.isEmpty(nodeName)) { - launch(Dispatchers.IO) { - clashBinder?.let { cm -> + 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] - val o = cm.queryOverride(Clash.OverrideSlot.Session) - o.mode = TunnelState.Mode.Global - cm.patchOverride(Clash.OverrideSlot.Session, o) - delay(3000) - //等待全局模式生效 - var maxIndex = 0 - while (true) { - if (maxIndex > 3) { - break - } - val names = cm.queryProxyGroupNames(false) - if (names.isNotEmpty()) { - if (names[0] == "GLOBAL") { - Log.d("repeat end") - break - } - } - Log.d("repeat agent") - maxIndex++ - delay(1000) - } + if (!TextUtils.isEmpty(baseNodeName)) { + GlobalScope.launch(Dispatchers.IO) { + clashBinder?.let { cm -> - cm.patchSelector("GLOBAL", nodeName) - } - } - } - } - } + val o = cm.queryOverride(Clash.OverrideSlot.Session) + o.mode = TunnelState.Mode.Global + cm.patchOverride(Clash.OverrideSlot.Session, o) + delay(3000) + //等待全局模式生效 + var maxIndex = 0 + while (true) { + if (maxIndex > 3) { + break + } + val names = cm.queryProxyGroupNames(false) + if (names.isNotEmpty()) { + if (names[0] == "GLOBAL") { + Log.d("repeat end") + break + } + } + Log.d("repeat agent") + maxIndex++ + delay(1000) + } - - override fun faild(response: Response) { - } - - override fun err(msg: String, exception: Exception?) { - } - - }) + cm.patchSelector("GLOBAL", baseNodeName) + } + } + } } } else { clashBinder?.let { cm -> @@ -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 { + val mList: MutableList = 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 + } } \ No newline at end of file diff --git a/app/src/main/java/com/yingmao/clash/fragment/setting/SettingFragment.kt b/app/src/main/java/com/yingmao/clash/fragment/setting/SettingFragment.kt index 4b2e9d9..c579821 100644 --- a/app/src/main/java/com/yingmao/clash/fragment/setting/SettingFragment.kt +++ b/app/src/main/java/com/yingmao/clash/fragment/setting/SettingFragment.kt @@ -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() @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() } diff --git a/app/src/main/java/com/yingmao/clash/view/adapter/NodeGroupAdapter.kt b/app/src/main/java/com/yingmao/clash/view/adapter/NodeGroupAdapter.kt new file mode 100644 index 0000000..a2d8082 --- /dev/null +++ b/app/src/main/java/com/yingmao/clash/view/adapter/NodeGroupAdapter.kt @@ -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() { + + private var nodeGroupList = mutableListOf() + + //设置回调监听 + 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, context: Context) { + nodeGroupList=infoList + this.context=context + notifyDataSetChanged() + } + inner class NodeGroupAdapterViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val nodeitemItem = itemView.findViewById(R.id.rl_node_list_item) + var nodeName = itemView.findViewById(R.id.node_list_item_tv_node_name) + + var chekImg = itemView.findViewById(R.id.mrb_node_checked) + + var countryImg = itemView.findViewById(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) + } +} \ No newline at end of file diff --git a/app/src/main/libs/core.aar b/app/src/main/libs/core.aar index 3feb490..02bbeee 100644 Binary files a/app/src/main/libs/core.aar and b/app/src/main/libs/core.aar differ diff --git a/app/src/main/libs/service.aar b/app/src/main/libs/service.aar index e0a1b40..4dff55d 100644 Binary files a/app/src/main/libs/service.aar and b/app/src/main/libs/service.aar differ diff --git a/app/src/main/res/layout/setting_fragment_layout.xml b/app/src/main/res/layout/setting_fragment_layout.xml index 0260cf0..a5d0b5a 100644 --- a/app/src/main/res/layout/setting_fragment_layout.xml +++ b/app/src/main/res/layout/setting_fragment_layout.xml @@ -288,6 +288,17 @@ app:layout_constraintStart_toStartOf="@+id/shareCard" app:layout_constraintTop_toBottomOf="@+id/shareCard" /> +