列表,个人中心

This commit is contained in:
fengjignqi
2026-09-22 16:52:11 +08:00
parent 851a06db1a
commit ec2c26e829
24 changed files with 341 additions and 20 deletions

View File

@@ -26,6 +26,8 @@ class DeviceDetailActivity :
} }
private var device: DeviceEntity? = null private var device: DeviceEntity? = null
/** 当前正在提交的眼罩目标状态,0 表示打开,1 表示关闭。 */
private var pendingCoverOn: Boolean? = null
override fun createBinding(inflater: LayoutInflater): DeviceDetailLayoutBinding { override fun createBinding(inflater: LayoutInflater): DeviceDetailLayoutBinding {
return DeviceDetailLayoutBinding.inflate(inflater) return DeviceDetailLayoutBinding.inflate(inflater)
} }
@@ -38,7 +40,7 @@ class DeviceDetailActivity :
ImmersionBar.with(this) ImmersionBar.with(this)
.statusBarView(binding.statusBarView) .statusBarView(binding.statusBarView)
.statusBarDarkFont(true) .statusBarDarkFont(true)
.navigationBarColor(R.color.color_zhu) .navigationBarColor(R.color.white)
.navigationBarDarkIcon(true) .navigationBarDarkIcon(true)
.init() .init()
} }
@@ -46,7 +48,7 @@ class DeviceDetailActivity :
override fun initView(savedInstanceState: Bundle?) { override fun initView(savedInstanceState: Bundle?) {
device = intent.getSerializableExtra(EXTRA_DEVICE) as? DeviceEntity device = intent.getSerializableExtra(EXTRA_DEVICE) as? DeviceEntity
binding.includedTitle.tvTitle.text = device?.name ?: "设备详情" binding.includeTitle.tvTitle.text = device?.name ?: "设备详情"
val deviceNo = device?.deviceNo val deviceNo = device?.deviceNo
if (deviceNo.isNullOrBlank()) { if (deviceNo.isNullOrBlank()) {
showError("设备信息无效") showError("设备信息无效")
@@ -57,7 +59,8 @@ class DeviceDetailActivity :
listOf( listOf(
binding.tvBind, binding.tvBind,
binding.switchCtrl, binding.switchCtrl,
binding.includedTitle.imgBack, binding.imgSwitchCtrl,
binding.includeTitle.imgBack,
).forEach { button -> ).forEach { button ->
button.setOnClickListener(this) button.setOnClickListener(this)
} }
@@ -68,16 +71,23 @@ class DeviceDetailActivity :
R.id.img_back ->{ R.id.img_back ->{
finish() finish()
} }
R.id.switch_ctrl ->{
if(device?.status == "0"){ R.id.img_switch_ctrl ->{
showMessage("设备已经离线")
return;
}
val currentDevice = device ?: return val currentDevice = device ?: return
// hoodCoverOn:0 表示打开,1 表示关闭;点击后发送切换后的目标状态。 if (currentDevice.status == "0") {
showMessage("设备已经离线")
return
}
// 根据当前服务端状态计算目标状态,再由图片按钮统一发起控制请求。
val coverOn = currentDevice.hoodCoverOn != HOOD_COVER_ON val coverOn = currentDevice.hoodCoverOn != HOOD_COVER_ON
pendingCoverOn = coverOn
presenter?.controlEye(currentDevice.deviceNo, coverOn) presenter?.controlEye(currentDevice.deviceNo, coverOn)
} }
R.id.switch_ctrl ->{
// 开关控件仅保留展示用途,实际控制统一由图片按钮处理。
}
} }
} }
@@ -91,20 +101,31 @@ class DeviceDetailActivity :
override fun getDeviceDetailSuccess(device: DeviceEntity) { override fun getDeviceDetailSuccess(device: DeviceEntity) {
this.device = device this.device = device
binding.includedTitle.tvTitle.text = device.name binding.includeTitle.tvTitle.text = device.name
binding.tvBind.text = "设备编号:${device.deviceNo}" binding.tvBind.text = "设备编号:${device.deviceNo}"
binding.switchCtrl.isChecked = device.hoodCoverOn == HOOD_COVER_ON updateCoverControlState(device.hoodCoverOn == HOOD_COVER_ON)
} }
override fun controlEyeSuccess(message: String) { override fun controlEyeSuccess(message: String) {
showMessage(message) showMessage(message)
val coverOn = pendingCoverOn ?: return
pendingCoverOn = null
device = device?.copy( device = device?.copy(
hoodCoverOn = if (binding.switchCtrl.isChecked) { hoodCoverOn = if (coverOn) {
HOOD_COVER_ON HOOD_COVER_ON
} else { } else {
HOOD_COVER_OFF HOOD_COVER_OFF
} }
) )
updateCoverControlState(coverOn)
}
/** 同步开关控件和图片按钮的视觉状态,避免请求成功前出现错误状态。 */
private fun updateCoverControlState(coverOn: Boolean) {
binding.switchCtrl.isChecked = coverOn
binding.imgSwitchCtrl.setImageResource(
if (coverOn) R.mipmap.icon_switch_on else R.mipmap.icon_switch_off
)
} }
override fun bindDeviceSuccess(message: String) { override fun bindDeviceSuccess(message: String) {

View File

@@ -55,6 +55,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding, LoginPresenter>(),
binding.getCodeButton, binding.getCodeButton,
binding.loginButton, binding.loginButton,
binding.registerButton, binding.registerButton,
binding.tvChangeLoginType,
).forEach { button -> ).forEach { button ->
button.setOnClickListener(this) button.setOnClickListener(this)
} }
@@ -63,6 +64,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding, LoginPresenter>(),
override fun onClick(view: View) { override fun onClick(view: View) {
when (view.id) { when (view.id) {
R.id.tvChangeCode -> {//更换登录方式 R.id.tvChangeCode -> {//更换登录方式
//Todo 2.目前这里的切换,只是针对的手机 验证码/密码登录,两种方式。需要修改一下,参考【Todo 1】这里不仅要对手机号的 验证码/密码登录 做处理,同时也要处理 邮箱的 验证码/密码登录;所以这里应该是四种登录方式。 手机验证码/密码 + 邮箱验证码/密码。
if (loginByPassword) { if (loginByPassword) {
binding.linearCode.visibility = View.VISIBLE binding.linearCode.visibility = View.VISIBLE
binding.linearPsd.visibility = View.GONE binding.linearPsd.visibility = View.GONE
@@ -76,7 +78,13 @@ class LoginActivity : BaseActivity<ActivityLoginBinding, LoginPresenter>(),
} }
} }
R.id.tv_change_login_type ->{
//Todo 1.这里是切换手机/邮箱 两种登录方式的按钮,点击切换,同时img_login_type改变,【icon_login_phone】/【icon_login_email】两种。
}
R.id.getCodeButton -> {//获取验证码 R.id.getCodeButton -> {//获取验证码
//Todo 3. 这里获取验证码,是2种,发送手机验证码和邮箱验证码。另外我【Apiservice】中已经写好了方法,两种获取验证码的接口都有。
val phone = binding.accountEditText.text.toString().trim() val phone = binding.accountEditText.text.toString().trim()
if (phone.isBlank()) { if (phone.isBlank()) {
ToastUtil.showShort(mContext, "手机号不能为空") ToastUtil.showShort(mContext, "手机号不能为空")
@@ -98,6 +106,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding, LoginPresenter>(),
} }
R.id.loginButton -> {//登录 R.id.loginButton -> {//登录
//Todo 4.同样也是和前面的逻辑一样,4种登录方法。需要你补齐目前缺少的。
val account = binding.accountEditText.text.toString().trim() val account = binding.accountEditText.text.toString().trim()
if (account.isBlank()) { if (account.isBlank()) {
ToastUtil.showShort(mContext, "账号不能为空") ToastUtil.showShort(mContext, "账号不能为空")

View File

@@ -86,6 +86,27 @@ class MineFragment : BaseFragment<MineCenterLayoutBinding, MinePresenter>(),
} }
//注销账号
R.id.linear_zxzh -> {
showDialogtext(
"确认注销账号?",
"注销账号后,账号将被删除,所有设备" +
"解绑、个人信息被清除,无法恢复,请谨慎操作?",
"确认注销",
"取消",
object : OnDialogClick {
override fun onCancelClick() {
}
override fun onConfirmClick() {
// 用户确认后调用服务端注销账号接口。
presenter?.userLogoff()
}
}
)
}
//退出登录 //退出登录
R.id.tv_logout -> { R.id.tv_logout -> {
showDialogtext( showDialogtext(
@@ -133,6 +154,9 @@ class MineFragment : BaseFragment<MineCenterLayoutBinding, MinePresenter>(),
showMessage(message) showMessage(message)
} }
/**
* 退出登录
*/
override fun onLogoutSuccess(message: String) { override fun onLogoutSuccess(message: String) {
val context = context ?: return val context = context ?: return
// 服务端退出成功后清除本地账号信息,避免应用重启后恢复旧登录状态。 // 服务端退出成功后清除本地账号信息,避免应用重启后恢复旧登录状态。
@@ -144,6 +168,21 @@ class MineFragment : BaseFragment<MineCenterLayoutBinding, MinePresenter>(),
goToClearTask(LoginActivity::class.java, null) goToClearTask(LoginActivity::class.java, null)
} }
/**
* 注销账号
*/
override fun onLogoffSuccess(message: String) {
val context = context ?: return
// 注销成功后清理本地会话,避免已注销账号继续被本地恢复。
UserInfoManager.clearUser(context)
AppConfig.USER_LOGIN = false
showMessage(message)
// 注销后清空页面栈并回到登录页。
goToClearTask(LoginActivity::class.java, null)
}
/** 查询应用缓存大小,并将结果显示在个人中心页面。 */ /** 查询应用缓存大小,并将结果显示在个人中心页面。 */
private fun displayCacheSize() { private fun displayCacheSize() {
val context = context ?: return val context = context ?: return

View File

@@ -29,6 +29,7 @@ interface MineContract {
callback: Callback<BaseBean<Any>> callback: Callback<BaseBean<Any>>
) )
fun userLogout(callback: Callback<BaseBean<Any>>) fun userLogout(callback: Callback<BaseBean<Any>>)
fun userLogoff(callback: Callback<BaseBean<Any>>)
fun cancel() fun cancel()
} }
@@ -47,5 +48,7 @@ interface MineContract {
fun onUpdateBindNewPhoneSuccess(message: String) {} fun onUpdateBindNewPhoneSuccess(message: String) {}
/** 退出登录成功回调。 */ /** 退出登录成功回调。 */
fun onLogoutSuccess(message: String) {} fun onLogoutSuccess(message: String) {}
/** 注销账号成功回调。 */
fun onLogoffSuccess(message: String) {}
} }
} }

View File

@@ -23,6 +23,7 @@ class MineImpl : MineContract.DataModel {
private var newPhoneCodeCall: Call<BaseBean<SendCodeEntity>>? = null private var newPhoneCodeCall: Call<BaseBean<SendCodeEntity>>? = null
private var updateBindNewPhoneCall: Call<BaseBean<Any>>? = null private var updateBindNewPhoneCall: Call<BaseBean<Any>>? = null
private var logoutCall: Call<BaseBean<Any>>? = null private var logoutCall: Call<BaseBean<Any>>? = null
private var logoffCall: Call<BaseBean<Any>>? = null
override fun getCurrentPhoneCode( override fun getCurrentPhoneCode(
callback: MineContract.Callback<BaseBean<Any>> callback: MineContract.Callback<BaseBean<Any>>
@@ -183,6 +184,33 @@ class MineImpl : MineContract.DataModel {
} }
} }
override fun userLogoff(callback: MineContract.Callback<BaseBean<Any>>) {
logoffCall?.cancel()
logoffCall = ApiClient.apiService
.userLogoff(NetworkManager.getToken())
.also { call ->
call.enqueue(object : Callback<BaseBean<Any>> {
override fun onResponse(
call: Call<BaseBean<Any>>,
response: Response<BaseBean<Any>>
) {
mainHandler.post {
response.body()?.let(callback::success)
?: callback.fail("注销账号失败")
}
}
override fun onFailure(call: Call<BaseBean<Any>>, error: Throwable) {
if (!call.isCanceled) {
mainHandler.post {
callback.fail(error.message ?: "网络请求失败")
}
}
}
})
}
}
override fun getUserInfo( override fun getUserInfo(
params: Map<String, String>, params: Map<String, String>,
callback: MineContract.Callback<MineUserInfo> callback: MineContract.Callback<MineUserInfo>
@@ -320,6 +348,8 @@ class MineImpl : MineContract.DataModel {
updateBindNewPhoneCall = null updateBindNewPhoneCall = null
logoutCall?.cancel() logoutCall?.cancel()
logoutCall = null logoutCall = null
logoffCall?.cancel()
logoffCall = null
mainHandler.removeCallbacksAndMessages(null) mainHandler.removeCallbacksAndMessages(null)
} }
} }

View File

@@ -146,6 +146,31 @@ class MinePresenter(private val mineImpl: MineImpl) : BasePresenter<MineContract
}) })
} }
/** 调用注销账号接口,成功后由页面清理本地会话并返回登录页。 */
fun userLogoff() {
val currentView = view ?: return
currentView.showLoading()
mineImpl.userLogoff(object : MineContract.Callback<BaseBean<Any>> {
override fun success(data: BaseBean<Any>) {
view?.apply {
hideLoading()
if (data.code == 0 || data.code == 200) {
onLogoffSuccess(data.message.ifBlank { "账号注销成功" })
} else {
showError(data.message.ifBlank { "注销账号失败" })
}
}
}
override fun fail(message: String) {
view?.apply {
hideLoading()
showError(message)
}
}
})
}
fun getUserInfo() { fun getUserInfo() {
val currentView = view ?: return val currentView = view ?: return
currentView.showLoading() currentView.showLoading()

View File

@@ -244,6 +244,15 @@ interface ApiService {
fun userLogout(@HeaderMap headers: Map<String, String>): Call<BaseBean<Any>> fun userLogout(@HeaderMap headers: Map<String, String>): Call<BaseBean<Any>>
/**
* 注销账号
*
* 不需要任何入参,
*/
@POST("app/user/account")
fun userLogoff(@HeaderMap headers: Map<String, String>): Call<BaseBean<Any>>
@GET("app/v1/checkVersion") @GET("app/v1/checkVersion")

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<stroke android:color="@color/black" android:width="1dp"/>
<corners android:radius="99dp"/>
</shape>

View File

@@ -55,6 +55,7 @@
android:paddingEnd="16dp"> android:paddingEnd="16dp">
<ImageView <ImageView
android:id="@+id/img_login_type"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:contentDescription="@null" android:contentDescription="@null"
@@ -65,7 +66,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_weight="1" android:layout_weight="2"
android:background="@null" android:background="@null"
android:hint="@string/register_account_hint" android:hint="@string/register_account_hint"
android:inputType="text" android:inputType="text"
@@ -73,6 +74,15 @@
android:textColor="@color/color_2E2E2E" android:textColor="@color/color_2E2E2E"
android:textColorHint="@color/color_999999" android:textColorHint="@color/color_999999"
android:textSize="15sp" /> android:textSize="15sp" />
<TextView
android:id="@+id/tv_change_login_type"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:gravity="center"
android:text="手机"
android:textColor="@color/color_zhu"
android:textSize="13sp" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View File

@@ -17,7 +17,7 @@
android:layout_height="48dp"> android:layout_height="48dp">
<include <include
android:id="@+id/included_title" android:id="@+id/included_title"
layout="@layout/include_activity_back_whitebg_layout" /> layout="@layout/include_activity_back_greenbg_layout" />
</RelativeLayout> </RelativeLayout>
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -7,16 +7,109 @@
android:id="@+id/statusBarView" android:id="@+id/statusBarView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:background="@color/color_zhu" /> android:background="@color/white" />
<RelativeLayout <RelativeLayout
android:id="@+id/rl_title" android:id="@+id/rl_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp"> android:layout_height="wrap_content"
android:background="@color/mainBackgroundColor">
<include <include
android:id="@+id/included_title" android:id="@+id/include_title"
layout="@layout/include_activity_back_gray_layout" /> layout="@layout/include_activity_back_whitebg_layout"/>
</RelativeLayout> </RelativeLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 视频播放区域 -->
<com.shuyu.gsyvideoplayer.video.GSYADVideoPlayer
android:id="@+id/exoPlayContextId"
android:layout_width="match_parent"
android:layout_height="220dp"/>
<!-- 右上侧按钮层 -->
<LinearLayout
android:id="@+id/rightMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
android:paddingHorizontal="8dp"
android:orientation="horizontal"
android:background="@color/black_50"
android:gravity="center_vertical"
android:paddingVertical="3dp">
<TextView
android:id="@+id/tv_latLng"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5"
android:textSize="12sp"
android:textColor="@color/white"
android:text="39.9042° N, 116.4074° E"/>
<ImageView
android:id="@+id/ivDownload"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@mipmap/icon_dev_detail_wifi"/>
<ImageView
android:id="@+id/ivDelete"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="10dp"
android:src="@mipmap/icon_dev_detail_power"/>
</LinearLayout>
<!-- 右下侧按钮层 -->
<LinearLayout
android:id="@+id/rightDow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginEnd="15dp">
<ImageView
android:id="@+id/ivMute"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginBottom="10dp"
android:src="@mipmap/ic_launcher"/>
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="15dp"
android:layout_marginHorizontal="20dp"
android:paddingHorizontal="18dp"
android:paddingVertical="14dp"
android:gravity="center_vertical"
android:background="@drawable/shape_dev_eye_ctrl_line_bg">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/icon_dev_open_eye"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:layout_marginStart="10dp"
android:textSize="13sp"
android:textColor="@color/color_333333"
android:text="眼罩开关"/>
<ImageView
android:id="@+id/img_switch_ctrl"
android:layout_width="36dp"
android:layout_height="24dp"
android:src="@mipmap/icon_switch_on"/>
</LinearLayout>
<TextView <TextView
android:id="@+id/tv_bind" android:id="@+id/tv_bind"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/relative_title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/color_zhu">
<ImageView
android:id="@+id/img_back"
android:layout_width="22dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:src="@drawable/img_back_black"
android:layout_marginLeft="10dp"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="18sp"
android:textColor="#2E2E2E"
android:textStyle="bold"
android:text="标题名称"
android:layout_marginLeft="10dp"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"/>
<TextView
android:id="@+id/tv_title_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/tv_title"
android:textSize="14sp"
android:textColor="#2E2E2E"
android:text="(剩余时间)"
android:layout_marginLeft="5dp"
android:visibility="gone"/>
<TextView
android:id="@+id/tv_title_right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:textSize="14sp"
android:textColor="#333333"
android:text=""
android:layout_marginLeft="5dp"
android:visibility="visible"/>
<ImageView
android:id="@+id/img_setting"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:src="@android:drawable/ic_menu_preferences"
android:layout_marginRight="20dp"
android:visibility="gone"/>
</RelativeLayout>
</RelativeLayout>

View File

@@ -6,7 +6,7 @@
android:id="@+id/relative_title" android:id="@+id/relative_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:background="@color/color_zhu"> android:background="@color/white">
<ImageView <ImageView
android:id="@+id/img_back" android:id="@+id/img_back"
android:layout_width="22dp" android:layout_width="22dp"

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

View File

@@ -129,6 +129,17 @@
<!--黑色透明度--> <!--黑色透明度-->
<color name="translucent_black_95">#0d000000</color> <color name="translucent_black_95">#0d000000</color>
<color name="black_10">#1A000000</color>
<color name="black_20">#33000000</color>
<color name="black_30">#4D000000</color>
<color name="black_40">#66000000</color>
<color name="black_50">#80000000</color>
<color name="black_60">#99000000</color>
<color name="black_70">#B3000000</color>
<color name="black_80">#CC000000</color>
<color name="black_90">#E6000000</color>
<!-- KotMvpProject 基础页面和日期选择器颜色。 --> <!-- KotMvpProject 基础页面和日期选择器颜色。 -->
<color name="purple_200">#FFBB86FC</color> <color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color> <color name="purple_500">#FF6200EE</color>
@@ -145,4 +156,7 @@
<color name="register_psd_rule">#286F51</color> <color name="register_psd_rule">#286F51</color>
</resources> </resources>