package com.yingmao.clash.act import android.annotation.SuppressLint import android.content.Intent import android.content.res.ColorStateList import android.graphics.Color import android.net.Uri import android.os.Build import android.os.Bundle import android.text.Spannable import android.text.SpannableStringBuilder import android.text.TextUtils import android.text.method.HideReturnsTransformationMethod import android.text.method.LinkMovementMethod import android.util.Log import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.* import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.yingmao.clash.view.dialog.RxDialogShapeLoading import com.yingmao.clash.net.http.entity.LoginRsp 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 import com.yingmao.clash.listener.DownLoadListener import com.yingmao.clash.net.http.HttpReqType import com.yingmao.clash.net.http.HttpStatus import com.yingmao.clash.net.http.entity.AppVersionCheckRsp import com.yingmao.clash.util.AESUtil import com.yingmao.clash.util.CommonUtils import com.yingmao.clash.view.PasswordTransformationMethodBigDot import com.yingmao.clash.view.RxToast import com.yingmao.clash.view.dialog.DialogAppVersion import com.yingmao.clash.view.dialog.DialogDoMainChoose import com.yingmao.clash.vm.LoginAtyVM import com.yingmao.clash.vm.SplashAtyVM import com.yingmao.clash.webview.CustomerServiceWebView import kotlinx.coroutines.MainScope import kotlinx.coroutines.async import kotlinx.coroutines.launch import java.io.File import androidx.core.graphics.toColorInt import androidx.core.net.toUri import com.google.android.material.floatingactionbutton.FloatingActionButton class LoginActivity : BaseActivity(), View.OnClickListener { private lateinit var loginViewMode: LoginAtyVM private lateinit var splashAtyVM: SplashAtyVM private lateinit var usernameEt: EditText private lateinit var passwordEt: EditText private lateinit var rxDialogShapeLoading: RxDialogShapeLoading private lateinit var rememberPasswordCB: CheckBox private lateinit var userAgreementCB: CheckBox private lateinit var loginUserName: String private lateinit var tvRegister: TextView private lateinit var customService: FloatingActionButton private var usernameEncrypt = "" private var passwordEncrypt = "" private var userOAID = "" private var registerHost = "" companion object { private const val LOGIN_TO_REGISTER_REQUEST_CODE = 1 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.layout_activity_login) //点击输入框外边隐藏输入法 必须在setContentView之后调用 setupUI(findViewById(android.R.id.content).getChildAt(0)) loginViewMode = ViewModelProvider(this)[LoginAtyVM::class.java] splashAtyVM = ViewModelProvider(this)[SplashAtyVM::class.java] observe(this, loginViewMode) initViews() loginViewMode.upgradeVersion() } override fun httpLoading(httpStatus: HttpStatus.Loading) { super.httpLoading(httpStatus) if (httpStatus.reqType == HttpReqType.LOGIN) { showLoadingDialog(resources.getString(R.string.LoggingIn)) } else if (httpStatus.reqType == HttpReqType.CHECK_LOGIN) { showLoadingDialog(resources.getString(R.string.LoggingIn)) } } private fun showLoadingDialog(content: String) { if (!::rxDialogShapeLoading.isInitialized) { rxDialogShapeLoading = RxDialogShapeLoading(this) } rxDialogShapeLoading.setMessage(content) if (!rxDialogShapeLoading.isShowing) { rxDialogShapeLoading.show() } } private fun hideLoadingDialog() { if (::rxDialogShapeLoading.isInitialized && rxDialogShapeLoading.isShowing) { rxDialogShapeLoading.cancel() } } override fun httpSuccess(httpStatus: HttpStatus.Success) { super.httpSuccess(httpStatus) if (httpStatus.reqType == HttpReqType.LOGIN) { val rsp = httpStatus.rsp com.cncat.vpn.common.log.Log.d("HttpReqType.LOGIN rsp==>${rsp}") if (rsp is LoginRsp) { if (rsp.code == "1000") { val mmkv = MMKV.defaultMMKV() mmkv.encode(BZYConstants.MMKV_KEY_TOKEN, rsp.data.vpnToken) mmkv.encode(BZYConstants.MMKV_KEY_PH_TOKEN, rsp.data.phToken) mmkv.encode(BZYConstants.MMKV_KEY_USER_ID, rsp.data.userId) mmkv.encode(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, loginUserName) mmkv.encode(BZYConstants.MMKV_KEY_IS_REGISTER, true) //如果登录过一次也算注册过 loginViewMode.checkLogin() } else { hideLoadingDialog() RxToast.error( "(${rsp.code})${if (rsp.message.isNullOrEmpty()) resources.getString(R.string.LoginFailed) else rsp.message}", Toast.LENGTH_LONG ) } } } else if (httpStatus.reqType == HttpReqType.CHECK_LOGIN) { hideLoadingDialog() val rsp = httpStatus.rsp if (rsp is CheckLoginRsp) { if (rsp.code == 0) { rsp.data?.let { MainApplication.setUserInfo(it) } val expired = rsp.data?.expired val isPerfect = rsp.data?.isPerfect val userCommand = rsp.data?.userCommand val mmkv = MMKV.defaultMMKV() mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true) mmkv.encode( BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, rsp.data?.vpnExpireTime ?: 0 ) mmkv.encode(BZYConstants.MMKV_KEY_IS_PERFECT, isPerfect ?: true) mmkv.encode(BZYConstants.MMKV_KEY_VPN_UserCommand, userCommand ?: "") toMain() } else { hideLoadingDialog() RxToast.error( "(${rsp.code})${if (rsp.msg.isNullOrEmpty()) resources.getString(R.string.CheckLoginFailure) else rsp.msg}", Toast.LENGTH_LONG ) } } } else if (httpStatus.reqType == HttpReqType.GETAPPVERION) { (httpStatus.rsp as AppVersionCheckRsp).let { aver -> com.cncat.vpn.common.log.Log.i("appVersionCheck ==>$aver") if (aver.code == 200) { aver.data?.let { md -> if (md.isForce) { //强制更新 if (!TextUtils.isEmpty(md.versionPath)) { showUpdateDialog(md.versionPath!!, md.illustrate!!, true) } } else if (md.isUpgrade) { //更新 if (!TextUtils.isEmpty(md.versionPath)) { showUpdateDialog(md.versionPath!!, md.illustrate!!, false) } } } } } } } var dialogDoMainChoose: DialogDoMainChoose? = null override fun httpFailure(httpStatus: HttpStatus.Failure) { super.httpFailure(httpStatus) if (httpStatus.reqType == HttpReqType.LOGIN) { SplashHelper(splashAtyVM).getHostWithFallback { success -> MainScope().launch { if (success) { if (dialogDoMainChoose == null) { dialogDoMainChoose = DialogDoMainChoose(this@LoginActivity) } if (dialogDoMainChoose?.isShowing == false) { dialogDoMainChoose?.setOnTryButtonListener(object : DialogDoMainChoose.OnTryButtonListener { override fun tryButton() { MMKV.defaultMMKV() .decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, "") loginViewMode.login( usernameEncrypt, passwordEncrypt, userOAID ) } override fun cancel() { com.cncat.vpn.common.log.Log.d("tryButton CHECK_LOGIN cancel") dialogDoMainChoose?.dismiss() } }) dialogDoMainChoose?.setOnListItemListener(object : DialogDoMainChoose.OnListItemListener { override fun listItemClick() { loginViewMode.login( usernameEncrypt, passwordEncrypt, userOAID ) } }) dialogDoMainChoose?.show() } } else { if (::rxDialogShapeLoading.isInitialized && rxDialogShapeLoading.isShowing) { rxDialogShapeLoading.cancel() } RxToast.error(resources.getString(R.string.LoginException)) } } } } else if (httpStatus.reqType == HttpReqType.CHECK_LOGIN) { if (::rxDialogShapeLoading.isInitialized && rxDialogShapeLoading.isShowing) { rxDialogShapeLoading.cancel() } Log.e( "LoginActivity", resources.getString(R.string.LoginException) + httpStatus.msg ) RxToast.error(resources.getString(R.string.LoginException)) } } private fun toMain() { startActivity(Intent(this, MainActivity::class.java)) finish() } private fun initViews() { val bindAccount = intent.getStringExtra(BZYConstants.EXTRA_Bind_Account); var username = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) if (!TextUtils.isEmpty(bindAccount)) { username = bindAccount } val password = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_PASSWORD) usernameEt = findViewById(R.id.et_username) usernameEt.setText(username) passwordEt = findViewById(R.id.et_password) passwordEt.transformationMethod = PasswordTransformationMethodBigDot() passwordEt.setText(password) val isChecked = MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_REMEMBER_IS_CHECKED, true) rememberPasswordCB = findViewById(R.id.cb_remember_password) if (!isChecked) { rememberPasswordCB.setTextColor("#828282".toColorInt()) } else { rememberPasswordCB.setTextColor("#828282".toColorInt()) } rememberPasswordCB.isChecked = isChecked rememberPasswordCB.setOnCheckedChangeListener { _, isChecked -> if (!isChecked) { MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_REMEMBER_PASSWORD) MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_REMEMBER_IS_CHECKED, false) rememberPasswordCB.setTextColor("#4F4F4F".toColorInt()) } else { MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_REMEMBER_IS_CHECKED, true) rememberPasswordCB.setTextColor("#4F4F4F".toColorInt()) } } val userAgreementIsChecked = MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_USER_AGREEMENT_IS_CHECKED) userAgreementCB = findViewById(R.id.cb_user_agreement) if (!userAgreementIsChecked) { userAgreementCB.setTextColor("#828282".toColorInt()) } else { userAgreementCB.setTextColor("#4F4F4F".toColorInt()) } userAgreementCB.isChecked = userAgreementIsChecked userAgreementCB.setOnCheckedChangeListener { _, isChecked -> if (!isChecked) { MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_USER_AGREEMENT_IS_CHECKED, false) userAgreementCB.setTextColor("#828282".toColorInt()) } else { MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_USER_AGREEMENT_IS_CHECKED, true) userAgreementCB.setTextColor("#4F4F4F".toColorInt()) } } initUserAgreementView(userAgreementCB) val passwordHideCb = findViewById(R.id.cb_password_hide) passwordHideCb.setOnCheckedChangeListener(IOnCheckedChangeListener()) val loginTv = findViewById(R.id.tv_login) loginTv.setOnClickListener(this) val registerTv = findViewById(R.id.tv_register) registerTv.setOnClickListener(this) val forgetPwdTv = findViewById(R.id.tv_forget_pwd) forgetPwdTv.setOnClickListener(this) findViewById(R.id.tv_kefu).setOnClickListener(this) tvRegister = findViewById(R.id.tvRegister) tvRegister.setOnClickListener { val intent = Intent(applicationContext, RegisterActivity::class.java) startActivity(intent) finish() } customService=findViewById(R.id.customService) customService.setOnClickListener { startActivity(Intent(this@LoginActivity, CustomerServiceWebView::class.java)) } } private fun initUserAgreementView(userAgreementCB: CheckBox) { val config = resources.configuration val language = config.locale.language if (language.equals("en")) { val spannableStringBuilder = SpannableStringBuilder(resources.getString(R.string.AgreeAgreement)) val clickableSpan: MyClickableSpan = object : MyClickableSpan() { override fun onClick(view: View) { spannableStringBuilder.setSpan( null, 5, 24, Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) userAgreementCB.text = spannableStringBuilder userAgreementCB.isChecked=true val intent = Intent(this@LoginActivity, AgreementAndPrivacyActivity::class.java) intent.putExtra( BZYConstants.EXTRA_KEY_AGREEMENT, BZYConstants.EXTRA_PRIVACY_AGREEMENT ) startActivity(intent) } } spannableStringBuilder.setSpan(clickableSpan, 5, 24, Spannable.SPAN_EXCLUSIVE_INCLUSIVE) val privacyClickableSpan: MyClickableSpan = object : MyClickableSpan() { override fun onClick(view: View) { spannableStringBuilder.setSpan( null, 27, resources.getString(R.string.AgreeAgreement).length, Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) userAgreementCB.text = spannableStringBuilder userAgreementCB.isChecked=true val intent = Intent(this@LoginActivity, AgreementAndPrivacyActivity::class.java) intent.putExtra( BZYConstants.EXTRA_KEY_AGREEMENT, BZYConstants.EXTRA_USER_AGREEMENT ) startActivity(intent) } } spannableStringBuilder.setSpan( privacyClickableSpan, 27, resources.getString(R.string.AgreeAgreement).length, Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) userAgreementCB.text = spannableStringBuilder userAgreementCB.movementMethod = LinkMovementMethod.getInstance() } else { val spannableStringBuilder = SpannableStringBuilder(resources.getString(R.string.AgreeAgreement)) val clickableSpan: MyClickableSpan = object : MyClickableSpan() { override fun onClick(view: View) { spannableStringBuilder.setSpan( null, 8, 13, Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) userAgreementCB.text = spannableStringBuilder userAgreementCB.isChecked=true val intent = Intent(this@LoginActivity, AgreementAndPrivacyActivity::class.java) intent.putExtra( BZYConstants.EXTRA_KEY_AGREEMENT, BZYConstants.EXTRA_PRIVACY_AGREEMENT ) startActivity(intent) } } spannableStringBuilder.setSpan(clickableSpan, 8, 13, Spannable.SPAN_EXCLUSIVE_INCLUSIVE) val privacyClickableSpan: MyClickableSpan = object : MyClickableSpan() { override fun onClick(view: View) { spannableStringBuilder.setSpan( null, 14, resources.getString(R.string.AgreeAgreement).length, Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) userAgreementCB.text = spannableStringBuilder userAgreementCB.isChecked=true val intent = Intent(this@LoginActivity, AgreementAndPrivacyActivity::class.java) intent.putExtra( BZYConstants.EXTRA_KEY_AGREEMENT, BZYConstants.EXTRA_USER_AGREEMENT ) startActivity(intent) } } spannableStringBuilder.setSpan( privacyClickableSpan, 14, resources.getString(R.string.AgreeAgreement).length, Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) userAgreementCB.text = spannableStringBuilder userAgreementCB.movementMethod = LinkMovementMethod.getInstance() } } override fun onClick(v: View?) { if (v != null) { when (v.id) { R.id.tv_login -> { val username = usernameEt.text.toString().trim() if (username.isBlank()) { getFocusAndShowSoftInput(usernameEt) return } if (!CommonUtils.checkUsername(username)) { getFocusAndShowSoftInput(usernameEt) return } val password = passwordEt.text.toString().trim() if (password.isBlank()) { getFocusAndShowSoftInput(passwordEt) return } if (rememberPasswordCB.isChecked) { MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, username) MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_REMEMBER_PASSWORD, password) MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_REMEMBER_IS_CHECKED, true) } else { // MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_REMEMBER_USERNAME) MMKV.defaultMMKV() .removeValueForKey(BZYConstants.MMKV_KEY_REMEMBER_PASSWORD) MMKV.defaultMMKV().encode(BZYConstants.MMKV_KEY_REMEMBER_IS_CHECKED, false) } if (TextUtils.isEmpty(username)) { return } usernameEncrypt = AESUtil.byteArrayToHexStr( AESUtil.encrypt(username) ) passwordEncrypt = AESUtil.byteArrayToHexStr( AESUtil.encrypt(password) ) Log.d("OALD", "OAID.....::" + MainApplication.OAID) userOAID = AESUtil.byteArrayToHexStr( AESUtil.encrypt(MainApplication.OAID) ) loginUserName = username if (!userAgreementCB.isChecked) { RxToast.warning(resources.getString(R.string.ReadAgreement)) return } loginViewMode.login(usernameEncrypt, passwordEncrypt, userOAID) } R.id.tv_register -> { if (BuildConfig.is_free && BuildConfig.productId.isNotEmpty()) { val intent = Intent(applicationContext, SplashActivity::class.java) startActivity(intent) } else { if (TextUtils.isEmpty(registerHost)) { val intent = Intent(applicationContext, RegisterActivity::class.java) startActivity(intent) finish() } else { startActivity(Intent(Intent.ACTION_VIEW, registerHost.toUri())) } } } R.id.tv_kefu -> { startActivity(Intent(this@LoginActivity, CustomerServiceWebView::class.java)) } R.id.tv_forget_pwd -> { val intent = Intent(applicationContext, ForGetPwdActivity::class.java) startActivity(intent) } } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == LOGIN_TO_REGISTER_REQUEST_CODE) { data?.let { usernameEt.setText(it.extras?.getString("username")) passwordEt.setText(it.extras?.getString("password")) } } } private fun getFocusAndShowSoftInput(editText: EditText) { editText.requestFocus() val imm: InputMethodManager = editText.context .getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED) } private inner class IOnCheckedChangeListener : CompoundButton.OnCheckedChangeListener { override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { if (isChecked) { passwordEt.transformationMethod = HideReturnsTransformationMethod.getInstance() passwordEt.setSelection(passwordEt.text.length) } else { passwordEt.transformationMethod = PasswordTransformationMethodBigDot() passwordEt.setSelection(passwordEt.text.length) } } } private fun showUpdateDialog(url: String, illustrate: String, isForce: Boolean) { if (TextUtils.isEmpty(url)) { RxToast.error(resources.getString(R.string.downloadEmpty)) return } var notice = illustrate if (TextUtils.isEmpty(illustrate)) { notice = resources.getString(R.string.NewVersion) } var isDownloaded = false; var isFinish = false var downFilePath = "" val dialog = DialogAppVersion( this, notice, url, isForce, object : DialogAppVersion.OnClickListener { override fun ok(tv_down: TextView, pb_down: ProgressBar) { if (isFinish) { installApk(downFilePath) return } if (isDownloaded) { RxToast.info(resources.getString(R.string.downloading)) return } isDownloaded = true splashAtyVM.downLoadFile(url, object : DownLoadListener { @SuppressLint("SetTextI18n") override fun loading(progress: Int) { lifecycleScope.async { pb_down.progress = progress tv_down.text = resources.getString(R.string.download) + "$progress%" } } override fun finish(path: String) { com.cncat.vpn.common.log.Log.d("down finish path==>$path") isFinish = true downFilePath = path installApk(path) } override fun error(msg: String) { com.cncat.vpn.common.log.Log.d("down error msg==>$msg") } }) } override fun cancel() { } }) dialog.show() } fun installApk(filePath: String) { val file = File(filePath) val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { FileProvider.getUriForFile(this, "${applicationContext.packageName}.fileprovider", file) } else { Uri.fromFile(file) } val intent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(uri, "application/vnd.android.package-archive") flags = Intent.FLAG_ACTIVITY_NEW_TASK if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } } startActivity(intent) } }