一键连

新增退出账号未绑定用户提示绑定
This commit is contained in:
level 2025-12-15 17:41:10 +08:00
parent 91ec4dfb83
commit 548216d93d
7 changed files with 179 additions and 126 deletions

View File

@ -377,7 +377,7 @@ class MainActivity : BaseActivity() {
mmkv.decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, 0) mmkv.decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, 0)
val nowExpireTime = checkLoginRsp.data!!.vpnExpireTime val nowExpireTime = checkLoginRsp.data!!.vpnExpireTime
if (nowExpireTime > oldExpireTime) { if (nowExpireTime!=oldExpireTime) {
val expired = checkLoginRsp.data.expired val expired = checkLoginRsp.data.expired
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true) mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true)
mmkv.encode(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, nowExpireTime) mmkv.encode(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, nowExpireTime)

View File

@ -448,127 +448,4 @@ class NodeGroupActivity : BaseActivity(){
intent.setPackage(this.packageName) intent.setPackage(this.packageName)
) )
} }
private fun checkGlo() {
val isGlobal = isGlo
val isGlobal2 = MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false)
Log.i("checkGlo isGlobal==>${isGlobal}, isGlobal2==>${isGlobal2}")
val px: ProxyMode = if (isGlobal2) {
ProxyMode.GLOBAL
} else {
ProxyMode.PAC
}
setBackground(px)
launch(Dispatchers.IO) {
if (isGlobal) {
if (clashRunning) {
getNodes()
}
} else {
if (clashRunning) {
delay(1000)
clashBinder?.let { cm ->
val names = cm.queryProxyGroupNames(false)
Log.i("checkGlo names==>${names}")
if (names.isNotEmpty()) {
val selectedName =
MMKV.defaultMMKV()
.decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "");
Log.i("selected_name ==>${selectedName}")
if (!TextUtils.isEmpty(selectedName)) {
val o = cm.queryOverride(Clash.OverrideSlot.Session)
o.mode = TunnelState.Mode.Rule
cm.patchOverride(Clash.OverrideSlot.Session, o)
delay(500)
var gName = names[0]
//全局模式下 names[0]会自动添加一个 "GLOBAL" 组
if (names.size >= 2) {
if ("GLOBAL" == gName) {
//names[0] 为 "GLOBAL" //names[1]才是规则模式下的组名,所以需要手动赋值
gName = names[1]
}
}
cm.patchSelector(gName, selectedName!!)
}
}
}
}
}
}
}
private suspend fun getNodes() {
coroutineScope {
val selectedName =
MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "")
Log.d("getNodes selected_name==>${selectedName}")
if (TextUtils.isEmpty(selectedName)) {
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)
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)
}
cm.patchSelector("GLOBAL", baseNodeName)
}
}
}
}
} else {
clashBinder?.let { cm ->
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)
}
cm.patchSelector("GLOBAL", selectedName!!)
}
}
}
}
} }

View File

