From c49627cba7f823b0025688b4ff7f36610713e1ba Mon Sep 17 00:00:00 2001 From: cyh Date: Fri, 25 Apr 2025 13:41:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=87=E6=8D=A2=E6=BA=90?= =?UTF-8?q?=E6=97=B6=E9=87=8D=E5=90=AFapp=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 +- .../yingmao/clash/act/ChangeNodeActivity.kt | 58 ++- .../clash/act/MemberRechargeActivity.kt | 282 +++++++++----- .../com/yingmao/clash/app/MainApplication.kt | 45 ++- .../clash/fragment/setting/SettingFragment.kt | 366 +++++++++++------- .../clash/view/dialog/DialogDoMainChoose.kt | 10 +- .../view/dialog/DialogSureRestartChoose.kt | 95 +++++ .../res/layout/dialog_app_restart_choose.xml | 68 ++++ app/src/main/res/values-en/strings.xml | 4 +- app/src/main/res/values-zh-rHK/strings.xml | 4 +- app/src/main/res/values-zh-rTW/strings.xml | 4 +- app/src/main/res/values/strings.xml | 4 +- 12 files changed, 678 insertions(+), 266 deletions(-) create mode 100644 app/src/main/java/com/yingmao/clash/view/dialog/DialogSureRestartChoose.kt create mode 100644 app/src/main/res/layout/dialog_app_restart_choose.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3307854..47b1106 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -79,6 +79,8 @@ dependencies { // https://mvnrepository.com/artifact/com.tencent/mmkv-static implementation("com.tencent:mmkv-static:1.2.10") + implementation ("com.jakewharton:process-phoenix:3.0.0") + implementation("com.afollestad.material-dialogs:core:3.2.1") //Retrofit 网络请求库 api("com.squareup.retrofit2:retrofit:2.9.0") @@ -95,7 +97,7 @@ dependencies { // api("com.google.code.gson:gson:2.10.1") api("org.yaml:snakeyaml:1.29") -// https://mvnrepository.com/artifact/com.al\ipay.sdk/alipaysdk-android +// https://mvnrepository.com/artifact/com.alipay.sdk/alipaysdk-android implementation("com.alipay.sdk:alipaysdk-android:15.8.17") // https://mvnrepository.com/artifact/com.journeyapps/zxing-android-embedded // implementation("com.journeyapps:zxing-android-embedded:3.6.0") diff --git a/app/src/main/java/com/yingmao/clash/act/ChangeNodeActivity.kt b/app/src/main/java/com/yingmao/clash/act/ChangeNodeActivity.kt index 32490d0..e1e47bf 100644 --- a/app/src/main/java/com/yingmao/clash/act/ChangeNodeActivity.kt +++ b/app/src/main/java/com/yingmao/clash/act/ChangeNodeActivity.kt @@ -15,6 +15,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.AppCompatImageView import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout @@ -37,10 +38,12 @@ import com.yingmao.clash.remote.Resource import com.cncat.vpn.service.model.Profile import com.cncat.vpn.service.remote.IProfileManager import com.cncat.vpn.service.remote.IRemoteService +import com.yingmao.clash.app.MainApplication 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 com.yingmao.clash.view.dialog.DialogSureRestartChoose import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope @@ -119,7 +122,7 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener { @SuppressLint("SetTextI18n") private fun initViews() { - aiBack=findViewById(R.id.aiBack) + aiBack = findViewById(R.id.aiBack) aiBack.setOnClickListener { onBackPressed() } @@ -186,7 +189,7 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener { return } //缓存订阅地址 - UIMainActivity.subUrl=nowSubUrl + UIMainActivity.subUrl = nowSubUrl MMKV.defaultMMKV().encode(BZYConstants.PROFILE_SUB_URL, nowSubUrl) //缓存节点 MMKV.defaultMMKV().encode(BZYConstants.NODE_All_ITEM_NAME_KEY, json) @@ -322,10 +325,12 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener { showLoading("正在获取...", this@ChangeNodeActivity) startTimer() updateProfile(false) + } private fun changeSubUrl(originalUrl: String, newUrl: String) { nowSubUrl = originalUrl.replace(Regex("https://[^/]+/"), "$newUrl/") + MMKV.defaultMMKV().encode(BZYConstants.PROFILE_SUB_URL, nowSubUrl) } val reloadLock = Semaphore(10) @@ -341,7 +346,8 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener { testRuning = true updateMoreBar("测速中") launch(Dispatchers.IO) { - var group = reloadLock.withPermit { UIMainActivity.clashBinder?.let { cb -> + var group = reloadLock.withPermit { + UIMainActivity.clashBinder?.let { cb -> var names = cb.queryProxyGroupNames(false) if (names.isNotEmpty()) { cb.healthCheck(names[0]) @@ -435,7 +441,7 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener { uuid?.let { uu -> pro.delete(uu) delay(500) - setSub2(pro, UIMainActivity.subUrl) + setSub2(pro, UIMainActivity.subUrl) } } } @@ -510,33 +516,51 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener { } } } - var dialogDoMainChoose:DialogDoMainChoose? =null + + var dialogDoMainChoose: DialogDoMainChoose? = null private fun showDomainList() { try { SplashHelper(changeNodeVM).getHostWithFallback { success -> MainScope().launch { + if (success) { - if (dialogDoMainChoose==null) { + if (dialogDoMainChoose == null) { dialogDoMainChoose = DialogDoMainChoose(this@ChangeNodeActivity) } - if (dialogDoMainChoose?.isShowing==false) { + if (dialogDoMainChoose?.isShowing == false) { dialogDoMainChoose?.setOnTryButtonListener(object : DialogDoMainChoose.OnTryButtonListener { override fun tryButton() { - var currentHost = MMKV.defaultMMKV() - .decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "") -// com.cncat.vpn.common.log.Log.d("tryButton currentHost==>${currentHost}") - var oldUrl = nowSubUrl - currentHost?.let { changeSubUrl(oldUrl, it) } - + 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() } } @@ -548,6 +572,14 @@ class ChangeNodeActivity : BaseActivity(), View.OnClickListener { } } + private fun saveSubUrl() { + var currentHost = MMKV.defaultMMKV() + .decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "") +// com.cncat.vpn.common.log.Log.d("tryButton currentHost==>${currentHost}") + var oldUrl = nowSubUrl + currentHost?.let { changeSubUrl(oldUrl, it) } + } + private fun stopTimer() { timerTask?.cancel() timer?.purge() diff --git a/app/src/main/java/com/yingmao/clash/act/MemberRechargeActivity.kt b/app/src/main/java/com/yingmao/clash/act/MemberRechargeActivity.kt index 3e32fcc..e02d55f 100644 --- a/app/src/main/java/com/yingmao/clash/act/MemberRechargeActivity.kt +++ b/app/src/main/java/com/yingmao/clash/act/MemberRechargeActivity.kt @@ -31,6 +31,7 @@ import com.stripe.android.model.StripeIntent import com.tencent.mmkv.MMKV import com.yingmao.clash.BuildConfig import com.yingmao.clash.R +import com.yingmao.clash.app.MainApplication import com.yingmao.clash.base.BaseActivity import com.yingmao.clash.conf.BZYConstants import com.yingmao.clash.entity.CheckLoginRsp @@ -49,6 +50,7 @@ import com.yingmao.clash.util.PayUtils import com.yingmao.clash.util.ThreadUtils import com.yingmao.clash.view.RxToast import com.yingmao.clash.view.dialog.DialogDoMainChoose +import com.yingmao.clash.view.dialog.DialogSureRestartChoose import com.yingmao.clash.view.dialog.RxDialogShapeLoading import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope @@ -58,23 +60,24 @@ import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.Locale -class MemberRechargeActivity: BaseActivity() { - private lateinit var aiBack: AppCompatImageView - private lateinit var userIdentity: TextView - private lateinit var userIdentityNumber: TextView - private lateinit var expirationTime: TextView - private lateinit var userIdentityNumberCopy: TextView +class MemberRechargeActivity : BaseActivity() { + private lateinit var aiBack: AppCompatImageView + private lateinit var userIdentity: TextView + private lateinit var userIdentityNumber: TextView + private lateinit var expirationTime: TextView + private lateinit var userIdentityNumberCopy: TextView private lateinit var viewModel: RechargeViewModel - private lateinit var swipeLayout: SwipeRefreshLayout - private lateinit var rvRechargeList:RecyclerView - private lateinit var tvPay:TextView - private lateinit var tvRechargeRecord:TextView + private lateinit var swipeLayout: SwipeRefreshLayout + private lateinit var rvRechargeList: RecyclerView + private lateinit var tvPay: TextView + private lateinit var tvRechargeRecord: TextView private var selectedGrade: Grade? = null private lateinit var rxDialogShapeLoading: RxDialogShapeLoading private var outTradeNo = "" - private var selectedGradeID :Int=0 + private var selectedGradeID: Int = 0 + //stripePay 支付 private var paymentIntentClientSecret: String = "" private val stripe: Stripe by lazy { @@ -95,60 +98,63 @@ class MemberRechargeActivity: BaseActivity() { private fun initData() { val validPeriod = MMKV.defaultMMKV().decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD) setExpireDate(validPeriod) - if(checkExpire()){ - userIdentity.text=resources.getString(R.string.TemporaryUser) - }else{ - userIdentity.text=resources.getString(R.string.VIPMember) + if (checkExpire()) { + userIdentity.text = resources.getString(R.string.TemporaryUser) + } else { + userIdentity.text = resources.getString(R.string.VIPMember) } - userIdentityNumber.text = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) + userIdentityNumber.text = + MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) viewModel = ViewModelProvider(this)[RechargeViewModel::class.java] observe(this, viewModel) //获取充值列表 - val username = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME,"")!! - val phToken = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN,"")!! + val username = + MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!! + val phToken = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")!! val userId = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_USER_ID)!! viewModel.getPhRechargeInfo(username, phToken, userId) } private fun initView() { - aiBack=findViewById(R.id.aiBack) - userIdentity=findViewById(R.id.userIdentity) - userIdentityNumber=findViewById(R.id.userIdentityNumber) - expirationTime=findViewById(R.id.expirationTime) - userIdentityNumberCopy=findViewById(R.id.userIdentityNumberCopy) - swipeLayout=findViewById(R.id.swipeLayout) - rvRechargeList=findViewById(R.id.rvRechargeList) - tvPay=findViewById(R.id.tvPay) - tvRechargeRecord=findViewById(R.id.tvRechargeRecord) + aiBack = findViewById(R.id.aiBack) + userIdentity = findViewById(R.id.userIdentity) + userIdentityNumber = findViewById(R.id.userIdentityNumber) + expirationTime = findViewById(R.id.expirationTime) + userIdentityNumberCopy = findViewById(R.id.userIdentityNumberCopy) + swipeLayout = findViewById(R.id.swipeLayout) + rvRechargeList = findViewById(R.id.rvRechargeList) + tvPay = findViewById(R.id.tvPay) + tvRechargeRecord = findViewById(R.id.tvRechargeRecord) swipeLayout.setColorSchemeColors(Color.rgb(47, 223, 189)) swipeLayout.setOnRefreshListener { selectedGrade = null - val username = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME,"")!! - val phToken = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN,"")!! + val username = + MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!! + val phToken = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")!! //获取充值列表 val userId = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_USER_ID)!! viewModel.getPhRechargeInfo(username, phToken, userId) } aiBack.setOnClickListener { - val intent=Intent() - this.setResult(1000,intent) + val intent = Intent() + this.setResult(1000, intent) finish() // 关闭当前 Activity } userIdentityNumberCopy.setOnClickListener { val userName = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) - val clipboardManager =getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipData = ClipData.newPlainText("text", userName) clipboardManager.setPrimaryClip(clipData) RxToast.info(resources.getString(R.string.CopyToClipboard)) } userIdentityNumber.setOnClickListener { val userName = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) - val clipboardManager =getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipData = ClipData.newPlainText("text", userName) clipboardManager.setPrimaryClip(clipData) RxToast.info(resources.getString(R.string.CopyToClipboard)) @@ -166,8 +172,9 @@ class MemberRechargeActivity: BaseActivity() { } } + @SuppressLint("SimpleDateFormat") - fun setExpireDate(validPeriod:Long) { + fun setExpireDate(validPeriod: Long) { val isExpire = checkExpire() var endTimeInfo = "--" if (isExpire) { @@ -240,11 +247,13 @@ class MemberRechargeActivity: BaseActivity() { Logger.d("获取充值续费信息") swipeLayout.isRefreshing = true } + HttpReqType.PAY -> { Logger.d("支付获取订单号") showLoadingDialog(resources.getString(R.string.GeneratingOrder)) } - else->{ + + else -> { } } @@ -257,7 +266,7 @@ class MemberRechargeActivity: BaseActivity() { swipeLayout.isRefreshing = false (httpStatus.rsp as RechargeInfoRsp).let { it -> if (it.code != "1000") { - RxToast.error(if (it.message.isNullOrBlank())resources.getString(R.string.FailedRechargeList)+ "(${it.code})" else "${it.code}:${it.message}") + RxToast.error(if (it.message.isNullOrBlank()) resources.getString(R.string.FailedRechargeList) + "(${it.code})" else "${it.code}:${it.message}") } else { it.data?.let { rechargeInfo -> initAdapter(rechargeInfo) @@ -265,14 +274,15 @@ class MemberRechargeActivity: BaseActivity() { } } } + HttpReqType.PAY -> { hideLoadingDialog() Logger.d("生成订单请求成功") (httpStatus.rsp as PayRsp).let { rsp -> if (rsp.code != "1000") { - RxToast.error(if (rsp.message.isNullOrBlank()) resources.getString(R.string.OrderGenerationFailed)+"(${rsp.code})" else "${rsp.code}:${rsp.message}") + RxToast.error(if (rsp.message.isNullOrBlank()) resources.getString(R.string.OrderGenerationFailed) + "(${rsp.code})" else "${rsp.code}:${rsp.message}") } else { - let { + let { outTradeNo = rsp.data?.outTradeNo!! val newUrl = rsp.data.payUrl.toString() orderPayment(rsp.data) @@ -281,10 +291,11 @@ class MemberRechargeActivity: BaseActivity() { } } } + HttpReqType.unionPay -> { (httpStatus.rsp as PayRsp).let { rsp -> if (rsp.code != "1000") { - RxToast.error(if (rsp.message.isNullOrBlank()) resources.getString(R.string.OrderGenerationFailed)+"(${rsp.code})" else "${rsp.code}:${rsp.message}") + RxToast.error(if (rsp.message.isNullOrBlank()) resources.getString(R.string.OrderGenerationFailed) + "(${rsp.code})" else "${rsp.code}:${rsp.message}") } else { if (rsp.data != null) { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(rsp.data.payUrl))) @@ -292,42 +303,46 @@ class MemberRechargeActivity: BaseActivity() { } } } - HttpReqType.StripeType ->{ + + HttpReqType.StripeType -> { (httpStatus.rsp as PayUploadParamData).let { - if(it.data!=null){ - paymentIntentClientSecret= it.data.clientSecret.toString() + if (it.data != null) { + paymentIntentClientSecret = it.data.clientSecret.toString() startCheckout() - }else{ + } else { hideLoading() RxToast.info(getString(R.string.NoPaymentReceipt)) } } } - HttpReqType.StripeWeChat ->{ + + HttpReqType.StripeWeChat -> { (httpStatus.rsp as PayUploadParamData).let { - if(it.data!=null){ - paymentIntentClientSecret= it.data.clientSecret.toString() + if (it.data != null) { + paymentIntentClientSecret = it.data.clientSecret.toString() val serviceUrl = MMKV.defaultMMKV().decodeString( BZYConstants.MMKV_KEY_CURRENT_HOST, BuildConfig.service_url )!! - val payUrl=serviceUrl+"/78zccgy0nsgknh9n4/iiii.html?s=${it.data.clientSecret.toString()}" + val payUrl = + serviceUrl + "/78zccgy0nsgknh9n4/iiii.html?s=${it.data.clientSecret.toString()}" val uri = Uri.parse(payUrl) val intent = Intent(Intent.ACTION_VIEW) intent.setData(uri) startActivity(intent) - }else{ + } else { hideLoading() RxToast.info(getString(R.string.NoPaymentReceipt)) } } } + HttpReqType.CHECK_LOGIN -> { Logger.d("支付成功更新有效期") val checkLoginRsp = httpStatus.rsp if (checkLoginRsp is CheckLoginRsp) { if (checkLoginRsp.code == 0) { - if(checkLoginRsp.data!=null){ + if (checkLoginRsp.data != null) { val vpnExpireTime = checkLoginRsp.data.vpnExpireTime setExpireDate(vpnExpireTime) } @@ -337,7 +352,8 @@ class MemberRechargeActivity: BaseActivity() { } } } - else->{ + + else -> { } } @@ -356,20 +372,23 @@ class MemberRechargeActivity: BaseActivity() { Logger.d("获取支付订单号异常${httpStatus.msg}") RxToast.error(resources.getString(R.string.DataPayNumber)) } + HttpReqType.unionPay -> { Logger.d("OrderGenerationFailed${httpStatus.msg}") RxToast.error(resources.getString(R.string.OrderGenerationFailed)) } + HttpReqType.StripeType -> { hideLoading() RxToast.error(resources.getString(R.string.NoPaymentReceipt)) } - else->{ + else -> { } } } + var dialogDoMainChoose: DialogDoMainChoose? = null private fun showDomainList() { try { @@ -392,6 +411,8 @@ class MemberRechargeActivity: BaseActivity() { val userId = MMKV.defaultMMKV() .decodeString(BZYConstants.MMKV_KEY_USER_ID)!! viewModel.getPhRechargeInfo(username, phToken, userId) + dialogDoMainChoose?.dismiss() + } override fun cancel() { @@ -399,6 +420,32 @@ class MemberRechargeActivity: BaseActivity() { dialogDoMainChoose?.dismiss() } }) + dialogDoMainChoose?.setOnListItemListener(object : + DialogDoMainChoose.OnListItemListener { + override fun listItemClick() { + MainApplication.restartApp(this@MemberRechargeActivity, object : + MainApplication.OnRestartButtonListener { + override fun enterButton() { + } + + override fun cancelButton() { + val username = MMKV.defaultMMKV() + .decodeString( + BZYConstants.MMKV_KEY_REMEMBER_USERNAME, + "" + )!! + val phToken = MMKV.defaultMMKV() + .decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")!! + val userId = MMKV.defaultMMKV() + .decodeString(BZYConstants.MMKV_KEY_USER_ID)!! + viewModel.getPhRechargeInfo(username, phToken, userId) + dialogDoMainChoose?.dismiss() + } + + }) + } + + }) dialogDoMainChoose?.show() } } @@ -409,6 +456,7 @@ class MemberRechargeActivity: BaseActivity() { e.printStackTrace() } } + private fun showLoadingDialog(content: String) { apply { if (!::rxDialogShapeLoading.isInitialized) { @@ -421,32 +469,37 @@ class MemberRechargeActivity: BaseActivity() { } } } + private fun hideLoadingDialog() { if (::rxDialogShapeLoading.isInitialized && rxDialogShapeLoading.isShowing) { rxDialogShapeLoading.cancel() } } + private fun initAdapter(recharge: RechargeInfo) { val rechargeListAdapter = VIPRechargeAdapter() - rvRechargeList.layoutManager =GridLayoutManager(CommonUtils.getApp(), 2, GridLayoutManager.VERTICAL, false) - rechargeListAdapter.setCloudServiceList(recharge,this@MemberRechargeActivity) - rvRechargeList.adapter=rechargeListAdapter - rechargeListAdapter.setListener(object :VIPRechargeAdapter.VIPRechargeItemClickListener{ + rvRechargeList.layoutManager = + GridLayoutManager(CommonUtils.getApp(), 2, GridLayoutManager.VERTICAL, false) + rechargeListAdapter.setCloudServiceList(recharge, this@MemberRechargeActivity) + rvRechargeList.adapter = rechargeListAdapter + rechargeListAdapter.setListener(object : VIPRechargeAdapter.VIPRechargeItemClickListener { @SuppressLint("SetTextI18n") override fun onItemClick(noteItem: Grade?) { - selectedGrade=noteItem + selectedGrade = noteItem val amount = selectedGrade!!.amount?.toFloat() if (amount != null) { - if(selectedGrade!!.id!=null){ + if (selectedGrade!!.id != null) { selectedGradeID = selectedGrade!!.id!! } val decimalFormat = DecimalFormat("#0.00") - tvPay.text =resources.getString(R.string.ActivateVIP)+"(¥${decimalFormat.format(amount)})" + tvPay.text = + resources.getString(R.string.ActivateVIP) + "(¥${decimalFormat.format(amount)})" } } }) } + @OptIn(DelicateCoroutinesApi::class) private fun startCheckout() { GlobalScope.launch { @@ -466,12 +519,14 @@ class MemberRechargeActivity: BaseActivity() { RxToast.info(getString(R.string.PaymentCompleted)) } } + StripeIntent.Status.RequiresAction -> { Log.i("startCheckout:RequiresAction") ThreadUtils.runOnUiThread { RxToast.info(getString(R.string.PaymentCancellation)) } } + else -> { Log.i("startCheckout status::${paymentIntent.status}") } @@ -487,9 +542,10 @@ class MemberRechargeActivity: BaseActivity() { ) } } + private fun orderPayment(data: Trade) { Log.i("PayGradeIDTEST:${selectedGradeID}") - if(data.payType!=null){ + if (data.payType != null) { let { MaterialDialog(it).show { this.cancelable(true) @@ -500,18 +556,19 @@ class MemberRechargeActivity: BaseActivity() { noVerticalPadding = true, horizontalPadding = false ) - for (index in data.payType!!){ - if(index.payModel==10){ - findViewById(R.id.llAggregatePayment).visibility= View.VISIBLE - val JUhePay=findViewById(R.id.JUhePay) - JUhePay.visibility= View.VISIBLE + for (index in data.payType!!) { + if (index.payModel == 10) { + findViewById(R.id.llAggregatePayment).visibility = + View.VISIBLE + val JUhePay = findViewById(R.id.JUhePay) + JUhePay.visibility = View.VISIBLE JUhePay.text = index.payChannel - if(index.payType==1){ - val ivAlipay =findViewById(R.id.ivAlipay) - ivAlipay.visibility= View.VISIBLE + if (index.payType == 1) { + val ivAlipay = findViewById(R.id.ivAlipay) + ivAlipay.visibility = View.VISIBLE ivAlipay.setOnClickListener { val payModel = index.payModel - val payType =index.payType + val payType = index.payType val payParam = index.payParam if (payModel != null && payType != null && payParam != null) { viewModel.orderPayment(data, payModel, payType, payParam) @@ -520,13 +577,13 @@ class MemberRechargeActivity: BaseActivity() { } } dismiss() - }else if(index.payType==2){ - val ivWechatPay= findViewById(R.id.ivWechatPay) - ivWechatPay.visibility= View.VISIBLE + } else if (index.payType == 2) { + val ivWechatPay = findViewById(R.id.ivWechatPay) + ivWechatPay.visibility = View.VISIBLE ivWechatPay.setOnClickListener { val payModel = index.payModel - val payType =index.payType - val payParam =index.payParam + val payType = index.payType + val payParam = index.payParam if (payModel != null && payType != null && payParam != null) { viewModel.orderPayment(data, payModel, payType, payParam) } else { @@ -536,18 +593,20 @@ class MemberRechargeActivity: BaseActivity() { } dismiss() } - }else if(index.payModel==11){ - findViewById(R.id.llQuickPayment).visibility= View.VISIBLE - val KJPay=findViewById(R.id.KJPay) - KJPay.visibility= View.VISIBLE + } else if (index.payModel == 11) { + findViewById(R.id.llQuickPayment).visibility = + View.VISIBLE + val KJPay = findViewById(R.id.KJPay) + KJPay.visibility = View.VISIBLE KJPay.text = index.payChannel - if(index.payType==1){ - val ivQuickPayment=findViewById(R.id.ivQuickPayment) - ivQuickPayment.visibility= View.VISIBLE + if (index.payType == 1) { + val ivQuickPayment = + findViewById(R.id.ivQuickPayment) + ivQuickPayment.visibility = View.VISIBLE ivQuickPayment.setOnClickListener { - val payModel =index.payModel - val payType =index.payType - val payParam =index.payParam + val payModel = index.payModel + val payType = index.payType + val payParam = index.payParam if (payModel != null && payType != null && payParam != null) { viewModel.orderPayment(data, payModel, payType, payParam) dismiss() @@ -556,13 +615,14 @@ class MemberRechargeActivity: BaseActivity() { dismiss() } } - }else if(index.payType==2){ - val ivQPWechatPay=findViewById(R.id.ivQPWechatPay) - ivQPWechatPay.visibility= View.VISIBLE + } else if (index.payType == 2) { + val ivQPWechatPay = + findViewById(R.id.ivQPWechatPay) + ivQPWechatPay.visibility = View.VISIBLE ivQPWechatPay.setOnClickListener { - val payModel =index.payModel - val payType =index.payType - val payParam =index.payParam + val payModel = index.payModel + val payType = index.payType + val payParam = index.payParam if (payModel != null && payType != null && payParam != null) { viewModel.orderPayment(data, payModel, payType, payParam) dismiss() @@ -572,54 +632,62 @@ class MemberRechargeActivity: BaseActivity() { } } } - }else if(index.payModel==7){ - findViewById(R.id.llStripe).visibility= View.VISIBLE - findViewById(R.id.llStripePay).visibility= View.VISIBLE - findViewById(R.id.tvStripePay).visibility= View.VISIBLE - findViewById(R.id.ivStripeAlipay).visibility= View.VISIBLE - val stripeWechatPay=findViewById(R.id.ivStripeWechatPay) - stripeWechatPay.visibility= View.VISIBLE + } else if (index.payModel == 7) { + findViewById(R.id.llStripe).visibility = View.VISIBLE + findViewById(R.id.llStripePay).visibility = View.VISIBLE + findViewById(R.id.tvStripePay).visibility = View.VISIBLE + findViewById(R.id.ivStripeAlipay).visibility = + View.VISIBLE + val stripeWechatPay = + findViewById(R.id.ivStripeWechatPay) + stripeWechatPay.visibility = View.VISIBLE findViewById(R.id.ivStripeAlipay).setOnClickListener { - if(selectedGradeID!=0){ - if(!PayUtils.isAlipayInstalled(this@MemberRechargeActivity)){ + if (selectedGradeID != 0) { + if (!PayUtils.isAlipayInstalled(this@MemberRechargeActivity)) { RxToast.error(resources.getString(R.string.AlipayNotInstalled)) return@setOnClickListener } dismiss() - showLoading(resources.getString(R.string.PaymentIn),this@MemberRechargeActivity) + showLoading( + resources.getString(R.string.PaymentIn), + this@MemberRechargeActivity + ) Log.i("PayGradeID:${selectedGradeID}") viewModel.getPayClientSecret(selectedGradeID) - }else{ + } else { dismiss() RxToast.info(getString(R.string.NoPaymentReceipt)) } } stripeWechatPay.setOnClickListener { - if(selectedGradeID!=0){ + if (selectedGradeID != 0) { viewModel.getWeChatPayClientSecret(selectedGradeID) dismiss() - }else{ + } else { dismiss() RxToast.info(getString(R.string.NoPaymentReceipt)) } } } } - getCustomView().findViewById(R.id.ivBack).setOnClickListener { - dismiss() - } + getCustomView().findViewById(R.id.ivBack) + .setOnClickListener { + dismiss() + } } } } } + override fun onRestart() { super.onRestart() android.util.Log.e("MemberRechargeActivity", " onRestart 重新可见之后刷新有效期") viewModel.checkLogin() } + override fun onBackPressed() { - val intent=Intent() - this.setResult(1000,intent) + val intent = Intent() + this.setResult(1000, intent) finish() // 关闭当前 Activity super.onBackPressed() } diff --git a/app/src/main/java/com/yingmao/clash/app/MainApplication.kt b/app/src/main/java/com/yingmao/clash/app/MainApplication.kt index 2114c8c..b5a01cc 100644 --- a/app/src/main/java/com/yingmao/clash/app/MainApplication.kt +++ b/app/src/main/java/com/yingmao/clash/app/MainApplication.kt @@ -1,6 +1,7 @@ package com.yingmao.clash.app import android.annotation.SuppressLint +import android.app.Activity import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager @@ -9,6 +10,7 @@ import android.content.Intent import android.content.res.Configuration import android.os.Build import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat.getString import androidx.core.content.getSystemService import com.cncat.vpn.common.Global import com.cncat.vpn.common.compat.currentProcessName @@ -16,6 +18,7 @@ import com.cncat.vpn.common.log.Log import com.github.gzuliyujiang.oaid.DeviceID import com.github.gzuliyujiang.oaid.DeviceIdentifier import com.github.gzuliyujiang.oaid.IGetter +import com.jakewharton.processphoenix.ProcessPhoenix import com.orhanobut.logger.Logger import com.stripe.android.PaymentConfiguration import com.tencent.mmkv.MMKV @@ -25,6 +28,12 @@ import com.yingmao.clash.conf.BZYConstants import com.yingmao.clash.entity.UserData import com.yingmao.clash.util.CrashCatchHandler import com.yingmao.clash.util.clashDir +import com.yingmao.clash.view.RxToast +import com.yingmao.clash.view.dialog.DialogDoMainChoose +import com.yingmao.clash.view.dialog.DialogSureRestartChoose +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.io.File import java.io.FileOutputStream import java.util.Locale @@ -53,8 +62,39 @@ class MainApplication : Application() { Log.d("setUserInfo") userBean = bean } - } + fun restartApp(activity: Activity,listener:OnRestartButtonListener) { + + var dialogDoMainChoose = DialogSureRestartChoose(activity) + if (!dialogDoMainChoose.isShowing) { + dialogDoMainChoose.setOnTryButtonListener(object : + DialogSureRestartChoose.OnTryButtonListener { + override fun tryButton() { + + MainScope().launch { + delay(500) + ProcessPhoenix.triggerRebirth(mApp) + } + listener.enterButton() + dialogDoMainChoose.dismiss() + + } + + override fun cancel() { + listener.cancelButton() + dialogDoMainChoose.dismiss() + } + }) + dialogDoMainChoose.show() + } + + } + + } + interface OnRestartButtonListener{ + fun enterButton() + fun cancelButton() + } override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) } @@ -86,7 +126,7 @@ class MainApplication : Application() { val crashCatchHandler: CrashCatchHandler = CrashCatchHandler.getInstance() //获得单例 crashCatchHandler.init(applicationContext) //初始化,传入context - if (DeviceID.supportedOAID(applicationContext)){ + if (DeviceID.supportedOAID(applicationContext)) { // 获取OAID/AAID,异步回调 DeviceID.getOAID(applicationContext, object : IGetter { override fun onOAIDGetComplete(result: String) { @@ -103,7 +143,6 @@ class MainApplication : Application() { } - //openinstall // OpenInstall.init(applicationContext); PaymentConfiguration.init( 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 ab8b63e..c2ae2f6 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 @@ -1,6 +1,8 @@ package com.yingmao.clash.fragment.setting import android.annotation.SuppressLint +import android.app.AlarmManager +import android.app.PendingIntent import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -9,6 +11,8 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment +import android.os.Handler +import android.os.Looper import android.provider.Settings import android.text.TextUtils import android.view.View @@ -39,6 +43,7 @@ import com.google.gson.Gson import com.hjq.permissions.OnPermissionCallback import com.hjq.permissions.Permission import com.hjq.permissions.XXPermissions +import com.jakewharton.processphoenix.ProcessPhoenix import com.orhanobut.logger.Logger import com.tencent.mmkv.MMKV import com.yingmao.clash.BuildConfig @@ -77,6 +82,7 @@ import com.yingmao.clash.view.RxToast import com.yingmao.clash.view.adapter.LanguageAdapter import com.yingmao.clash.view.dialog.DialogDoMainChoose import com.yingmao.clash.view.dialog.DialogNotice +import com.yingmao.clash.view.dialog.DialogSureRestartChoose import com.yingmao.clash.view.dialog.RxDialogShapeLoading import com.yingmao.clash.vm.MainAtyVM import com.yingmao.clash.webview.BZYWebViewKeFu @@ -91,36 +97,37 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import java.util.UUID +import kotlin.system.exitProcess -class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatchers.IO) { - private lateinit var clTopUp: ConstraintLayout - private lateinit var userIdentity: TextView - private lateinit var userIdentityNumber: TextView - private lateinit var expirationTime: TextView - private lateinit var userIdentityNumberCopy: TextView - private lateinit var clNotice:ConstraintLayout - private lateinit var clTermsService:ConstraintLayout - private lateinit var clPrivacyPolicy:ConstraintLayout - private lateinit var clShare:ConstraintLayout - private lateinit var clCustomerService:ConstraintLayout - private lateinit var clPreventLoss:ConstraintLayout - private lateinit var clExit:ConstraintLayout - private lateinit var clChangePsw:ConstraintLayout - private lateinit var initializationRepair:ConstraintLayout +class SettingFragment : BaseFragment(), CoroutineScope by CoroutineScope(Dispatchers.IO) { + private lateinit var clTopUp: ConstraintLayout + private lateinit var userIdentity: TextView + private lateinit var userIdentityNumber: TextView + private lateinit var expirationTime: TextView + private lateinit var userIdentityNumberCopy: TextView + private lateinit var clNotice: ConstraintLayout + private lateinit var clTermsService: ConstraintLayout + private lateinit var clPrivacyPolicy: ConstraintLayout + private lateinit var clShare: ConstraintLayout + private lateinit var clCustomerService: ConstraintLayout + private lateinit var clPreventLoss: ConstraintLayout + private lateinit var clExit: ConstraintLayout + private lateinit var clChangePsw: ConstraintLayout + private lateinit var initializationRepair: ConstraintLayout - private lateinit var clUploadErrorLog:ConstraintLayout + private lateinit var clUploadErrorLog: ConstraintLayout - private lateinit var clSubApplicationProxy:ConstraintLayout - private lateinit var clApplicationLanguage:ConstraintLayout - private lateinit var clTelegraph:ConstraintLayout - private lateinit var clFire:ConstraintLayout + private lateinit var clSubApplicationProxy: ConstraintLayout + private lateinit var clApplicationLanguage: ConstraintLayout + private lateinit var clTelegraph: ConstraintLayout + private lateinit var clFire: ConstraintLayout - private lateinit var tvExitLog:TextView + private lateinit var tvExitLog: TextView - private lateinit var tvSignIn:TextView + private lateinit var tvSignIn: TextView - private lateinit var tvVersion:TextView + private lateinit var tvVersion: TextView private var baseLoading: RxDialogShapeLoading? = null private val fragments = arrayListOf() private lateinit var mainAtyVM: MainAtyVM @@ -131,7 +138,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch backIv: ImageView, moreIv: TextView, titleTv: TextView, - container: FrameLayout + container: FrameLayout, ) { titleBarRl.visibility = View.GONE titleTv.visibility = View.GONE @@ -145,94 +152,126 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch @SuppressLint("SetTextI18n") private fun initLanguage(view: View) { userIdentityNumberCopy.text = requireActivity().resources.getString(R.string.copy) - view.findViewById(R.id.expirationTimeStr).text = requireActivity().resources.getString(R.string.expire_date) - view.findViewById(R.id.tvVip).text = requireActivity().resources.getString(R.string.vip_recharge) - view.findViewById(R.id.tvVipStr).text = requireActivity().resources.getString(R.string.RechargeDiscount) - view.findViewById(R.id.tvNotice).text = requireActivity().resources.getString(R.string.notice) - view.findViewById(R.id.tvNoticeStr).text = requireActivity().resources.getString(R.string.newNotice) + view.findViewById(R.id.expirationTimeStr).text = + requireActivity().resources.getString(R.string.expire_date) + view.findViewById(R.id.tvVip).text = + requireActivity().resources.getString(R.string.vip_recharge) + view.findViewById(R.id.tvVipStr).text = + requireActivity().resources.getString(R.string.RechargeDiscount) + view.findViewById(R.id.tvNotice).text = + requireActivity().resources.getString(R.string.notice) + view.findViewById(R.id.tvNoticeStr).text = + requireActivity().resources.getString(R.string.newNotice) - view.findViewById(R.id.tvChangePsw).text = requireActivity().resources.getString(R.string.change_password) - view.findViewById(R.id.tvChangePswStr).text = requireActivity().resources.getString(R.string.change_password) - view.findViewById(R.id.tvFDS).text = requireActivity().resources.getString(R.string.PreventLoss) - view.findViewById(R.id.tvFDSStr).text = requireActivity().resources.getString(R.string.PreventLoss) + view.findViewById(R.id.tvChangePsw).text = + requireActivity().resources.getString(R.string.change_password) + view.findViewById(R.id.tvChangePswStr).text = + requireActivity().resources.getString(R.string.change_password) + view.findViewById(R.id.tvFDS).text = + requireActivity().resources.getString(R.string.PreventLoss) + view.findViewById(R.id.tvFDSStr).text = + requireActivity().resources.getString(R.string.PreventLoss) - view.findViewById(R.id.tvShare).text = requireActivity().resources.getString(R.string.share) - view.findViewById(R.id.tvShareStr).text = requireActivity().resources.getString(R.string.SharingDuration) - view.findViewById(R.id.tvkefu).text = requireActivity().resources.getString(R.string.kefu) + view.findViewById(R.id.tvShare).text = + requireActivity().resources.getString(R.string.share) + view.findViewById(R.id.tvShareStr).text = + requireActivity().resources.getString(R.string.SharingDuration) + view.findViewById(R.id.tvkefu).text = + requireActivity().resources.getString(R.string.kefu) - view.findViewById(R.id.tvInit).text = requireActivity().resources.getString(R.string.InitializationRepair) - view.findViewById(R.id.tvInitStr).text = requireActivity().resources.getString(R.string.InitializationRepairStr) - view.findViewById(R.id.tvExit).text = requireActivity().resources.getString(R.string.exit) - view.findViewById(R.id.tvExitStr).text = requireActivity().resources.getString(R.string.exitTxt) + view.findViewById(R.id.tvInit).text = + requireActivity().resources.getString(R.string.InitializationRepair) + view.findViewById(R.id.tvInitStr).text = + requireActivity().resources.getString(R.string.InitializationRepairStr) + view.findViewById(R.id.tvExit).text = + requireActivity().resources.getString(R.string.exit) + view.findViewById(R.id.tvExitStr).text = + requireActivity().resources.getString(R.string.exitTxt) - view.findViewById(R.id.tvTermsService).text = requireActivity().resources.getString(R.string.ServiceTerms) - view.findViewById(R.id.tvTermsServiceStr).text = requireActivity().resources.getString(R.string.TermsOfService) - view.findViewById(R.id.tvPrivacyPolicy).text = requireActivity().resources.getString(R.string.PrivacyPolicy) - view.findViewById(R.id.tvPrivacyPolicyStr).text = requireActivity().resources.getString(R.string.PrivacyOfPolicy) + view.findViewById(R.id.tvTermsService).text = + requireActivity().resources.getString(R.string.ServiceTerms) + view.findViewById(R.id.tvTermsServiceStr).text = + requireActivity().resources.getString(R.string.TermsOfService) + view.findViewById(R.id.tvPrivacyPolicy).text = + requireActivity().resources.getString(R.string.PrivacyPolicy) + view.findViewById(R.id.tvPrivacyPolicyStr).text = + requireActivity().resources.getString(R.string.PrivacyOfPolicy) - view.findViewById(R.id.tvUploadErrorLog).text = requireActivity().resources.getString(R.string.UploadErrorLog) - view.findViewById(R.id.tvUploadErrorLogStr).text = requireActivity().resources.getString(R.string.UploadErrorLog) - view.findViewById(R.id.tvSubApplicationProxy).text = requireActivity().resources.getString(R.string.source_switch) - view.findViewById(R.id.tvSubApplicationProxyStr).text = requireActivity().resources.getString(R.string.source_switch_str) + view.findViewById(R.id.tvUploadErrorLog).text = + requireActivity().resources.getString(R.string.UploadErrorLog) + view.findViewById(R.id.tvUploadErrorLogStr).text = + requireActivity().resources.getString(R.string.UploadErrorLog) + view.findViewById(R.id.tvSubApplicationProxy).text = + requireActivity().resources.getString(R.string.source_switch) + view.findViewById(R.id.tvSubApplicationProxyStr).text = + requireActivity().resources.getString(R.string.source_switch_str) - view.findViewById(R.id.tvLanguageSwitching).text = requireActivity().resources.getString(R.string.LanguageSwitching) - view.findViewById(R.id.tvLanguageSwitchingStr).text = requireActivity().resources.getString(R.string.LanguageSwitchingStr) - view.findViewById(R.id.tvTelegram).text = requireActivity().resources.getString(R.string.telegram) - view.findViewById(R.id.tvTelegramStr).text = requireActivity().resources.getString(R.string.TelegramStr) + view.findViewById(R.id.tvLanguageSwitching).text = + requireActivity().resources.getString(R.string.LanguageSwitching) + view.findViewById(R.id.tvLanguageSwitchingStr).text = + requireActivity().resources.getString(R.string.LanguageSwitchingStr) + view.findViewById(R.id.tvTelegram).text = + requireActivity().resources.getString(R.string.telegram) + view.findViewById(R.id.tvTelegramStr).text = + requireActivity().resources.getString(R.string.TelegramStr) tvSignIn.text = requireActivity().resources.getString(R.string.SignIn) - tvExitLog.text=requireActivity().resources.getString(R.string.exit) - tvVersion.text=requireActivity().resources.getString(R.string.edition) + "${BuildConfig.VERSION_NAME}." + BuildConfig.productId + tvExitLog.text = requireActivity().resources.getString(R.string.exit) + tvVersion.text = + requireActivity().resources.getString(R.string.edition) + "${BuildConfig.VERSION_NAME}." + BuildConfig.productId } private fun initView(view: View) { - clTopUp=view.findViewById(R.id.clTopUp) - userIdentity=view.findViewById(R.id.userIdentity) - userIdentityNumber=view.findViewById(R.id.userIdentityNumber) - expirationTime=view.findViewById(R.id.expirationTime) - userIdentityNumberCopy=view.findViewById(R.id.userIdentityNumberCopy) - clNotice=view.findViewById(R.id.clNotice) - clTermsService=view.findViewById(R.id.clTermsService) - clPrivacyPolicy=view.findViewById(R.id.clPrivacyPolicy) - clShare=view.findViewById(R.id.clShare) - clCustomerService=view.findViewById(R.id.clCustomerService) - clPreventLoss=view.findViewById(R.id.clFDS) - clExit=view.findViewById(R.id.clExit) - clChangePsw=view.findViewById(R.id.clChangePsw) - initializationRepair=view.findViewById(R.id.clInitializationRepair) - tvSignIn=view.findViewById(R.id.tvSignIn) + clTopUp = view.findViewById(R.id.clTopUp) + userIdentity = view.findViewById(R.id.userIdentity) + userIdentityNumber = view.findViewById(R.id.userIdentityNumber) + expirationTime = view.findViewById(R.id.expirationTime) + userIdentityNumberCopy = view.findViewById(R.id.userIdentityNumberCopy) + clNotice = view.findViewById(R.id.clNotice) + clTermsService = view.findViewById(R.id.clTermsService) + clPrivacyPolicy = view.findViewById(R.id.clPrivacyPolicy) + clShare = view.findViewById(R.id.clShare) + clCustomerService = view.findViewById(R.id.clCustomerService) + clPreventLoss = view.findViewById(R.id.clFDS) + clExit = view.findViewById(R.id.clExit) + clChangePsw = view.findViewById(R.id.clChangePsw) + initializationRepair = view.findViewById(R.id.clInitializationRepair) + tvSignIn = view.findViewById(R.id.tvSignIn) - clUploadErrorLog=view.findViewById(R.id.clUploadErrorLog) - clSubApplicationProxy=view.findViewById(R.id.clSubApplicationProxy) - clApplicationLanguage=view.findViewById(R.id.clApplicationLanguage) - clTelegraph=view.findViewById(R.id.clTelegraph) - tvExitLog=view.findViewById(R.id.tvExitLog) - tvVersion=view.findViewById(R.id.tvVersion) - clFire=view.findViewById(R.id.clFire) + clUploadErrorLog = view.findViewById(R.id.clUploadErrorLog) + clSubApplicationProxy = view.findViewById(R.id.clSubApplicationProxy) + clApplicationLanguage = view.findViewById(R.id.clApplicationLanguage) + clTelegraph = view.findViewById(R.id.clTelegraph) + tvExitLog = view.findViewById(R.id.tvExitLog) + tvVersion = view.findViewById(R.id.tvVersion) + clFire = view.findViewById(R.id.clFire) setExpireDate() - if(checkExpire()){ - userIdentity.text=requireActivity().resources.getString(R.string.TemporaryUser) - }else{ - userIdentity.text=requireActivity().resources.getString(R.string.VIPMember) + if (checkExpire()) { + userIdentity.text = requireActivity().resources.getString(R.string.TemporaryUser) + } else { + userIdentity.text = requireActivity().resources.getString(R.string.VIPMember) } - userIdentityNumber.text = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) + userIdentityNumber.text = + MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) userIdentityNumber.setOnClickListener { val userName = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) - val clipboardManager = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipboardManager = + requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipData = ClipData.newPlainText("text", userName) clipboardManager.setPrimaryClip(clipData) RxToast.info(resources.getString(R.string.CopyToClipboard)) } userIdentityNumberCopy.setOnClickListener { val userName = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) - val clipboardManager = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipboardManager = + requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipData = ClipData.newPlainText("text", userName) clipboardManager.setPrimaryClip(clipData) RxToast.info(resources.getString(R.string.CopyToClipboard)) @@ -284,7 +323,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch startActivity(intent) } initializationRepair.setOnClickListener { - if (TextUtils.isEmpty(subUrl)){ + if (TextUtils.isEmpty(subUrl)) { return@setOnClickListener } showLoading(resources.getString(R.string.UnderRepair), requireActivity()) @@ -298,7 +337,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch delay(1000) if (subUrl?.isNotEmpty() == true) { startTimer() - setSub2(subUrl!!,pm) + setSub2(subUrl!!, pm) } } } @@ -308,9 +347,12 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch mainAtyVM.vipSign() } clUploadErrorLog.setOnClickListener { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Environment.isExternalStorageManager()) { - showLoading(resources.getString(R.string.UploadingInProgress), requireActivity()) + showLoading( + resources.getString(R.string.UploadingInProgress), + requireActivity() + ) // 已授予权限 val directory: File? = requireActivity().getExternalFilesDir("CrashLog") if (directory != null) { @@ -332,7 +374,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch startActivity(intent) hideLoading() } - }else{ + } else { checkPermission() } } @@ -438,43 +480,63 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch intent.setData(uri) startActivity(intent) } - tvExitLog.setOnClickListener { cleanUserInfo() } } - var dialogDoMainChoose:DialogDoMainChoose? =null + + + + + var dialogDoMainChoose: DialogDoMainChoose? = null private fun showDomainList() { try { SplashHelper(mainAtyVM).getHostWithFallback { success -> MainScope().launch { if (success) { - if (dialogDoMainChoose==null) { + if (dialogDoMainChoose == null) { dialogDoMainChoose = DialogDoMainChoose(activity) } - if (dialogDoMainChoose?.isShowing==false) { + if (dialogDoMainChoose?.isShowing == false) { dialogDoMainChoose?.setOnTryButtonListener(object : DialogDoMainChoose.OnTryButtonListener { override fun tryButton() { - RxToast.info(getString(R.string.StorageSuccessful)) + RxToast.info(getString(R.string.ModificationFailed)) dialogDoMainChoose?.dismiss() } override fun cancel() { - dialogDoMainChoose?.dismiss() } }) + dialogDoMainChoose?.setOnListItemListener(object : + DialogDoMainChoose.OnListItemListener { + override fun listItemClick() { + RxToast.info(getString(R.string.StorageSuccessful)) + + MainApplication.restartApp(requireActivity(),object:MainApplication.OnRestartButtonListener{ + + override fun enterButton() { + + } + + override fun cancelButton() { + dialogDoMainChoose?.dismiss() + } + }) + } + + }) dialogDoMainChoose?.show() } } } } - } catch (e: Exception) { e.printStackTrace() } } + @SuppressLint("SimpleDateFormat") fun setExpireDate() { val isExpire = checkExpire() @@ -547,6 +609,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } return true } + private fun cleanUserInfo() { MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_PH_TOKEN) MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_TOKEN) @@ -564,6 +627,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch startActivity(Intent(CommonUtils.getApp(), LoginActivity::class.java)) requireActivity().finish() } + private fun cleanProfile() { launch(Dispatchers.IO) { profileBinder?.let { pm -> @@ -576,18 +640,21 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } } } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) Logger.d("onActivityCreated") mainAtyVM = ViewModelProvider(this)[MainAtyVM::class.java] observe(this, mainAtyVM) } + private fun stopUpLoadTimer() { if (activity != null) { val uiMainActivity = activity as UIMainActivity uiMainActivity.stopUploadTimer() } } + private fun goNewRecharge() { if (activity != null) { val uiMainActivity = activity as UIMainActivity @@ -600,9 +667,9 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } override fun httpSuccess(httpStatus: HttpStatus.Success) { - when(httpStatus.reqType){ + when (httpStatus.reqType) { - HttpReqType.VIPSIGN->{ + HttpReqType.VIPSIGN -> { val data = httpStatus.rsp as VipSignRsp if (data.code == "1000") { var gifMins = 30 @@ -619,19 +686,22 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch RxToast.error(resources.getString(R.string.CheckFailed) + "${data.message}") } } - HttpReqType.CHECK_LOGIN->{ + + HttpReqType.CHECK_LOGIN -> { (httpStatus.rsp as CheckLoginRsp).let { checkLoginRsp -> if (checkLoginRsp.code == 0) { val mmkv = MMKV.defaultMMKV() - val oldExpireTime = mmkv.decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, 0) - if(checkLoginRsp.data!=null){ + val oldExpireTime = + mmkv.decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, 0) + if (checkLoginRsp.data != null) { val nowExpireTime = checkLoginRsp.data.vpnExpireTime if (nowExpireTime > oldExpireTime) { val expired = checkLoginRsp.data.expired mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true) mmkv.encode(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, nowExpireTime) setExpireDate() - fragments.filterIsInstance().forEach { it.setExpireDate() } + fragments.filterIsInstance() + .forEach { it.setExpireDate() } mainAtyVM.getAirNodes() RxToast.info(resources.getString(R.string.SuccessfullyUpdatedUserInformation)) } @@ -643,7 +713,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } } - HttpReqType.GET_AIRNODES->{ + HttpReqType.GET_AIRNODES -> { val data = httpStatus.rsp as SubscriptionRsp if (data.code != "1000") { RxToast.error(resources.getString(R.string.NodeAcquisitionFailed) + "${data.msg}") @@ -660,7 +730,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } } - HttpReqType.UploadParameters->{ + HttpReqType.UploadParameters -> { val upLoadData = httpStatus.rsp as UploadParamData if (upLoadData.code == 1000) { val directory: File? = requireActivity().getExternalFilesDir("CrashLog") @@ -685,35 +755,42 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } } - }else{ + } else { hideLoading() } } - else->{ + + else -> { } } } + override fun httpFailure(httpStatus: HttpStatus.Failure) { - when(httpStatus.reqType){ - HttpReqType.VIPSIGN->{ + when (httpStatus.reqType) { + HttpReqType.VIPSIGN -> { RxToast.error(resources.getString(R.string.CheckFailed) + httpStatus.msg) } - HttpReqType.GET_AIRNODES->{ + + HttpReqType.GET_AIRNODES -> { Logger.e("获取节点异常:${httpStatus.msg}") } - HttpReqType.CHECK_LOGIN->{ + + HttpReqType.CHECK_LOGIN -> { RxToast.error(resources.getString(R.string.QueryFailed)) } - HttpReqType.UploadParameters->{ + + HttpReqType.UploadParameters -> { hideLoading() } - else->{ + + else -> { } } } - private suspend fun setSub2(url: String) { + + private suspend fun setSub2(url: String) { profileBinder?.let { pm -> pm.queryAll().forEach { itemPro -> if (itemPro.name != Conf.getClashConfName(requireActivity().applicationContext)) { @@ -753,6 +830,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } } } + fun showLoading(str: String, context: Context) { val message = if (TextUtils.isEmpty(str)) "进行中..." else str if (baseLoading == null) { @@ -769,6 +847,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch baseLoading?.cancel() } } + private fun startTimer() { getNodeTimeOutNum = 5 timerJob = launch { @@ -780,7 +859,8 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch errInit() } } - private fun errInit(){ + + private fun errInit() { requireActivity().runOnUiThread { hideLoading() RxToast.error(resources.getString(R.string.FixTimeout)) @@ -801,7 +881,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch val actName = it.name Log.d("new main actname==>" + actName) pm.setActive(it) - newUuid = pro.uuid.toString(); + newUuid = pro.uuid.toString(); isSubed = true Log.d("setSub2 setActive end") finishInit() @@ -830,6 +910,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } } + private fun finishInit() { timerJob?.cancel() @@ -839,16 +920,20 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } } + private fun checkPermission() { XXPermissions.with(this) // 申请单个权限 .permission(Permission.MANAGE_EXTERNAL_STORAGE) .permission(Permission.Group.STORAGE) - .request(object: OnPermissionCallback { + .request(object : OnPermissionCallback { override fun onGranted(permissions: MutableList, allGranted: Boolean) { if (!allGranted) { - showLoading(resources.getString(R.string.UploadingInProgress), requireActivity()) - val directory: File? =requireActivity().getExternalFilesDir("CrashLog") + showLoading( + resources.getString(R.string.UploadingInProgress), + requireActivity() + ) + val directory: File? = requireActivity().getExternalFilesDir("CrashLog") if (directory != null) { val allFilePath = getAllFilePath(directory.path) if (allFilePath.isNotEmpty()) { @@ -864,6 +949,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch return } } + override fun onDenied(permissions: MutableList, doNotAskAgain: Boolean) { if (doNotAskAgain) { RxToast.error(resources.getString(R.string.ReadPermissionsDenied)) @@ -877,6 +963,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch } }) } + private fun showBottomDialog(data: List) { //1、使用Dialog、设置style val bottomSheetDialog = BottomSheetDialog(requireActivity(), R.style.BottomSheetDialog) @@ -887,35 +974,38 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch //设置点击dialog外部不消失 //bottomSheetDialog.setCanceledOnTouchOutside(false); bottomSheetDialog.show() - bottomView.findViewById(R.id.IVClose).setOnClickListener { bottomSheetDialog.dismiss() } + bottomView.findViewById(R.id.IVClose) + .setOnClickListener { bottomSheetDialog.dismiss() } val abRecharge = bottomSheetDialog.findViewById(R.id.RvLanguage) - abRecharge!!.layoutManager= LinearLayoutManager(requireActivity()) + abRecharge!!.layoutManager = LinearLayoutManager(requireActivity()) val languageAdapter = LanguageAdapter() languageAdapter.notifyAdapter(data, requireActivity()) - abRecharge.adapter=languageAdapter + abRecharge.adapter = languageAdapter val config = resources.configuration - languageAdapter.setListener(object: LanguageAdapter.onItemClickListener{ + languageAdapter.setListener(object : LanguageAdapter.onItemClickListener { override fun onItemClick(languageInfoList: List, isPosition: Int) { val languageItem = languageInfoList[isPosition] - if(!languageItem.isChoose){ - when(languageItem.id){ - 1->{ - languageItem.isChoose=true + if (!languageItem.isChoose) { + when (languageItem.id) { + 1 -> { + languageItem.isChoose = true config.locale = Locale.CHINA updateActivity("Chinese-CN", Locale.CHINA) RxToast.success("切换为简体中文!") } - 2->{ - languageItem.isChoose=true - config.locale = Locale("zh","HK") - updateActivity("Chinese-HK", Locale("zh","HK")) + + 2 -> { + languageItem.isChoose = true + config.locale = Locale("zh", "HK") + updateActivity("Chinese-HK", Locale("zh", "HK")) RxToast.success("切換爲中文繁體!") } - 3->{ - languageItem.isChoose=true + + 3 -> { + languageItem.isChoose = true config.locale = Locale.ENGLISH - updateActivity("English",Locale.ENGLISH) + updateActivity("English", Locale.ENGLISH) RxToast.success("Switch to English!") } } @@ -926,19 +1016,21 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch }) } - fun updateActivity(language:String,locale: Locale){ + + fun updateActivity(language: String, locale: Locale) { MMKV.defaultMMKV().encode(BZYConstants.languageInfo, language) - val resources =this.resources + val resources = this.resources val metrics = resources.displayMetrics val config = resources.configuration - config.locale=locale - resources.updateConfiguration(config,metrics) + config.locale = locale + resources.updateConfiguration(config, metrics) //设置好选择的语言以后,需要清除任务栈中的所有activity,打开首页,确保以后进入的页面都是当前选择的语言 val intent = Intent(requireActivity(), SplashActivity::class.java) intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) startActivity(intent) requireActivity().finish() } + private fun uploadForAliOss(data: ParamData, filePath: List, dirStr: String) { val endpoint = "oss-cn-hongkong.aliyuncs.com" val accessKeyId = data.credentials?.accessKeyId @@ -983,7 +1075,7 @@ class SettingFragment: BaseFragment(), CoroutineScope by CoroutineScope(Dispatch override fun onFailure( request: PutObjectRequest?, clientExcepion: ClientException, - serviceException: ServiceException + serviceException: ServiceException, ) { requireActivity().runOnUiThread { hideLoading() diff --git a/app/src/main/java/com/yingmao/clash/view/dialog/DialogDoMainChoose.kt b/app/src/main/java/com/yingmao/clash/view/dialog/DialogDoMainChoose.kt index 47c5451..7ea1289 100644 --- a/app/src/main/java/com/yingmao/clash/view/dialog/DialogDoMainChoose.kt +++ b/app/src/main/java/com/yingmao/clash/view/dialog/DialogDoMainChoose.kt @@ -41,6 +41,8 @@ open class DialogDoMainChoose : RxDialog { private set lateinit var onTryListener: OnTryButtonListener + lateinit var onDomainListItemListener: OnListItemListener + constructor(context: Context?, themeResId: Int) : super(context!!, themeResId) { initView() } @@ -110,6 +112,9 @@ open class DialogDoMainChoose : RxDialog { fun setOnTryButtonListener(listener: OnTryButtonListener) { this.onTryListener = listener } + fun setOnListItemListener(listener: OnListItemListener) { + this.onDomainListItemListener = listener + } var domainListAdapter: DomainListAdapter = DomainListAdapter(R.layout.domain_list_item_layout) private fun initAdapter() { @@ -134,7 +139,7 @@ open class DialogDoMainChoose : RxDialog { if (domainListAdapter.selectedPosition == position) -1 else position domainListAdapter.notifyItemChanged(position) - this.onTryListener.tryButton() + this.onDomainListItemListener.listItemClick() } } } @@ -148,5 +153,8 @@ open class DialogDoMainChoose : RxDialog { fun tryButton() fun cancel() } + interface OnListItemListener { + fun listItemClick() + } } \ No newline at end of file diff --git a/app/src/main/java/com/yingmao/clash/view/dialog/DialogSureRestartChoose.kt b/app/src/main/java/com/yingmao/clash/view/dialog/DialogSureRestartChoose.kt new file mode 100644 index 0000000..f856d5a --- /dev/null +++ b/app/src/main/java/com/yingmao/clash/view/dialog/DialogSureRestartChoose.kt @@ -0,0 +1,95 @@ +package com.yingmao.clash.view.dialog + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.chad.library.adapter.base.BaseQuickAdapter +import com.cncat.vpn.common.log.Log +import com.tencent.mmkv.MMKV + +import com.yingmao.clash.R +import com.yingmao.clash.act.UIMainActivity +import com.yingmao.clash.conf.BZYConstants +import com.yingmao.clash.entity.DomainItemBean + +import com.yingmao.clash.view.adapter.DomainListAdapter +import com.yingmao.clash.view.dialog.DialogDoMainChoose.OnTryButtonListener + + +/** + * @author tamsiree + * @date 2016/7/19 + * 确认 取消 Dialog + */ +open class DialogSureRestartChoose : RxDialog { + lateinit var titleView: TextView + private set + lateinit var but_try: TextView + private set + lateinit var but_cancel: TextView + private set + lateinit var domainRecyclerView: RecyclerView + private set + lateinit var onTryListener: OnTryButtonListener + + constructor(context: Context?, themeResId: Int) : super(context!!, themeResId) { + initView() + } + + constructor( + context: Context?, + cancelable: Boolean, + cancelListener: DialogInterface.OnCancelListener?, + ) : super(context!!, cancelable, cancelListener) { + initView() + } + + constructor(context: Context?) : super(context!!) { + initView() + } + + constructor(context: Activity?) : super(context!!) { + initView() + } + + constructor(context: Context?, alpha: Float, gravity: Int) : super(context, alpha, gravity) { + initView() + } + + fun setOnTryButtonListener(listener: OnTryButtonListener) { + this.onTryListener = listener + } + private fun initView() { + val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_app_restart_choose, null) + titleView = dialogView.findViewById(R.id.tv_title) + but_try = dialogView.findViewById(R.id.but_try) + but_cancel = dialogView.findViewById(R.id.but_cancel) + + but_try.setOnClickListener { + onTryListener.tryButton() + } + but_cancel.setOnClickListener { onTryListener.cancel() } + + setContentView(dialogView) + setCancelable(false) + window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + + + interface OnTryButtonListener { + fun tryButton() + fun cancel() + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_app_restart_choose.xml b/app/src/main/res/layout/dialog_app_restart_choose.xml new file mode 100644 index 0000000..2767b29 --- /dev/null +++ b/app/src/main/res/layout/dialog_app_restart_choose.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 238aeb2..254552f 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -3,7 +3,7 @@ 波斯猫回国加速器 影猫 影猫加速器 - 太子加速器 + 太子 file rule proxy @@ -348,4 +348,6 @@ Service Description\n 每天 Popular applications Popular Application Navigation + Suggest restarting + Restart \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 79e085e..46d33db 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -3,7 +3,7 @@ 波斯貓回國加速器 影貓 影貓加速器 - 太子加速器 + 太子 文件 規則 代理 @@ -338,4 +338,6 @@ 每天 熱門應用 熱門應用導航 + "建議重啓 " + "重啓 " \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9fb13a4..f1260d4 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -4,7 +4,7 @@ 影貓 影貓加速器 - 太子加速器 + 太子 文件 規則 代理 @@ -337,4 +337,6 @@ 每天 熱門應用 熱門應用導航 + "建議重啓 " + "重啓 " \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 12864bb..102b4a4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,7 +3,7 @@ 波斯猫回国加速器 影猫 影猫加速器 - 太子加速器 + 太子 文件 规则 代理 @@ -356,4 +356,6 @@ 每天 热门应用 热门应用导航 + 建议重启 + 重启 \ No newline at end of file