From 851a06db1a9b8038d1135570ba8e661d6044f25e Mon Sep 17 00:00:00 2001 From: fengjignqi <15192412330@163.com> Date: Tue, 22 Sep 2026 14:57:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=97=E8=A1=A8=EF=BC=8C=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E4=B8=AD=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 + .../main/java/com/juhabi/birds/AppConfig.kt | 2 + .../com/juhabi/birds/base/BaseActivity.kt | 36 +++- .../com/juhabi/birds/base/BaseFragment.kt | 36 +++- .../com/juhabi/birds/mine/MineFragment.kt | 97 +++++++++- .../juhabi/birds/mine/UpdateUserAccount.kt | 131 ++++++++++++- .../com/juhabi/birds/mine/mvp/MineContract.kt | 22 +++ .../com/juhabi/birds/mine/mvp/MineImpl.kt | 175 ++++++++++++++++++ .../juhabi/birds/mine/mvp/MinePresenter.kt | 140 ++++++++++++++ .../com/juhabi/birds/network/ApiService.kt | 50 +++++ app/src/main/res/layout/dialog_input_text.xml | 1 - .../include_activity_back_gray_layout.xml | 2 +- .../main/res/layout/mine_center_layout.xml | 14 +- .../res/layout/permission_confirm_dialog.xml | 1 - .../res/layout/update_user_phone_layout.xml | 2 + .../main/res/mipmap-xhdpi/icon_back_white.png | Bin 0 -> 808 bytes .../res/mipmap-xxhdpi/icon_back_white.png | Bin 0 -> 1535 bytes .../res/mipmap-xxxhdpi/icon_back_white.png | Bin 0 -> 1264 bytes 18 files changed, 698 insertions(+), 13 deletions(-) create mode 100644 app/src/main/res/mipmap-xhdpi/icon_back_white.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_back_white.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icon_back_white.png diff --git a/app/build.gradle b/app/build.gradle index c52001f..7201666 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,4 +80,6 @@ dependencies { implementation "androidx.camera:camera-view:1.0.0-alpha25" //圆形视图库 implementation 'com.makeramen:roundedimageview:2.3.0' + //完整版引入 + implementation 'io.github.carguo:gsyvideoplayer:13.2.1' } diff --git a/app/src/main/java/com/juhabi/birds/AppConfig.kt b/app/src/main/java/com/juhabi/birds/AppConfig.kt index 8e711b8..b56aaa8 100644 --- a/app/src/main/java/com/juhabi/birds/AppConfig.kt +++ b/app/src/main/java/com/juhabi/birds/AppConfig.kt @@ -12,6 +12,8 @@ object AppConfig { const val SEND_CODE_SCENE_LOGIN = "login" //验证码场景 --重置密码 const val SEND_CODE_SCENE_RESET = "resetPwd" + //验证码场景 --更换手机号 + const val SEND_CODE_SCENE_REBIND = "rebind" @Volatile var userToken: String = "" diff --git a/app/src/main/java/com/juhabi/birds/base/BaseActivity.kt b/app/src/main/java/com/juhabi/birds/base/BaseActivity.kt index 29dc330..e041a30 100644 --- a/app/src/main/java/com/juhabi/birds/base/BaseActivity.kt +++ b/app/src/main/java/com/juhabi/birds/base/BaseActivity.kt @@ -276,7 +276,17 @@ abstract class BaseActivity> : } } dialog.setCanceledOnTouchOutside(touchOutsideCancel) - dialog.setOnShowListener { dialog.window?.setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) } + dialog.setOnShowListener { + dialog.window?.apply { + // 直接设置窗口宽度,确保屏幕左右各保留 50dp,避免 AlertDialog 默认宽度影响根布局 margin。 + setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) + setLayout( + (resources.displayMetrics.widthPixels - + 2 * (50 * resources.displayMetrics.density).toInt()), + WindowManager.LayoutParams.WRAP_CONTENT + ) + } + } dialog.show() } @@ -322,7 +332,17 @@ abstract class BaseActivity> : } } dialog.setCanceledOnTouchOutside(touchOutsideCancel) - dialog.setOnShowListener { dialog.window?.setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) } + dialog.setOnShowListener { + dialog.window?.apply { + // 固定窗口宽度,确保输入弹窗左右各保留 50dp。 + setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) + setLayout( + (resources.displayMetrics.widthPixels - + 2 * (50 * resources.displayMetrics.density).toInt()), + WindowManager.LayoutParams.WRAP_CONTENT + ) + } + } dialog.show() } @@ -370,7 +390,17 @@ abstract class BaseActivity> : val dialog = AlertDialog.Builder(this).setView(view).create() view.findViewById(R.id.permissionCancel).setOnClickListener { dialog.dismiss(); onCancel() } view.findViewById(R.id.permissionAllow).setOnClickListener { dialog.dismiss(); onAllow() } - dialog.setOnShowListener { dialog.window?.setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) } + dialog.setOnShowListener { + dialog.window?.apply { + // 动态权限确认弹窗固定左右各 50dp,避免使用 AlertDialog 默认宽度。 + setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) + setLayout( + (resources.displayMetrics.widthPixels - + 2 * (50 * resources.displayMetrics.density).toInt()), + WindowManager.LayoutParams.WRAP_CONTENT + ) + } + } dialog.show() } diff --git a/app/src/main/java/com/juhabi/birds/base/BaseFragment.kt b/app/src/main/java/com/juhabi/birds/base/BaseFragment.kt index 41b34fa..b092c14 100644 --- a/app/src/main/java/com/juhabi/birds/base/BaseFragment.kt +++ b/app/src/main/java/com/juhabi/birds/base/BaseFragment.kt @@ -276,7 +276,17 @@ abstract class BaseFragment> : Fragment(), I } } dialog.setCanceledOnTouchOutside(touchOutsideCancel) - dialog.setOnShowListener { dialog.window?.setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) } + dialog.setOnShowListener { + dialog.window?.apply { + // 直接设置窗口宽度,确保屏幕左右各保留 50dp,避免 AlertDialog 默认宽度影响根布局 margin。 + setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) + setLayout( + (resources.displayMetrics.widthPixels - + 2 * (50 * resources.displayMetrics.density).toInt()), + WindowManager.LayoutParams.WRAP_CONTENT + ) + } + } dialog.show() } @@ -322,7 +332,17 @@ abstract class BaseFragment> : Fragment(), I } } dialog.setCanceledOnTouchOutside(touchOutsideCancel) - dialog.setOnShowListener { dialog.window?.setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) } + dialog.setOnShowListener { + dialog.window?.apply { + // 固定窗口宽度,确保输入弹窗左右各保留 50dp。 + setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) + setLayout( + (resources.displayMetrics.widthPixels - + 2 * (50 * resources.displayMetrics.density).toInt()), + WindowManager.LayoutParams.WRAP_CONTENT + ) + } + } dialog.show() } @@ -368,7 +388,17 @@ abstract class BaseFragment> : Fragment(), I val dialog = AlertDialog.Builder(requireContext()).setView(view).create() view.findViewById(R.id.permissionCancel).setOnClickListener { dialog.dismiss(); onCancel() } view.findViewById(R.id.permissionAllow).setOnClickListener { dialog.dismiss(); onAllow() } - dialog.setOnShowListener { dialog.window?.setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) } + dialog.setOnShowListener { + dialog.window?.apply { + // 动态权限确认弹窗固定左右各 50dp,避免使用 AlertDialog 默认宽度。 + setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) + setLayout( + (resources.displayMetrics.widthPixels - + 2 * (50 * resources.displayMetrics.density).toInt()), + WindowManager.LayoutParams.WRAP_CONTENT + ) + } + } dialog.show() } /** 关闭并释放顶部权限提示弹窗。 */ diff --git a/app/src/main/java/com/juhabi/birds/mine/MineFragment.kt b/app/src/main/java/com/juhabi/birds/mine/MineFragment.kt index fe274f3..4c4dc95 100644 --- a/app/src/main/java/com/juhabi/birds/mine/MineFragment.kt +++ b/app/src/main/java/com/juhabi/birds/mine/MineFragment.kt @@ -4,6 +4,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import java.io.File +import java.util.Locale import com.juhabi.birds.base.BaseFragment import com.juhabi.birds.mine.model.MineUserInfo import com.juhabi.birds.R @@ -14,6 +16,8 @@ import com.juhabi.birds.mine.mvp.MineContract import com.juhabi.birds.mine.mvp.MineImpl import com.juhabi.birds.mine.mvp.MinePresenter import com.juhabi.birds.util.manager.UserInfoManager +import com.juhabi.birds.AppConfig +import com.juhabi.birds.login.LoginActivity /** * 个人中心 @@ -31,11 +35,13 @@ class MineFragment : BaseFragment(), override fun onResume() { super.onResume() + displayCacheSize() /** 页面恢复显示时重新请求当前用户的个人信息。 */ presenter?.getUserInfo() } override fun initView(savedInstanceState: Bundle?) { + displayCacheSize() listOf( binding.linearXgmm, binding.linearGhsj, @@ -62,6 +68,23 @@ class MineFragment : BaseFragment(), goTo(UpdateUserAccount::class.java, null) } + R.id.linear_qchc -> { + showDialogtext( + "提示", + "清除当前应用缓存?", + "确定", + "取消", + object : OnDialogClick { + override fun onCancelClick() { + } + + override fun onConfirmClick() { + clearCache() + } + } + ) + } + //退出登录 R.id.tv_logout -> { @@ -75,7 +98,8 @@ class MineFragment : BaseFragment(), } override fun onConfirmClick() { - // 退出登录接口后续接入 + // 用户确认后调用服务端退出登录接口。 + presenter?.userLogout() } } ) @@ -108,4 +132,75 @@ class MineFragment : BaseFragment(), override fun showError(message: String) { showMessage(message) } + + override fun onLogoutSuccess(message: String) { + val context = context ?: return + // 服务端退出成功后清除本地账号信息,避免应用重启后恢复旧登录状态。 + UserInfoManager.clearUser(context) + AppConfig.USER_LOGIN = false + showMessage(message) + + // 清空之前的页面栈,防止用户返回到已登录页面。 + goToClearTask(LoginActivity::class.java, null) + } + + /** 查询应用缓存大小,并将结果显示在个人中心页面。 */ + private fun displayCacheSize() { + val context = context ?: return + Thread { + val cacheSize = getFolderSize(context.cacheDir) + val sizeText = formatCacheSize(cacheSize) + activity?.runOnUiThread { + if (isAdded && view != null) { + binding.tvTotalCache.text = sizeText + } + } + }.start() + } + + /** 清理应用缓存,删除完成后重新创建缓存目录并刷新显示。 */ + private fun clearCache() { + val context = context ?: return + Thread { + deleteDir(context.cacheDir) + context.cacheDir.mkdirs() + activity?.runOnUiThread { + if (isAdded && view != null) { + binding.tvTotalCache.text = formatCacheSize(0L) + showMessage("缓存清理成功") + } + } + }.start() + } + + /** 递归计算目录下所有文件的字节数。 */ + private fun getFolderSize(directory: File?): Long { + if (directory == null || !directory.exists()) { + return 0L + } + if (directory.isFile) { + return directory.length() + } + return directory.listFiles()?.sumOf { file -> getFolderSize(file) } ?: 0L + } + + /** 递归删除文件或目录。 */ + private fun deleteDir(file: File?): Boolean { + if (file == null || !file.exists()) { + return true + } + if (file.isDirectory) { + file.listFiles()?.forEach { child -> deleteDir(child) } + } + return file.delete() + } + + /** 大于等于 1 MB 使用 MB,否则使用 KB,保持与旧 Java 页面一致。 */ + private fun formatCacheSize(bytes: Long): String { + return if (bytes >= 1024L * 1024L) { + String.format(Locale.US, "%.2f MB", bytes / (1024.0 * 1024.0)) + } else { + String.format(Locale.US, "%d KB", bytes / 1024L) + } + } } diff --git a/app/src/main/java/com/juhabi/birds/mine/UpdateUserAccount.kt b/app/src/main/java/com/juhabi/birds/mine/UpdateUserAccount.kt index f251edf..92c7db8 100644 --- a/app/src/main/java/com/juhabi/birds/mine/UpdateUserAccount.kt +++ b/app/src/main/java/com/juhabi/birds/mine/UpdateUserAccount.kt @@ -1,6 +1,7 @@ package com.juhabi.birds.mine import android.os.Bundle +import android.os.CountDownTimer import android.view.LayoutInflater import android.view.View import com.gyf.immersionbar.ImmersionBar @@ -14,12 +15,15 @@ import com.juhabi.birds.mine.mvp.MineContract import com.juhabi.birds.mine.mvp.MineImpl import com.juhabi.birds.mine.mvp.MinePresenter import com.juhabi.birds.util.manager.UserInfoManager +import com.juhabi.birds.util.ToastUtil /** * 修改手机号 */ class UpdateUserAccount : BaseActivity(), MineContract.MineView, View.OnClickListener { + private var codeCountDownTimer: CountDownTimer? = null + private var newPhoneCodeCountDownTimer: CountDownTimer? = null override fun createBinding(inflater: LayoutInflater): UpdateUserPhoneLayoutBinding { return UpdateUserPhoneLayoutBinding.inflate(inflater) } @@ -59,22 +63,132 @@ class UpdateUserAccount : BaseActivity{ - + // 新接口根据登录态识别当前手机号,不需要传手机号或验证码场景。 + presenter?.getCurrentPhoneCode() } R.id.tv_confirmAccount ->{ + // 只有填写旧手机号收到的验证码后,才能进入新手机号填写页面。 + val confirmCode = binding.EtConfirmCode.text.toString().trim() + if (confirmCode.isBlank()) { + ToastUtil.showShort(mContext, "验证码不能为空") + return + } + + // 服务端校验通过后,再进入新手机号填写页面。 + presenter?.verifyOldPhone(confirmCode) } R.id.tv_new_phone_getcode ->{ + // 新手机号不能为空,校验通过后使用 rebind 场景发送验证码。 + val newPhone = binding.etNewPhone.text.toString().trim() + if (newPhone.isBlank()) { + ToastUtil.showShort(mContext, "新手机号不能为空") + return + } + presenter?.sendNewPhoneCode(newPhone) } R.id.tv_submit_new_account ->{ + //绑定新的手机号码 + val newPhone = binding.etNewPhone.text.toString().trim() + val code = binding.etNewPhoneCode.text.toString().trim() + if (newPhone.isBlank()) { + ToastUtil.showShort(mContext, "新手机号不能为空") + return + } + if (code.isBlank()) { + ToastUtil.showShort(mContext, "验证码不能为空") + return + } + + // 手机号和验证码校验通过后,提交新手机号换绑请求。 + presenter?.updateBindNewPhone(newPhone, code) } } } override fun showError(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + } + + override fun sendCodeSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + codeCountDownTimer?.cancel() + binding.tvConfirmGetcode.apply { + isEnabled = false + isClickable = false + setBackgroundResource(R.drawable.shape_register_code_disabled_bg) + text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) + } + codeCountDownTimer = object : CountDownTimer(CODE_COUNTDOWN_MILLIS, 1_000L) { + override fun onTick(millisUntilFinished: Long) { + val secondsRemaining = (millisUntilFinished + 999L) / 1_000L + binding.tvConfirmGetcode.text = getString( + R.string.register_code_countdown, + secondsRemaining + ) + } + + override fun onFinish() { + codeCountDownTimer = null + binding.tvConfirmGetcode.apply { + isEnabled = true + isClickable = true + setBackgroundResource(R.drawable.shape_register_code_bg) + setText(R.string.register_get_code) + } + } + }.start() + } + + override fun onVerifyOldPhoneSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + // 当前手机号验证成功后,隐藏验证区域并展示新手机号输入区域。 + binding.linearConfirmUserAccount.visibility = View.GONE + binding.linearNewUserAccount.visibility = View.VISIBLE + } + + override fun newPhoneCodeSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + newPhoneCodeCountDownTimer?.cancel() + binding.tvNewPhoneGetcode.apply { + isEnabled = false + isClickable = false + setBackgroundResource(R.drawable.shape_register_code_disabled_bg) + text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) + } + newPhoneCodeCountDownTimer = object : CountDownTimer(CODE_COUNTDOWN_MILLIS, 1_000L) { + override fun onTick(millisUntilFinished: Long) { + val secondsRemaining = (millisUntilFinished + 999L) / 1_000L + binding.tvNewPhoneGetcode.text = getString( + R.string.register_code_countdown, + secondsRemaining + ) + } + + override fun onFinish() { + newPhoneCodeCountDownTimer = null + binding.tvNewPhoneGetcode.apply { + isEnabled = true + isClickable = true + setBackgroundResource(R.drawable.shape_register_code_bg) + setText(R.string.register_get_code) + } + } + }.start() + } + + override fun onUpdateBindNewPhoneSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + // 返回个人中心后,MineFragment 会在 onResume 中重新获取最新用户资料。 + finish() } override fun getUserInfoSuccess(userInfo: MineUserInfo) { @@ -83,5 +197,18 @@ class UpdateUserAccount : BaseActivity { @@ -17,6 +20,15 @@ interface MineContract { callback: Callback ) fun modifyPassword(oldPassword: String, newPassword: String, callback: Callback) + fun getCurrentPhoneCode(callback: Callback>) + fun sendPhoneCode(request: JSONObject, callback: Callback>) + fun verifyOldPhone(oldCode: String, callback: Callback>) + fun updateBindNewPhone( + newPhoneNumber: String, + code: String, + callback: Callback> + ) + fun userLogout(callback: Callback>) fun cancel() } @@ -25,5 +37,15 @@ interface MineContract { fun onVersionChecked(versionInfo: AppVersionInfo) /** 修改密码成功回调,非修改密码页面无需处理。 */ fun onModifyPasswordSuccess(message: String) {} + /** 发送验证码成功回调。 */ + fun sendCodeSuccess(message: String) {} + /** 新手机号验证码发送成功回调。 */ + fun newPhoneCodeSuccess(message: String) {} + /** 当前手机号验证成功回调。 */ + fun onVerifyOldPhoneSuccess(message: String) {} + /** 新手机号绑定成功回调。 */ + fun onUpdateBindNewPhoneSuccess(message: String) {} + /** 退出登录成功回调。 */ + fun onLogoutSuccess(message: String) {} } } diff --git a/app/src/main/java/com/juhabi/birds/mine/mvp/MineImpl.kt b/app/src/main/java/com/juhabi/birds/mine/mvp/MineImpl.kt index 931e0f3..16836a3 100644 --- a/app/src/main/java/com/juhabi/birds/mine/mvp/MineImpl.kt +++ b/app/src/main/java/com/juhabi/birds/mine/mvp/MineImpl.kt @@ -11,12 +11,177 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import org.json.JSONObject +import com.juhabi.birds.login.model.SendCodeEntity class MineImpl : MineContract.DataModel { private val mainHandler = Handler(Looper.getMainLooper()) private var userInfoCall: Call>? = null private var versionCall: Call>? = null private var modifyPasswordCall: Call>? = null + private var sendCodeCall: Call>? = null + private var verifyOldPhoneCall: Call>? = null + private var newPhoneCodeCall: Call>? = null + private var updateBindNewPhoneCall: Call>? = null + private var logoutCall: Call>? = null + + override fun getCurrentPhoneCode( + callback: MineContract.Callback> + ) { + sendCodeCall?.cancel() + sendCodeCall = ApiClient.apiService + .getCurrentPhoneCode(NetworkManager.getToken()) + .also { call -> + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + mainHandler.post { + response.body()?.let(callback::success) + ?: callback.fail("验证码发送失败") + } + } + + override fun onFailure( + call: Call>, + error: Throwable + ) { + if (!call.isCanceled) { + mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + } + } + }) + } + } + + override fun sendPhoneCode( + request: JSONObject, + callback: MineContract.Callback> + ) { + newPhoneCodeCall?.cancel() + newPhoneCodeCall = ApiClient.apiService + .sendPhoneCode(NetworkManager.getToken(), NetworkManager.toRequestBody(request)) + .also { call -> + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + mainHandler.post { + response.body()?.let(callback::success) + ?: callback.fail("验证码发送失败") + } + } + + override fun onFailure( + call: Call>, + error: Throwable + ) { + if (!call.isCanceled) { + mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + } + } + }) + } + } + + override fun verifyOldPhone( + oldCode: String, + callback: MineContract.Callback> + ) { + verifyOldPhoneCall?.cancel() + val requestBody = JSONObject().apply { + put("oldCode", oldCode.trim()) + } + verifyOldPhoneCall = ApiClient.apiService + .verifyOldPhone(NetworkManager.getToken(), NetworkManager.toRequestBody(requestBody)) + .also { call -> + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + mainHandler.post { + response.body()?.let(callback::success) + ?: callback.fail("当前手机号验证失败") + } + } + + override fun onFailure(call: Call>, error: Throwable) { + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + } + + override fun updateBindNewPhone( + newPhoneNumber: String, + code: String, + callback: MineContract.Callback> + ) { + updateBindNewPhoneCall?.cancel() + val requestBody = JSONObject().apply { + put("newPhoneNumber", newPhoneNumber.trim()) + put("code", code.trim()) + } + updateBindNewPhoneCall = ApiClient.apiService + .updateBindNewPhone( + NetworkManager.getToken(), + NetworkManager.toRequestBody(requestBody) + ) + .also { call -> + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + mainHandler.post { + response.body()?.let(callback::success) + ?: callback.fail("手机号更换失败") + } + } + + override fun onFailure(call: Call>, error: Throwable) { + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + } + + override fun userLogout(callback: MineContract.Callback>) { + logoutCall?.cancel() + logoutCall = ApiClient.apiService + .userLogout(NetworkManager.getToken()) + .also { call -> + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + mainHandler.post { + response.body()?.let(callback::success) + ?: callback.fail("退出登录失败") + } + } + + override fun onFailure(call: Call>, error: Throwable) { + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + } override fun getUserInfo( params: Map, @@ -145,6 +310,16 @@ class MineImpl : MineContract.DataModel { versionCall = null modifyPasswordCall?.cancel() modifyPasswordCall = null + sendCodeCall?.cancel() + sendCodeCall = null + verifyOldPhoneCall?.cancel() + verifyOldPhoneCall = null + newPhoneCodeCall?.cancel() + newPhoneCodeCall = null + updateBindNewPhoneCall?.cancel() + updateBindNewPhoneCall = null + logoutCall?.cancel() + logoutCall = null mainHandler.removeCallbacksAndMessages(null) } } diff --git a/app/src/main/java/com/juhabi/birds/mine/mvp/MinePresenter.kt b/app/src/main/java/com/juhabi/birds/mine/mvp/MinePresenter.kt index b383613..c902a7b 100644 --- a/app/src/main/java/com/juhabi/birds/mine/mvp/MinePresenter.kt +++ b/app/src/main/java/com/juhabi/birds/mine/mvp/MinePresenter.kt @@ -3,9 +3,149 @@ package com.juhabi.birds.mine.mvp import com.juhabi.birds.base.BasePresenter import com.juhabi.birds.main.model.AppVersionInfo import com.juhabi.birds.mine.model.MineUserInfo +import com.juhabi.birds.network.BaseBean +import com.juhabi.birds.login.model.SendCodeEntity +import org.json.JSONObject class MinePresenter(private val mineImpl: MineImpl) : BasePresenter() { + /** 通过登录态向当前绑定的手机号发送验证码,无需额外请求参数。 */ + fun getCurrentPhoneCode() { + val currentView = view ?: return + currentView.showLoading() + mineImpl.getCurrentPhoneCode(object : MineContract.Callback> { + override fun success(data: BaseBean) { + view?.apply { + hideLoading() + if (data.code == 0 || data.code == 200) { + sendCodeSuccess(data.message.ifBlank { "验证码已发送" }) + } else { + showError(data.message.ifBlank { "验证码发送失败" }) + } + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + }) + } + + /** 向新手机号发送 rebind 场景验证码。 */ + fun sendNewPhoneCode(phoneNumber: String) { + val currentView = view ?: return + currentView.showLoading() + val request = JSONObject().apply { + put("phoneNumber", phoneNumber.trim()) + put("scene", com.juhabi.birds.AppConfig.SEND_CODE_SCENE_REBIND) + } + mineImpl.sendPhoneCode(request, object : MineContract.Callback> { + override fun success(data: BaseBean) { + view?.apply { + hideLoading() + if (data.code == 0 || data.code == 200) { + newPhoneCodeSuccess(data.message.ifBlank { "验证码已发送" }) + } else { + showError(data.message.ifBlank { "验证码发送失败" }) + } + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + }) + } + + /** 校验当前手机号收到的验证码。 */ + fun verifyOldPhone(oldCode: String) { + val currentView = view ?: return + currentView.showLoading() + mineImpl.verifyOldPhone(oldCode, object : MineContract.Callback> { + override fun success(data: BaseBean) { + view?.apply { + hideLoading() + if (data.code == 0 || data.code == 200) { + onVerifyOldPhoneSuccess( + data.message.ifBlank { "当前手机号验证成功" } + ) + } else { + showError(data.message.ifBlank { "当前手机号验证失败" }) + } + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + }) + } + + /** 提交新手机号和验证码,完成手机号换绑。 */ + fun updateBindNewPhone(newPhoneNumber: String, code: String) { + val currentView = view ?: return + currentView.showLoading() + mineImpl.updateBindNewPhone( + newPhoneNumber, + code, + object : MineContract.Callback> { + override fun success(data: BaseBean) { + view?.apply { + hideLoading() + if (data.code == 0 || data.code == 200) { + onUpdateBindNewPhoneSuccess( + data.message.ifBlank { "手机号更换成功" } + ) + } else { + showError(data.message.ifBlank { "手机号更换失败" }) + } + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + ) + } + + /** 调用服务端退出登录接口,并统一分发成功或失败结果。 */ + fun userLogout() { + val currentView = view ?: return + currentView.showLoading() + mineImpl.userLogout(object : MineContract.Callback> { + override fun success(data: BaseBean) { + view?.apply { + hideLoading() + if (data.code == 0 || data.code == 200) { + onLogoutSuccess(data.message.ifBlank { "退出登录成功" }) + } else { + showError(data.message.ifBlank { "退出登录失败" }) + } + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + }) + } + fun getUserInfo() { val currentView = view ?: return currentView.showLoading() diff --git a/app/src/main/java/com/juhabi/birds/network/ApiService.kt b/app/src/main/java/com/juhabi/birds/network/ApiService.kt index e661577..c2856c8 100644 --- a/app/src/main/java/com/juhabi/birds/network/ApiService.kt +++ b/app/src/main/java/com/juhabi/birds/network/ApiService.kt @@ -23,6 +23,14 @@ interface ApiService { //发送手机验证码 + /** + * 入参 + * { + * "phoneNumber": "15550099523", + * "scene": "场景值" + * } + * + */ @POST("app/auth/sms/code") fun sendPhoneCode(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> @@ -194,6 +202,48 @@ interface ApiService { fun modifyPassword(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + /** + * 验证原手机号--发送短信 + * + * 不需要任何入参, + */ + @POST("app/user/phone/old-code") + fun getCurrentPhoneCode(@HeaderMap headers: Map): Call> + + + /** + * 验证原手机号--验证手机号 + * + * 入参 + *{ + * "oldCode": "123456" + * } + * + */ + @POST("app/user/phone/verify-old") + fun verifyOldPhone(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + + /** + * 更换新的绑定手机号码 + { + "newPhoneNumber": "string", + "code": "string" + } + */ + @PUT("app/user/phone") + fun updateBindNewPhone(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + + /** + * 退出登录 + * + * 不需要任何入参, + */ + @POST("app/auth/logout") + fun userLogout(@HeaderMap headers: Map): Call> + + @GET("app/v1/checkVersion") diff --git a/app/src/main/res/layout/dialog_input_text.xml b/app/src/main/res/layout/dialog_input_text.xml index ef6ccce..efdcca0 100644 --- a/app/src/main/res/layout/dialog_input_text.xml +++ b/app/src/main/res/layout/dialog_input_text.xml @@ -3,7 +3,6 @@ android:id="@+id/contentDialog" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginHorizontal="36dp" android:background="@drawable/bg_confirm_dialog" android:orientation="vertical"> diff --git a/app/src/main/res/layout/include_activity_back_gray_layout.xml b/app/src/main/res/layout/include_activity_back_gray_layout.xml index a9603c9..da0ea52 100644 --- a/app/src/main/res/layout/include_activity_back_gray_layout.xml +++ b/app/src/main/res/layout/include_activity_back_gray_layout.xml @@ -12,7 +12,7 @@ android:layout_width="22dp" android:layout_height="match_parent" android:layout_centerVertical="true" - android:src="@mipmap/img_back_black" + android:src="@mipmap/icon_back_white" android:layout_marginStart="10dp"/> + + diff --git a/app/src/main/res/layout/update_user_phone_layout.xml b/app/src/main/res/layout/update_user_phone_layout.xml index fc55a45..5d4afca 100644 --- a/app/src/main/res/layout/update_user_phone_layout.xml +++ b/app/src/main/res/layout/update_user_phone_layout.xml @@ -23,6 +23,7 @@ android:id="@+id/linear_confirm_user_account" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="20dp" android:orientation="vertical" android:visibility="visible"> 4e-7>4&Gg@uKMg`I+e{sFO2A%dc!f}(}?3WA0H06|48MFks)7FJp)rcj6=Xc5uU zQc06QiWGq~DN+P3&ztPRW^ZTjX7-y|0vEpT=5}Up_IYMzFSE(6Y={4h1-hQV@eG8{ zK)>I=NY0TfD3ntOC$l)J3$TaeTUvRV! zt&S=X8(wzN1ct!>3(2+S|8Q-=FHxF= zsQ~cLp?W~p#VnO)8F@2!`pe7&a`^k|v?c|A0r=ILwpLZG1puEQr=W1b&(NlkH;1R+ zb~6CDwgm6#NSiT=fe`#vMt*Ie#i$~921@YP9W2q|b=@EV;I()EOb4C9bA6vBq|>vk z1b=C(X5cl+$QM@)nE`!Oo-aD6lpi^cy5{MBTZ9O>mW`gUZpOS}w~^-uAp)+i!=v=M zk4zdN01b%Rj>`!UnEx;0a2Vj%LZzK+IPLl8z5ET-7{B!Mj4sy=VR1!e~0A@%$ zrF3l{l1T&$056G`l$}bn5&*uC&|YX=$CWH*;!vgD?69?fNLWs9DQ}Q2iCON6Bea`` ziGZk(_(bWF&{wN&Y;y@S0pPWqByolJb=&7c;sotxVJaXhX5uSllf(?m$xOmr0C>&B zH%gbp9@@+#q6h#uio|zj;+)QHlhFhO#d2zz2|W}|x0Q%0kU}E5KuO{Z@9VbW#Y`Nh z-8+&9h>GR(i&9T=E(zD>kdL_|nSc;V63aTSr2bb{sTzJ5N1)-lxx`6Mss4#kkEB%l zMREb*A@QH`82N_$Lf)b78Krl0A|OhD7s=b?W%9M^Lb5Y~5ZJoUj0NgOC1u?P00030 m|5D*YZU6uP21!IgR09CiZr)*d6B^Y300005kU_w7a@ul^`sX)sfZ%Ry$PTRpa_A;@FasKfFcAU!=22ae!qVpx&gh5en8vMX=wLIyt5we93UVZ zOq6${ThQ_7Aapu<7F~w2TBiV2a2|ocC1@`+Z2PgX8lNL+vsq^V5%>{A{Qx=xjXQ3$ z=@`bd*|;5L+d+s^!;;`A1qSfPmxmjkgc66S8n# zBy^mV4MgalGdn)g(yvGU!Z03B3gPHRl+2b~w z&0AxDfHPB8E?tBo^&I1gcoASe4>>k5Ry!cTEI^A1=1~-bgG=un{#$`QK&@M?fB@tC zt^m4*d{`t}Gv`fw&LVR@MXi+;$1pGh7;NP10LLc`7x#KSuX9{z4^1ze7jc*djMqDK z%dlDi0mpf#32_68!NKMKvbvJaXQ(JMkgNv;95d%aCQX53<`h>}9Q(ks0s@YYV!UqI z%pPKyBb*}3ien#GHbB5JbCxsndgL=InxOe>Rb*KWA9XfBOnM5s86AK^_78(Ep`w1R zIG&M~6VO%cAy!txc@DoNrTtoQOd~BPpp!@LVb>4eVsjO6zM<`e}5#Chdw;9QGR)^5zC ztw36YiY~WofB@42k|*J1^lfQ^=Bp}q;BhW0*0KUJ$_mmF`Yx5y1kHDTHj_o(ngku& z8q5yJK!D_G;HC6aUPUpJHUVj8x$(tPtQJ592PD^%T_{UgY!_0IL2`{}CH>ctugKAiH8aU_JD-yC=69@PQv2cavCLNB?2C~Q-V7l#DV}J~C2+}sTW03a8XFZUXqt?o;Q9uOj#iW_! z#iWm(|Matfgmf$8%Yd{eZR6HBAS0|YX*GTC-(v4IABx{y$dYRuQY(N6)=c_|_D1v+ zia|1yyzpF0ChdcNW7ZlVBP5f4qrC#T$R8^=v&Z*?)&R*qU#s6*1!Q;?q_y-@%57i# zu0xHLTkC)b)^huWHnrT$9{=p=PBJO>fPiV|XGH)qgesF#%WV(7<1LJr0V$7|aG#qM z1IWX#Af=X@4=H^Ml?$xNE2)Q-hBn-vYnD1jsx zR{^};h?U#kd?)rM8!qWqJRn1-VA5StU0_r~i00960 lyu0^_00006Nkl~_)6nLYahi45adsS{HQonFb#CZ*ZLh1M;e9OnU9>t>Qjt> z7W~5=xKvMHdeY3cxCjA)4^Y0@eegG6KW`?M9%nB=@Tu#62;@-sO4^J`+f4anEnp4* zE&$N)fOUda$yUGw_|GDFuP_<16fglk&~FY74v@g~;*p(ziSVxl|8{ZetqigfAoy20 zwvakst2MyCX^cPF2w21CYBmI66!`3D76QD&Hx@v4^M_~n&~RM>yu)`bAP4wv1x$p0 zsZ&k!vmIdX3U^h|MEK`|Z+f1;3wnE=D*-dWH@n5qhTJvnMnI165#vHYuJBz55d81j z(U2=#Zm1or&QGq|(eZ4Dq)UL{b6@9TySKw9fPX6ZzlN_8CuOn%*6?|nHI__L@cVW? zNdcw6M^17AN`jx9fC=#b=wN7Yldto&pKpdFN`Sy0DxS1wA$30RX>b{96TXJ%0tB8e zAoSUxd*E1lKz9qDgN-VnbofyPRQTX;;=H6+;jiHH)4U22{7nz!BI|shx5HFGh2KWM znl=;Af=>xpK;dne3LxvX1R7?39UIw|1akopmjv2`bEX3# zD+y5q09_L3vst`^XacGP2t;}&yvlm@v-FY>wFImbG+ZYXTOB1KpwVYt5{@+)a@&H2 znfC~^y00M_0brAa*XID54v}!7i#6US(CU7Mqy&I%qJ%SR3GDNfNKOC;Q<8)v1(YNq zSpok^;FdPC#>8uNq$-JC8DrKnO`Eg;NVKY>APL&WW6Bbe7a)LKCjkFc(~LD5HP9&_ zSqUx#fLkR2_}NKtBOq4^t_0*L!JUAaB$)r1diHx)xD>#FOl%YA{6)jen9W5Cp90pn za**I!03N(ca4*2Sge(MjmXM8rmINN^@LYjeV}b_RykX8t0JPO6@Gyrf=1>Wy4|TE= zFoT2>-Mlk1JMyRkI^b9=ssef_FTMC500030{|$yzp8x;=21!Ig aR09CG(YDamqlxwa0000