diff --git a/.gradle/8.13/executionHistory/executionHistory.bin b/.gradle/8.13/executionHistory/executionHistory.bin index 3c05a85..1d05c8a 100644 Binary files a/.gradle/8.13/executionHistory/executionHistory.bin and b/.gradle/8.13/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.13/executionHistory/executionHistory.lock b/.gradle/8.13/executionHistory/executionHistory.lock index 881a7e5..a7ed870 100644 Binary files a/.gradle/8.13/executionHistory/executionHistory.lock and b/.gradle/8.13/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.13/fileHashes/fileHashes.bin b/.gradle/8.13/fileHashes/fileHashes.bin index 6ff3adc..6c98b6e 100644 Binary files a/.gradle/8.13/fileHashes/fileHashes.bin and b/.gradle/8.13/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.13/fileHashes/fileHashes.lock b/.gradle/8.13/fileHashes/fileHashes.lock index 85e5057..b970a1d 100644 Binary files a/.gradle/8.13/fileHashes/fileHashes.lock and b/.gradle/8.13/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.13/fileHashes/resourceHashesCache.bin b/.gradle/8.13/fileHashes/resourceHashesCache.bin index d6e9e8d..e951736 100644 Binary files a/.gradle/8.13/fileHashes/resourceHashesCache.bin and b/.gradle/8.13/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 11ad109..1a76699 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index 7e500f0..484309e 100644 Binary files a/.gradle/file-system.probe and b/.gradle/file-system.probe differ diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 0000000..4d3d6d9 --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,2289 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2a45ead..09ff73e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,6 +53,9 @@ + () var pos = -1 var isAuto = false - var payType = "1" + var payType = "0" override fun onCreate(savedInstanceState: Bundle?) { binding = ActivityOpenMemberBinding.inflate(layoutInflater) setContentView(binding.root) @@ -220,7 +222,10 @@ class OpenMemberActivity : CKLHBaseActionBarActivity() { fun aliPay(orderStr : String){ GlobalScope.launch(Dispatchers.IO){ - + // [支付宝沙箱测试] 仅 Debug 包启用,Release 包继续使用正式环境。 + if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) { + EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX) + } val alipay = PayTask(this@OpenMemberActivity) val result = alipay.payV2(orderStr, true) diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/order/PayOrderActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/order/PayOrderActivity.kt index 116cdd8..25bd14e 100644 --- a/app/src/main/java/com/ck/ckcollar/app/ui/act/order/PayOrderActivity.kt +++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/order/PayOrderActivity.kt @@ -1,6 +1,7 @@ package com.ck.ckcollar.app.ui.act.order import android.content.Context +import android.content.pm.ApplicationInfo import android.os.Bundle import android.text.TextUtils import android.view.LayoutInflater @@ -8,6 +9,7 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.alipay.sdk.app.EnvUtils import com.alipay.sdk.app.PayTask import com.bumptech.glide.Glide import com.ck.ckcollar.app.CKApp @@ -55,7 +57,7 @@ class PayOrderActivity : CKBaseActivity() { var memberModel: UserInfoModel? = null - var payType = "1" + var payType = "0" var selectBalance = false var selectPoints = false @@ -83,9 +85,6 @@ class PayOrderActivity : CKBaseActivity() { binding.btnAli.setOnClickListener(payClick) binding.btnWX.setOnClickListener(payClick) - binding.btnBalance.setOnClickListener(payClick) - binding.btnPoints.setOnClickListener(payClick) - binding.btnBalance.setOnClickListener { @@ -102,6 +101,11 @@ class PayOrderActivity : CKBaseActivity() { } } binding.btnPoints.setOnClickListener { + if ((memberModel?.points ?: 0.0) <= 0.0) { + toast(getString(R.string.points_over)) + return@setOnClickListener + } + selectPoints = !selectPoints if (selectPoints){ binding.image4.setImageResource(R.mipmap.image_address_check_select) @@ -113,8 +117,7 @@ class PayOrderActivity : CKBaseActivity() { binding.btnBuyNow.setOnClickListener { - - if (selectBalance){ + if (selectBalance && !canPointsCoverPayAmount()){ getPayPassword() }else{ order("") @@ -171,7 +174,7 @@ class PayOrderActivity : CKBaseActivity() { if (TextUtils.isEmpty(payType) && memberModel?.balance == 0.0){ - if (!selectPoints || "1".equals(confirmOrderModel?.isPoint)){ + if (!selectPoints || "1".equals(confirmOrderModel?.isPoint.toString())){ toast(getString(R.string.balance_over)) return } @@ -207,10 +210,7 @@ class PayOrderActivity : CKBaseActivity() { var showBalance = BigDecimal.valueOf(model.payAmount).setScale(2, RoundingMode.HALF_UP) if (selectPoints){ - memberModel?.intoAmounts?.let { intoAmounts -> - val pointsDeduction = BigDecimal.valueOf(memberModel!!.points) - .multiply(BigDecimal(intoAmounts)) - .setScale(2, RoundingMode.HALF_UP) + calculatePointsDeduction()?.let { pointsDeduction -> showBalance = showBalance.subtract(pointsDeduction) .max(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)) } @@ -234,6 +234,11 @@ class PayOrderActivity : CKBaseActivity() { var payType = payType if(selectBalance){ + if (TextUtils.isEmpty(payType) && memberModel?.balance == 0.0){ + toast(getString(R.string.balance_over)) + return + } + if (TextUtils.isEmpty(payType)){ payType = "2" }else{ @@ -241,6 +246,11 @@ class PayOrderActivity : CKBaseActivity() { } } if(selectPoints){ + if (TextUtils.isEmpty(payType) && memberModel?.points == 0.0){ + toast(getString(R.string.points_over)) + return + } + if (TextUtils.isEmpty(payType)){ payType = "3" }else{ @@ -304,9 +314,28 @@ class PayOrderActivity : CKBaseActivity() { }) } - fun aliPay(orderStr: String) { - GlobalScope.launch(Dispatchers.IO) { + private fun canPointsCoverPayAmount(): Boolean { + if (!selectPoints) return false + val pointsDeduction = calculatePointsDeduction() ?: return false + val payAmount = BigDecimal.valueOf(model.payAmount).setScale(2, RoundingMode.HALF_UP) + return pointsDeduction >= payAmount + } + private fun calculatePointsDeduction(): BigDecimal? { + val member = memberModel ?: return null + val intoAmounts = member.intoAmounts?.toBigDecimalOrNull() ?: return null + return BigDecimal.valueOf(member.points) + .multiply(intoAmounts) + .setScale(2, RoundingMode.HALF_UP) + } + + fun aliPay(orderStr: String) { + + GlobalScope.launch(Dispatchers.IO) { + // [支付宝沙箱测试] 仅 Debug 包启用,Release 包继续使用正式环境。 + if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) { + EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX) + } val alipay = PayTask(this@PayOrderActivity) val result = alipay.payV2(orderStr, true) @@ -347,7 +376,7 @@ class PayOrderActivity : CKBaseActivity() { } override fun getResult(data: DataCollection, resultCode: Int) { - if (resultCode == 10001){ + if (resultCode == 10001){ showPayBottomView() } } diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/AddPetActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/AddPetActivity.kt index d8a34e9..2f16226 100644 --- a/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/AddPetActivity.kt +++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/petmanager/AddPetActivity.kt @@ -342,8 +342,8 @@ class AddPetActivity : CKBaseActivity() { data?.data?.let(::handleSelectedImage) ?: toast(getString(R.string.read_photo_failed)) }else if (requestCode == 555 && resultCode == RESULT_OK) { - var path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH,"") - uploadImage(path!!) + val path = PreferencesUtils.getString(this@AddPetActivity, Const.IMAGE_PATH, "") + compressAndUploadImage(path.orEmpty()) } binding.textPhotoNum.setText((this.data.size - 1 ).toString() + "/3") } @@ -389,6 +389,29 @@ class AddPetActivity : CKBaseActivity() { ) } + private fun compressAndUploadImage(path: String) { + if (path.isBlank()) { + toast(getString(R.string.read_photo_failed)) + return + } + + LubanCompressUtil.getCompressFile( + this, + File(path), + object : LubanCompressUtil.CompressCallback { + override fun success(file: File) { + if (isFinishing || isDestroyed) return + uploadImage(file.absolutePath) + } + + override fun fail(failMsg: String) { + if (isFinishing || isDestroyed) return + toast(failMsg) + } + } + ) + } + private fun copySelectedImageToCache(uri: Uri): String? { val extension = contentResolver.getType(uri) ?.let(MimeTypeMap.getSingleton()::getExtensionFromMimeType) diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/setting/SettingActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/setting/SettingActivity.kt index 1c424b3..b814526 100644 --- a/app/src/main/java/com/ck/ckcollar/app/ui/act/setting/SettingActivity.kt +++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/setting/SettingActivity.kt @@ -5,94 +5,66 @@ import android.os.Bundle import android.view.View import com.ck.ckcollar.app.CKApp import com.ck.ckcollar.app.R +import com.ck.ckcollar.app.config.Config import com.ck.ckcollar.app.databinding.ActivitySettingBinding +import com.ck.ckcollar.app.ui.act.home.MainActivity import com.ck.ckcollar.app.ui.act.login.AgreementActivity import com.ck.ckcollar.app.ui.act.login.LoginActivity -import com.ck.ckcollar.app.ui.act.home.MainActivity import com.ck.ckcollar.app.ui.base.CKBaseActivity import com.ck.ckcollar.app.ui.dialog.CKAlertDialog import com.tt.kit.model.Data import com.tt.kit.model.DataCollection class SettingActivity : CKBaseActivity(), View.OnClickListener { - lateinit var binding: ActivitySettingBinding + private lateinit var binding: ActivitySettingBinding + override fun onCreate(savedInstanceState: Bundle?) { binding = ActivitySettingBinding.inflate(layoutInflater) setContentView(binding.root) super.onCreate(savedInstanceState) - - setStatusBarColor(resources.getColor(R.color.white)) - setActionBarColor(resources.getColor(R.color.white)) - + setStatusBarColor(getColor(R.color.white)) + setActionBarColor(getColor(R.color.white)) setTitleText(getString(R.string.setting)) } override fun initView() { binding.btnLogout.visibility = if (CKApp.app.isUserLoggedIn) View.VISIBLE else View.GONE - - binding.contentLoginPassword.setOnClickListener(this) - binding.contentPaymentPassword.setOnClickListener(this) - binding.contentAboutUs.setOnClickListener(this) - binding.contentUserAgreement.setOnClickListener(this) - binding.contentPrivacyAgreement.setOnClickListener(this) - binding.contentShippingAddress.setOnClickListener(this) - binding.contentCancellation.setOnClickListener(this) - binding.btnLogout.setOnClickListener(this) + listOf(binding.contentLoginPassword, binding.contentPaymentPassword, binding.contentAboutUs, + binding.contentUserAgreement, binding.contentPrivacyAgreement, binding.contentShippingAddress, + binding.contentCancellation, binding.btnLogout).forEach { it.setOnClickListener(this) } } - override fun initData() { - } + override fun initData() = Unit override fun onClick(v: View?) { - - when (v?.id){ - R.id.contentLoginPassword->{ - if (!requireLogin()) return - toActivity(PasswordManagerActivity::class.java, DataCollection()) - } - R.id.contentPaymentPassword->{ - if (!requireLogin()) return - toActivity(PayPasswordManagerActivity::class.java, DataCollection()) - - } - R.id.contentAboutUs->{ - toActivity(AgreementActivity::class.java, DataCollection().apply { add(Data("title","关于我们")) }) - } - R.id.contentUserAgreement->{ - toActivity(AgreementActivity::class.java, DataCollection().apply { add(Data("title","用户协议")) }) - } - R.id.contentPrivacyAgreement->{ - toActivity(AgreementActivity::class.java, DataCollection().apply { add(Data("title","隐私协议")) }) - } - R.id.contentShippingAddress->{ - if (!requireLogin()) return - toActivity(AddressActivity::class.java, DataCollection().apply { add(Data("flag","1")) - }) - } - R.id.contentCancellation->{ - if (!requireLogin()) return - toActivity(AccountCancellationActivity::class.java, DataCollection()) - } - R.id.btnLogout->{ - CKAlertDialog(this@SettingActivity,getString(R.string.logout),getString(R.string.logout_alert),object : CKAlertDialog.AlertClickListener{ - override fun onBack(isConfirm: Boolean) { - if (isConfirm){ - CKApp.app.clearLoginState() - startActivity( - Intent(this@SettingActivity, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) - putExtra(MainActivity.EXTRA_SHOW_HOME, true) - } - ) - finish() - } + when (v?.id) { + R.id.contentLoginPassword -> if (requireLogin()) toActivity(PasswordManagerActivity::class.java, DataCollection()) + R.id.contentPaymentPassword -> if (requireLogin()) toActivity(PayPasswordManagerActivity::class.java, DataCollection()) + R.id.contentAboutUs -> toActivity(AgreementActivity::class.java, DataCollection().apply { add(Data("title", getString(R.string.about_us))) }) + R.id.contentUserAgreement -> toActivity(AgreementActivity::class.java, DataCollection().apply { add(Data("title", getString(R.string.user_agreement))) }) + R.id.contentPrivacyAgreement -> toActivity(SettingWebViewActivity::class.java, pageParams(Config.PRIVACY_POLICY, getString(R.string.privacy_agreement))) + R.id.contentShippingAddress -> if (requireLogin()) toActivity(AddressActivity::class.java, DataCollection().apply { add(Data("flag", "1")) }) + R.id.contentCancellation -> if (requireLogin()) toActivity(AccountCancellationActivity::class.java, DataCollection()) + R.id.btnLogout -> CKAlertDialog(this, getString(R.string.logout), getString(R.string.logout_alert), object : CKAlertDialog.AlertClickListener { + override fun onBack(isConfirm: Boolean) { + if (isConfirm) { + CKApp.app.clearLoginState() + startActivity(Intent(this@SettingActivity, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + putExtra(MainActivity.EXTRA_SHOW_HOME, true) + }) + finish() } - }).show() - - } + } + }).show() } } + private fun pageParams(url: String, title: String) = DataCollection().apply { + add(Data("url", url)) + add(Data("title", title)) + } + private fun requireLogin(): Boolean { if (CKApp.app.isUserLoggedIn) return true toActivity(LoginActivity::class.java, DataCollection()) diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/setting/SettingWebViewActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/setting/SettingWebViewActivity.kt new file mode 100644 index 0000000..e45e4a3 --- /dev/null +++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/setting/SettingWebViewActivity.kt @@ -0,0 +1,65 @@ +package com.ck.ckcollar.app.ui.act.setting + +import android.os.Bundle +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import android.net.Uri +import com.ck.ckcollar.app.R +import com.ck.ckcollar.app.databinding.ActivitySettingWebViewBinding +import com.ck.ckcollar.app.ui.base.CKBaseActivity + +/** Displays a setting document supplied by the caller. */ +class SettingWebViewActivity : CKBaseActivity() { + private lateinit var binding: ActivitySettingWebViewBinding + + override fun onCreate(savedInstanceState: Bundle?) { + binding = ActivitySettingWebViewBinding.inflate(layoutInflater) + setContentView(binding.root) + super.onCreate(savedInstanceState) + setActionBarColor(getColor(R.color.white)) + setStatusBarColor(getColor(R.color.white)) + } + + override fun initView() { + binding.webView.settings.apply { + javaScriptEnabled = true; domStorageEnabled = true + useWideViewPort = true; loadWithOverviewMode = true + setSupportZoom(true); builtInZoomControls = true; displayZoomControls = false + layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS + } + binding.webView.apply { + isFocusableInTouchMode = true + requestFocus() + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + // Do this after the page's anchor/default restoration has run. + view?.post { + view.scrollTo(0, 0) + view.evaluateJavascript("window.scrollTo(0, 0);", null) + } + view?.postDelayed({ view.scrollTo(0, 0) }, 300) + } + } + } + } + + override fun initData() { + val params = getReceiveParam() + setTitleText(params.get("title")?.stringValue?.toString().orEmpty()) + val url = params.get("url")?.stringValue?.toString().orEmpty() + if (url.isNotEmpty()) { + // Fragment anchors (for example #a6 in the privacy URL) override the + // initial position, while this screen always starts at the document top. + val topUrl = runCatching { Uri.parse(url).buildUpon().fragment(null).build().toString() } + .getOrDefault(url) + binding.webView.loadUrl(topUrl) + } + } + + override fun onDestroy() { + binding.webView.apply { stopLoading(); destroy() } + super.onDestroy() + } +} diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/shop/OrderActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/shop/OrderActivity.kt index cc12a50..6818710 100644 --- a/app/src/main/java/com/ck/ckcollar/app/ui/act/shop/OrderActivity.kt +++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/shop/OrderActivity.kt @@ -65,7 +65,7 @@ class OrderActivity : CKBaseActivity() { var memberModel: UserInfoModel? = null - var payType = "1" + var payType = "0" var selectBalance = false var selectPoints = false diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/user/UserInfoActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/user/UserInfoActivity.kt index b0ad97b..fa06bbc 100644 --- a/app/src/main/java/com/ck/ckcollar/app/ui/act/user/UserInfoActivity.kt +++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/user/UserInfoActivity.kt @@ -26,6 +26,7 @@ import com.ck.ckcollar.app.ui.dialog.CKEditNameDialog import com.ck.ckcollar.app.ui.dialog.CKEditPhoneDialog import com.ck.ckcollar.app.ui.dialog.CKPhotoDialog import com.ck.ckcollar.app.utils.Const +import com.ck.ckcollar.app.utils.IDCardValidate import com.ck.ckcollar.app.utils.networkone.ICallback import com.ck.ckcollar.app.utils.networkone.TTHttpClient import com.ck.ckcollar.app.utils.networkone.model.TTRequestData @@ -86,7 +87,7 @@ class UserInfoActivity: CKBaseActivity() { binding.textPhone.setOnClickListener { CKEditPhoneDialog( this@UserInfoActivity, - getString(R.string.bind_phone), + getString(R.string.modify_phone_number), object : CKEditPhoneDialog.AlertClickListener { override fun onBack(text: String) { binding.textPhone.text = text @@ -99,7 +100,12 @@ class UserInfoActivity: CKBaseActivity() { binding.textIdCard.setOnClickListener { CKEditDialog(this@UserInfoActivity,getString(R.string.id_card),binding.textIdCard.text.toString(),object : CKEditDialog.AlertClickListener{ override fun onBack(text: String) { - binding.textIdCard.text = text + val idCard = text.trim().uppercase() + if (!IDCardValidate.validateEffective(idCard)) { + toast("请输入正确的身份证号码") + return + } + binding.textIdCard.text = idCard save() } }).show() diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/act/welcome/IndexActivity.kt b/app/src/main/java/com/ck/ckcollar/app/ui/act/welcome/IndexActivity.kt index 0a2d08a..2f77772 100644 --- a/app/src/main/java/com/ck/ckcollar/app/ui/act/welcome/IndexActivity.kt +++ b/app/src/main/java/com/ck/ckcollar/app/ui/act/welcome/IndexActivity.kt @@ -4,9 +4,27 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.os.Message +import android.app.AlertDialog +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.text.SpannableString +import android.text.Spanned +import android.text.method.LinkMovementMethod +import android.text.style.ClickableSpan +import android.view.View +import android.view.Gravity +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.ScrollView import com.ck.ckcollar.app.CKApp +import com.ck.ckcollar.app.R import com.ck.ckcollar.app.databinding.ActivityIndexBinding import com.ck.ckcollar.app.ui.act.home.MainActivity +import com.ck.ckcollar.app.ui.act.login.AgreementActivity +import com.ck.ckcollar.app.ui.act.setting.SettingWebViewActivity +import com.ck.ckcollar.app.config.Config import com.ck.ckcollar.app.ui.base.CKBaseActivity import com.ck.ckcollar.app.utils.Const import com.cyclone.shadowsocks.utils.PreferencesUtils @@ -23,19 +41,89 @@ class IndexActivity : CKBaseActivity() { } var handler = object : Handler(Looper.getMainLooper()){ override fun handleMessage(msg: Message) { - if (!CKApp.app.isUserLoggedIn){ - var isFirst = PreferencesUtils.getBolean(this@IndexActivity, Const.FIRST,true) as Boolean - if (isFirst){ - toActivity(WelcomeActivity::class.java, DataCollection()) - }else{ - toActivity(MainActivity::class.java, DataCollection()) - } - }else{ - toActivity(MainActivity::class.java, DataCollection()) - } + continueAfterAgreement() + } + } + private fun continueAfterAgreement() { + val agreed = PreferencesUtils.getBolean(this, Const.AGREEMENT_CHECK, false) as Boolean + if (!agreed) { + showUserNotice() + return + } + val isFirst = PreferencesUtils.getBolean(this, Const.FIRST, true) as Boolean + toActivity(if (isFirst) WelcomeActivity::class.java else MainActivity::class.java, DataCollection()) + finish() + } - finish() + private fun showUserNotice() { + fun dp(value: Int) = (value * resources.displayMetrics.density).toInt() + val title = TextView(this).apply { + text = "用户须知" + setTextColor(Color.rgb(35, 39, 55)) + textSize = 20f + gravity = Gravity.CENTER + setTypeface(typeface, android.graphics.Typeface.BOLD) + } + val content = TextView(this).apply { + setTextColor(Color.rgb(95, 99, 115)) + textSize = 14f + setLineSpacing(0f, 1.35f) + movementMethod = LinkMovementMethod.getInstance() + val text = "感谢您使用宠盾!我们非常重视您的个人信息和隐私保护。请阅读并同意《用户协议》和《隐私协议》后继续使用。\n\n1. 为提供宠物管理、设备连接、消息通知和 AI 识别等服务,我们可能会收集必要的账号、设备及使用信息。\n\n2. 在您使用拍照、扫码、地图定位、蓝牙或相册等功能时,我们会在获得授权后申请相应权限;您可以随时在系统设置中关闭权限,但可能无法使用对应功能。\n\n3. 我们会采取合理的安全措施保护您的信息,不会将信息用于协议约定以外的用途,也不会向无关方出售或出租您的信息。\n\n4. 为保障服务稳定运行,部分功能可能由第三方服务提供商协助完成,我们会要求其按照法律法规和隐私政策保护您的信息。\n\n5. 当协议内容或服务发生重要变化时,我们会通过弹窗或其他适当方式提醒您。您可以通过应用内相关入口再次查看完整协议。" + val spannable = SpannableString(text) + val userStart = text.indexOf("《用户协议》") + val privacyStart = text.indexOf("《隐私协议》") + spannable.setSpan(object : ClickableSpan() { + override fun onClick(widget: View) = toActivity(AgreementActivity::class.java, DataCollection().apply { add(com.tt.kit.model.Data("title", getString(R.string.user_agreement))) }) + }, userStart, userStart + 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan(object : ClickableSpan() { + override fun onClick(widget: View) = toActivity(SettingWebViewActivity::class.java, DataCollection().apply { + add(com.tt.kit.model.Data("url", Config.PRIVACY_POLICY)); add(com.tt.kit.model.Data("title", getString(R.string.privacy_agreement))) + }) + }, privacyStart, privacyStart + 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + setText(spannable) + } + val contentScroll = ScrollView(this).apply { + isFillViewport = true + isVerticalScrollBarEnabled = false + addView(content, ViewGroup.LayoutParams(-1, -2)) + } + lateinit var dialog: AlertDialog + val buttons = LinearLayout(this).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER + } + fun action(text: String, backgroundColor: Int, textColor: Int, onClick: () -> Unit) = TextView(this).apply { + this.text = text; gravity = Gravity.CENTER; setTextColor(textColor); textSize = 15f + setTypeface(typeface, android.graphics.Typeface.BOLD); setPadding(0, dp(13), 0, dp(13)) + background = GradientDrawable().apply { setColor(backgroundColor); cornerRadius = dp(10).toFloat() } + setOnClickListener { onClick() } + } + val rejectButton = action("不同意", Color.rgb(245, 246, 250), Color.rgb(35, 39, 55), { + dialog.dismiss(); finishAndRemoveTask() + }) + val agreeButton = action("同意", getColor2(R.attr.buttonColor), Color.WHITE) { + PreferencesUtils.putBoolean(this@IndexActivity, Const.AGREEMENT_CHECK, true) + dialog.dismiss(); continueAfterAgreement() + } + buttons.addView(rejectButton, LinearLayout.LayoutParams(0, dp(52), 1f).apply { marginEnd = dp(7) }) + buttons.addView(agreeButton, LinearLayout.LayoutParams(0, dp(52), 1f).apply { marginStart = dp(7) }) + val panel = LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(24), dp(25), dp(24), dp(24)) + addView(title, LinearLayout.LayoutParams(-1, dp(40))) + addView(contentScroll, LinearLayout.LayoutParams(-1, dp(390)).apply { topMargin = dp(12); bottomMargin = dp(20) }) + addView(buttons, LinearLayout.LayoutParams(-1, dp(52))) + background = GradientDrawable().apply { setColor(Color.WHITE); cornerRadius = dp(22).toFloat() } + } + dialog = AlertDialog.Builder(this).setView(panel).setCancelable(false).create() + dialog.show() + dialog.window?.apply { + setBackgroundDrawableResource(android.R.color.transparent) + setDimAmount(0.55f) + setGravity(Gravity.CENTER) + setLayout((resources.displayMetrics.widthPixels * 0.82f).toInt(), WindowManager.LayoutParams.WRAP_CONTENT) } } diff --git a/app/src/main/java/com/ck/ckcollar/app/ui/dialog/CKEditPhoneDialog.kt b/app/src/main/java/com/ck/ckcollar/app/ui/dialog/CKEditPhoneDialog.kt index 64fd3dd..2c7dcd2 100644 --- a/app/src/main/java/com/ck/ckcollar/app/ui/dialog/CKEditPhoneDialog.kt +++ b/app/src/main/java/com/ck/ckcollar/app/ui/dialog/CKEditPhoneDialog.kt @@ -1,6 +1,7 @@ package com.ck.ckcollar.app.ui.dialog import android.content.Context +import android.content.res.ColorStateList import android.text.TextUtils import android.view.LayoutInflater import android.view.View @@ -33,8 +34,15 @@ class CKEditPhoneDialog( dialogAlertBinding.title.text = title } dialogAlertBinding.btnSendCode.setOnClickListener { sendCode() } + dialogAlertBinding.btnSendCode.setTextColor( + ColorStateList( + arrayOf(intArrayOf(-android.R.attr.state_enabled), intArrayOf()), + intArrayOf(mContext.getColor(R.color.textSecondColor), mContext.getColor(R.color.appStyle)) + ) + ) dialogAlertBinding.cancelButton.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View?) { + dialogAlertBinding.btnSendCode.stopCountDown() cancel() } }) @@ -72,7 +80,7 @@ class CKEditPhoneDialog( add(TTRequestData("smsCode",dialogAlertBinding.textCode.text.toString())) },object : ICallback{ override fun onSuccess(data: String) { - mContext.toast(mContext.getString(R.string.send_success)) + mContext.toast(mContext.getString(R.string.edit_success)) cancel() } @@ -94,7 +102,8 @@ class CKEditPhoneDialog( add(TTRequestData("scene","changePhone")) },object : ICallback{ override fun onSuccess(data: String) { - mContext.toast(mContext.getString(R.string.edit_success)) + mContext.toast(mContext.getString(R.string.verification_code_sent)) + dialogAlertBinding.btnSendCode.startCountDown() alertClickListener.onBack(dialogAlertBinding.textPhone.text.toString()) } @@ -108,4 +117,4 @@ class CKEditPhoneDialog( interface AlertClickListener { fun onBack(text : String) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/ck/ckcollar/app/utils/IDCardValidate.kt b/app/src/main/java/com/ck/ckcollar/app/utils/IDCardValidate.kt new file mode 100644 index 0000000..a50557d --- /dev/null +++ b/app/src/main/java/com/ck/ckcollar/app/utils/IDCardValidate.kt @@ -0,0 +1,81 @@ +package com.ck.ckcollar.app.utils + +import java.time.DateTimeException +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.format.ResolverStyle + +/** 校验中国大陆地区的 15 位和 18 位居民身份证号码。 */ +object IDCardValidate { + private val weights = intArrayOf(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2) + private val verifyCodes = charArrayOf('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2') + private val areaCodes = setOf( + "11", "12", "13", "14", "15", + "21", "22", "23", + "31", "32", "33", "34", "35", "36", "37", + "41", "42", "43", "44", "45", "46", + "50", "51", "52", "53", "54", + "61", "62", "63", "64", "65", + "71", "81", "82", "91" + ) + private val birthDateFormatter = DateTimeFormatter + .ofPattern("uuuuMMdd") + .withResolverStyle(ResolverStyle.STRICT) + private val earliestBirthDate = LocalDate.of(1900, 1, 1) + + @JvmStatic + fun validate_effective(idcardNumber: String?): Boolean = validateEffective(idcardNumber) + + /** + * 为兼容 Water 项目而保留,第二个参数不会影响校验结果。 + */ + @JvmStatic + @Suppress("UNUSED_PARAMETER") + fun validate_effective(idcardNumber: String?, isreturn_AutoCard: Boolean): Boolean = + validateEffective(idcardNumber) + + @JvmStatic + fun validateEffective(idCardNumber: String?): Boolean { + val source = idCardNumber?.trim()?.uppercase() ?: return false + val cardNumber = when (source.length) { + OLD_CARD_NUMBER_LENGTH -> convertToNewCardNumber(source) ?: return false + NEW_CARD_NUMBER_LENGTH -> source + else -> return false + } + + if (!cardNumber.take(17).all(Char::isDigit)) return false + if (cardNumber.last() != 'X' && !cardNumber.last().isDigit()) return false + if (cardNumber.substring(0, 2) !in areaCodes) return false + if (!hasValidBirthDate(cardNumber.substring(6, 14))) return false + return cardNumber.last() == calculateVerifyCode(cardNumber) + } + + private fun convertToNewCardNumber(oldCardNumber: String): String? { + if (!oldCardNumber.all(Char::isDigit)) return null + val firstSeventeen = buildString(17) { + append(oldCardNumber, 0, 6) + append("19") + append(oldCardNumber, 6, oldCardNumber.length) + } + return firstSeventeen + calculateVerifyCode(firstSeventeen) + } + + private fun hasValidBirthDate(value: String): Boolean { + val birthDate = try { + LocalDate.parse(value, birthDateFormatter) + } catch (_: DateTimeException) { + return false + } + return birthDate.isAfter(earliestBirthDate) && birthDate.isBefore(LocalDate.now()) + } + + private fun calculateVerifyCode(cardNumber: CharSequence): Char { + val sum = weights.indices.sumOf { index -> + (cardNumber[index] - '0') * weights[index] + } + return verifyCodes[sum % 11] + } + + private const val NEW_CARD_NUMBER_LENGTH = 18 + private const val OLD_CARD_NUMBER_LENGTH = 15 +} diff --git a/app/src/main/res/layout/activity_after_sales.xml b/app/src/main/res/layout/activity_after_sales.xml index a0016ce..778f821 100644 --- a/app/src/main/res/layout/activity_after_sales.xml +++ b/app/src/main/res/layout/activity_after_sales.xml @@ -102,7 +102,7 @@ android:text="@string/no_reason_text" android:background="@drawable/bg_cz_button" android:paddingHorizontal="5dp" - > + android:visibility="gone"/> - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_pay_order.xml b/app/src/main/res/layout/activity_pay_order.xml index 5b2d452..31dfec3 100644 --- a/app/src/main/res/layout/activity_pay_order.xml +++ b/app/src/main/res/layout/activity_pay_order.xml @@ -232,7 +232,7 @@ android:orientation="horizontal" android:gravity="center_vertical" android:layout_marginTop="10dp" - android:visibility="gone" + android:visibility="visible" > diff --git a/app/src/main/res/layout/activity_recharge.xml b/app/src/main/res/layout/activity_recharge.xml index ec3f2da..72d8db3 100644 --- a/app/src/main/res/layout/activity_recharge.xml +++ b/app/src/main/res/layout/activity_recharge.xml @@ -56,38 +56,6 @@ android:layout_marginBottom="10dp" > - - - - - - - + + + + + + + + + + + - - - - - - - - - + + + + + + + + + 取消成功 收货成功 绑定成功 + 修改手机号 下一步 立即体验 @@ -44,7 +45,7 @@ 进入平台(%sS) - (%s) 秒 + (%s) 秒后重发 发送验证码 登录 注册 @@ -505,6 +506,7 @@ 输入详细地址 选择标签 输入正确手机号 + 验证码已发送 注册成功 找回成功