@ -5,15 +5,19 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
import android.text.method.HideReturnsTransformationMethod import android.text.method.HideReturnsTransformationMethod
import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.webkit.WebView
import android.widget.* import android.widget.*
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.cncat.vpn.service.ClashManager import com.cncat.vpn.service.ClashManager
import com.cncat.vpn.service.remote.IClashManager import com.cncat.vpn.service.remote.IClashManager
import com.cncat.vpn.service.remote.wrap import com.cncat.vpn.service.remote.wrap
import com.github.gzuliyujiang.oaid.DeviceID
import com.github.gzuliyujiang.oaid.DeviceIdentifier
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.yingmao.clash.view.dialog.RxDialogShapeLoading import com.yingmao.clash.view.dialog.RxDialogShapeLoading
@ -22,6 +26,7 @@ import com.tencent.mmkv.MMKV
import com.yingmao.clash.BuildConfig import com.yingmao.clash.BuildConfig
import com.yingmao.clash.R import com.yingmao.clash.R
import com.yingmao.clash.app.MainApplication import com.yingmao.clash.app.MainApplication
import com.yingmao.clash.app.MainApplication.Companion.OAID
import com.yingmao.clash.base.BaseActivity import com.yingmao.clash.base.BaseActivity
import com.yingmao.clash.conf.BZYConstants import com.yingmao.clash.conf.BZYConstants
import com.yingmao.clash.net.http.HttpReqType import com.yingmao.clash.net.http.HttpReqType
@ -177,7 +182,38 @@ class RegisterActivity : BaseActivity(), View.OnClickListener {
override fun onClick(v: View?) { override fun onClick(v: View?) {
when (v?.id) { when (v?.id) {
R.id.tv_register -> { R.id.tv_register -> {
var userAgent = System.getProperty("http.agent")
Log.i("SplashRegister", "userAgent::$userAgent")
if (userAgent != null) {
if (userAgent.isEmpty() || userAgent.length < 5) {
val webView = WebView(this@RegisterActivity)
userAgent = webView.settings.userAgentString
}
} else {
val webView = WebView(this@RegisterActivity)
userAgent = webView.settings.userAgentString
}
var oaId = OAID
Log.i(
"SplashRegister",
"OAID::" + OAID + "oaId.isEmpty()::" + oaId.isEmpty() + "userAgent::" + userAgent
)
val equipmentModel = Utils.getEquipmentModel()
Log.i("equipmentModel", "equipmentModel::" + equipmentModel)
if (oaId.isEmpty()) {
if (DeviceID.supportedOAID(MainApplication.getApp())) {
//同步获取oaid
val aOaId = DeviceIdentifier.getOAID(MainApplication.getApp())
oaId = aOaId
OAID = aOaId
Log.i(
"doRegisterByEncryption",
"aOaId::" + aOaId + "oaId::" + oaId + "OAID::" + OAID
)
}
}
val smsCode =etSmSCode.text.trim().toString() val smsCode =etSmSCode.text.trim().toString()
if(smsCode.isEmpty()){ if(smsCode.isEmpty()){
RxToast.error("验证码不能为空") RxToast.error("验证码不能为空")
@ -192,6 +228,10 @@ class RegisterActivity : BaseActivity(), View.OnClickListener {
reqJson.addProperty("from", "5") reqJson.addProperty("from", "5")
reqJson.addProperty("androidDevice", Utils.getUniqueID(this)) reqJson.addProperty("androidDevice", Utils.getUniqueID(this))
reqJson.addProperty("smsCode", smsCode) reqJson.addProperty("smsCode", smsCode)
reqJson.addProperty("userAgent", userAgent)
reqJson.addProperty("oaid", oaId)
reqJson.addProperty("equipmentModel", equipmentModel)
reqJson.addProperty("pkg", BuildConfig.applicationId)
if (registerKey.isNotEmpty() && registerIV2.isNotEmpty()) { if (registerKey.isNotEmpty() && registerIV2.isNotEmpty()) {
val data = AESUtil.byteArrayToHexStr( val data = AESUtil.byteArrayToHexStr(
AESUtil.RegisterEncrypt(reqJson.toString(), registerKey, registerIV2) AESUtil.RegisterEncrypt(reqJson.toString(), registerKey, registerIV2)

View File

@ -15,6 +15,9 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView
import com.alibaba.sdk.android.oss.ClientException import com.alibaba.sdk.android.oss.ClientException
import com.alibaba.sdk.android.oss.OSSClient import com.alibaba.sdk.android.oss.OSSClient
import com.alibaba.sdk.android.oss.ServiceException import com.alibaba.sdk.android.oss.ServiceException
@ -49,9 +52,11 @@ import com.yingmao.clash.net.http.entity.UploadParamData
import com.yingmao.clash.util.CommonUtils import com.yingmao.clash.util.CommonUtils
import com.yingmao.clash.util.DataUtils import com.yingmao.clash.util.DataUtils
import com.yingmao.clash.util.DataUtils.getAllFilePath import com.yingmao.clash.util.DataUtils.getAllFilePath
import com.yingmao.clash.util.Utils
import com.yingmao.clash.util.stopClashService import com.yingmao.clash.util.stopClashService
import com.yingmao.clash.view.RxToast import com.yingmao.clash.view.RxToast
import com.yingmao.clash.view.adapter.LanguageAdapter import com.yingmao.clash.view.adapter.LanguageAdapter
import com.yingmao.clash.view.dialog.DialogBandChoose
import com.yingmao.clash.view.dialog.DialogDoMainChoose import com.yingmao.clash.view.dialog.DialogDoMainChoose
import com.yingmao.clash.view.dialog.DialogSureCancelLogOut import com.yingmao.clash.view.dialog.DialogSureCancelLogOut
import com.yingmao.clash.view.popwindow.CommonPopupWindow import com.yingmao.clash.view.popwindow.CommonPopupWindow
@ -126,10 +131,48 @@ class SettingActivity : BaseActivity() {
}) })
} }
tvExit.setOnClickListener { tvExit.setOnClickListener {
cleanUserInfo() val userAccount =
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
if (Utils.isEmail(userAccount) || Utils.isPhoneNumber(userAccount)) {
cleanUserInfo()
}else{
MaterialDialog(this@SettingActivity).show {
this.cancelable(true)
cornerRadius(res = R.dimen.dp_10)
customView(
R.layout.user_bing_exit,
scrollable = false,
noVerticalPadding = true,
horizontalPadding = false
)
getCustomView().findViewById<TextView>(R.id.tvExitBt)
.setOnClickListener {
cleanUserInfo()
dismiss()
}
getCustomView().findViewById<TextView>(R.id.tvBindingBt)
.setOnClickListener {
showBingDialogChoose()
dismiss()
}
}
}
} }
} }
private fun showBingDialogChoose() {
val dialog = DialogBandChoose(this)
dialog.setOnBandClickListener(object : DialogBandChoose.OnBandClickListener {
override fun onBandClick(isPhone: Boolean) {
val intent = Intent(CommonUtils.getApp(), BandPhoneActivity::class.java)
intent.putExtra("isPhone", isPhone)
startActivity(intent)
dialog.dismiss()
}
})
dialog.show()
}
override fun httpLoading(httpStatus: HttpStatus.Loading) { override fun httpLoading(httpStatus: HttpStatus.Loading) {
super.httpLoading(httpStatus) super.httpLoading(httpStatus)

View File

@ -0,0 +1,24 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- This is the main color -->
<item>
<shape>
<!-- 边框颜色 -->
<gradient android:startColor="#FF757EFF" android:endColor="#FF009AEF" android:angle="0"/>
<corners android:radius="10dp"/>
</shape>
</item>
<!-- 给View的上 左 右设置8dp的边框 -->
<item
android:bottom="3dp"
android:left="1dp"
android:right="3dp"
android:top="1dp">
<shape>
<!-- View填充颜色 -->
<solid android:color="#FF8A04" />
<gradient android:startColor="#FF757EFF" android:endColor="#FF009AEF" android:angle="0"/>
<corners android:radius="10dp" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,68 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tvBindingContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/bindingShowTxt"
android:textColor="#FF000000"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/clBt"
android:layout_marginBottom="20dp"
android:layout_marginTop="20dp"
/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/clBt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="20dp"
>
<TextView
android:id="@+id/tvExitBt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="@string/exit"
android:paddingStart="40dp"
android:paddingEnd="40dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_marginStart="20dp"
android:background="@drawable/exit_check_bg"
/>
<TextView
android:id="@+id/tvBindingBt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@drawable/exit_check_bg"
android:paddingStart="40dp"
android:paddingTop="10dp"
android:paddingEnd="40dp"
android:paddingBottom="10dp"
android:text="@string/binding"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -483,4 +483,5 @@
<string name="GolbalModeNoChoose">全局模式加速下暂不支持切换节点,请切换至智能模式!</string> <string name="GolbalModeNoChoose">全局模式加速下暂不支持切换节点,请切换至智能模式!</string>
<string name="GetNodesAgined">获取节点中,请稍后点击加速</string> <string name="GetNodesAgined">获取节点中,请稍后点击加速</string>
<string name="SubscriptionlinkTop">获取节点信息出错,请手动点击 [源切换] 或联系客服处理!</string> <string name="SubscriptionlinkTop">获取节点信息出错,请手动点击 [源切换] 或联系客服处理!</string>
<string name="bindingShowTxt">当前账号尚未绑定,直接退出可能会造成账号丢失,推荐前往绑定账号或保存临时账号后在进行退出</string>
</resources> </resources>