This commit is contained in:
pengxiaolong
2026-03-02 13:01:54 +08:00
parent c018243116
commit 3a65b3f43d
19 changed files with 582 additions and 73 deletions

View File

@@ -6,7 +6,8 @@
"Bash(find /d/Test/MyApplication/app/src/main/assets -name \"vocab.txt\" -exec wc -l {} ;)", "Bash(find /d/Test/MyApplication/app/src/main/assets -name \"vocab.txt\" -exec wc -l {} ;)",
"WebSearch", "WebSearch",
"Bash(grep:*)", "Bash(grep:*)",
"Bash(find:*)" "Bash(find:*)",
"Bash(ls:*)"
] ]
} }
} }

View File

@@ -235,7 +235,9 @@ class MainActivity : AppCompatActivity() {
bottomNav.post { bottomNav.post {
bottomNav.selectedItemId = R.id.home_graph bottomNav.selectedItemId = R.id.home_graph
openGlobal(R.id.loginFragment) // ✅ 退出登录后立刻打开登录页 // ✅ clearBackStack=true清空全局回退栈如 PersonalSettings 等残留页面),
// 避免用户从登录页返回时全局栈未清空导致底部 Tab 栏消失
openGlobal(R.id.loginFragment, clearBackStack = true)
} }
} }

View File

@@ -769,6 +769,9 @@ class MyInputMethodService : InputMethodService(), KeyboardEnvironment {
aiKeyboard = null aiKeyboard = null
emojiKeyboard = null emojiKeyboard = null
// 深色模式切换时更新主题,下次键盘创建时使用新主题
ThemeManager.onSystemConfigurationChanged(this)
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
} }

View File

@@ -32,6 +32,17 @@ interface ApiService {
suspend fun logout( suspend fun logout(
): ApiResponse<Boolean> ): ApiResponse<Boolean>
//注销账户
@POST("user/cancelAccount")
suspend fun cancelAccount(
): ApiResponse<Boolean>
//按 locale 查询提示信息
@GET("keyboardWarningMessage/byLocale")
suspend fun keyboardWarningMessage(
@Query("locale") id: String
): ApiResponse<keyboardWarningMessageResponse>
//发送验证邮件 //发送验证邮件
@POST("user/sendVerifyMail") @POST("user/sendVerifyMail")
suspend fun sendVerifyCode( suspend fun sendVerifyCode(

View File

@@ -54,6 +54,12 @@ data class VerifyCodeRequest(
val mailAddress: String, val mailAddress: String,
val verifyCode: String, val verifyCode: String,
) )
//注销账户
data class keyboardWarningMessageResponse(
val locale: String,
val content: String,
val updatedAt: String,
)
//重置密码 //重置密码
data class ResetPasswordRequest( data class ResetPasswordRequest(

View File

@@ -128,17 +128,24 @@ object ThemeManager {
} }
fun init(context: Context) { fun init(context: Context) {
android.util.Log.d("1314520-ThemeManager", "init: currentThemeName=$currentThemeName")
if (currentThemeName != null) return if (currentThemeName != null) return
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
isManualTheme = prefs.getBoolean(KEY_IS_MANUAL_THEME, false) isManualTheme = prefs.getBoolean(KEY_IS_MANUAL_THEME, false)
val savedTheme = prefs.getString(KEY_CURRENT_THEME, "default") ?: "default"
// 默认主题不应视为手动模式,修正历史脏数据
if (isManualTheme && (savedTheme == "default" || savedTheme == "default_darkness")) {
isManualTheme = false
prefs.edit().putBoolean(KEY_IS_MANUAL_THEME, false).apply()
}
if (!isManualTheme) { if (!isManualTheme) {
updateThemeForSystemMode(context) updateThemeForSystemMode(context)
context.registerSystemDarkModeListener() context.registerSystemDarkModeListener()
} else { } else {
val name = prefs.getString(KEY_CURRENT_THEME, "default") ?: "default" setCurrentTheme(context, savedTheme, true)
setCurrentTheme(context, name, true)
} }
} }
@@ -147,6 +154,23 @@ object ThemeManager {
setCurrentTheme(context, defaultTheme, false) setCurrentTheme(context, defaultTheme, false)
} }
/**
* 系统配置变更时调用,非手动主题模式下自动切换深色/浅色主题
*/
fun onSystemConfigurationChanged(context: Context) {
val dark = isDarkMode(context)
// 默认主题始终跟随系统深色模式
if (isManualTheme && (currentThemeName == "default" || currentThemeName == "default_darkness")) {
isManualTheme = false
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit().putBoolean(KEY_IS_MANUAL_THEME, false).apply()
}
android.util.Log.d("1314520-ThemeManager", "onSystemConfigurationChanged: isManualTheme=$isManualTheme, isDark=$dark, currentTheme=$currentThemeName")
if (!isManualTheme) {
updateThemeForSystemMode(context)
}
}
/** /**
* 切换当前主题: * 切换当前主题:
* - 更新 currentThemeName * - 更新 currentThemeName
@@ -154,6 +178,7 @@ object ThemeManager {
* - 重新加载该主题的全部图片到缓存 * - 重新加载该主题的全部图片到缓存
*/ */
fun setCurrentTheme(context: Context, themeName: String, isManual: Boolean = true) { fun setCurrentTheme(context: Context, themeName: String, isManual: Boolean = true) {
android.util.Log.d("1314520-ThemeManager", "setCurrentTheme: themeName=$themeName, isManual=$isManual, old=$currentThemeName")
currentThemeName = themeName currentThemeName = themeName
isManualTheme = isManual isManualTheme = isManual
@@ -163,9 +188,11 @@ object ThemeManager {
.putBoolean(KEY_IS_MANUAL_THEME, isManual) .putBoolean(KEY_IS_MANUAL_THEME, isManual)
.apply() .apply()
// 如果是手动选择主题,取消系统模式监听 // 如果是手动选择主题,取消系统模式监听;否则重新注册
if (isManual) { if (isManual) {
context.unregisterSystemDarkModeListener() context.unregisterSystemDarkModeListener()
} else {
context.registerSystemDarkModeListener()
} }
val newFilePathCache: MutableMap<String, File> = ConcurrentHashMap() val newFilePathCache: MutableMap<String, File> = ConcurrentHashMap()

View File

@@ -0,0 +1,39 @@
package com.example.myapplication.ui.mine
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.example.myapplication.R
class CancelAccountDialogFragment(
private val onConfirm: () -> Unit
) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = Dialog(requireContext())
dialog.setContentView(R.layout.dialog_cancel_account)
dialog.setCancelable(true)
dialog.window?.apply {
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
dialog.findViewById<View>(R.id.btn_cancel).setOnClickListener {
dismiss()
}
dialog.findViewById<View>(R.id.btn_confirm).setOnClickListener {
dismiss()
onConfirm()
}
return dialog
}
}

View File

@@ -46,7 +46,6 @@ class MineFragment : Fragment() {
private lateinit var nickname: TextView private lateinit var nickname: TextView
private lateinit var vipIcon: ImageView private lateinit var vipIcon: ImageView
private lateinit var time: TextView private lateinit var time: TextView
private lateinit var logout: TextView
private lateinit var avatar: CircleImageView private lateinit var avatar: CircleImageView
private lateinit var share: LinearLayout private lateinit var share: LinearLayout
private lateinit var loadingOverlay: LoadingOverlay private lateinit var loadingOverlay: LoadingOverlay
@@ -76,7 +75,6 @@ class MineFragment : Fragment() {
nickname = view.findViewById(R.id.nickname) nickname = view.findViewById(R.id.nickname)
vipIcon = view.findViewById(R.id.vip_icon) vipIcon = view.findViewById(R.id.vip_icon)
time = view.findViewById(R.id.time) time = view.findViewById(R.id.time)
logout = view.findViewById(R.id.logout)
avatar = view.findViewById(R.id.avatar) avatar = view.findViewById(R.id.avatar)
share = view.findViewById(R.id.click_Share) share = view.findViewById(R.id.click_Share)
loadingOverlay = LoadingOverlay.attach(view.findViewById(R.id.rootCoordinator)) loadingOverlay = LoadingOverlay.attach(view.findViewById(R.id.rootCoordinator))
@@ -123,17 +121,6 @@ class MineFragment : Fragment() {
} }
} }
logout.setOnClickListener {
LogoutDialogFragment { doLogout() }
.show(parentFragmentManager, "logout_dialog")
BehaviorReporter.report(
isNewUser = false,
"page_id" to "person_info",
"element_id" to "logout_btn",
)
}
view.findViewById<ImageView>(R.id.imgLeft).setOnClickListener { view.findViewById<ImageView>(R.id.imgLeft).setOnClickListener {
// 使用事件总线打开充值页面 // 使用事件总线打开充值页面
AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.rechargeFragment)) AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.rechargeFragment))
@@ -321,36 +308,6 @@ class MineFragment : Fragment() {
} }
} }
private fun doLogout() {
viewLifecycleOwner.lifecycleScope.launch {
try {
val response = RetrofitClient.apiService.logout()
if (!isAdded) return@launch
if (response.code == 0) {
EncryptedSharedPreferencesUtil.remove(requireContext(), "Personal_information")
EncryptedSharedPreferencesUtil.remove(requireContext(), "user")
// 清空 UI
nickname.text = ""
time.text = ""
renderVip(false, null)
Glide.with(requireContext())
.load(R.drawable.default_avatar)
.placeholder(R.drawable.component_loading)
.error(R.drawable.no_search_result)
.into(avatar)
// 触发登出事件让MainActivity打开登录页面
AuthEventBus.emit(AuthEvent.Logout(returnTabTag = "tab_mine"))
} else {
Log.e(TAG, "logout fail code=${response.code}")
}
} catch (e: Exception) {
Log.e(TAG, "logout exception", e)
}
}
}
private fun renderVip(isVip: Boolean?, vipLevel: Int?) { private fun renderVip(isVip: Boolean?, vipLevel: Int?) {

View File

@@ -0,0 +1,109 @@
package com.example.myapplication.ui.mine.myotherpages
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import android.widget.FrameLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.example.myapplication.R
import com.example.myapplication.network.AuthEvent
import com.example.myapplication.network.AuthEventBus
import com.example.myapplication.network.RetrofitClient
import com.example.myapplication.ui.mine.CancelAccountDialogFragment
import com.example.myapplication.utils.EncryptedSharedPreferencesUtil
import kotlinx.coroutines.launch
class CancelAccount : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.cancel_account, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<FrameLayout>(R.id.iv_close).setOnClickListener {
AuthEventBus.emit(AuthEvent.UserUpdated)
requireActivity().onBackPressedDispatcher.onBackPressed()
}
view.findViewById<TextView>(R.id.btn_delete).setOnClickListener {
CancelAccountDialogFragment { doCancelAccount() }
.show(parentFragmentManager, "cancel_account_dialog")
}
loadWarningMessage(view)
}
private fun loadWarningMessage(view: View) {
val progressBar = view.findViewById<ProgressBar>(R.id.progressBar)
val webView = view.findViewById<WebView>(R.id.webView)
val tvEmpty = view.findViewById<TextView>(R.id.tvEmpty)
val locale = resources.configuration.locales[0]
val apiLocale = when (locale.language) {
"zh" -> "zh-CN"
else -> "en-US"
}
viewLifecycleOwner.lifecycleScope.launch {
try {
val response = RetrofitClient.apiService.keyboardWarningMessage(apiLocale)
if (!isAdded) return@launch
val content = response.data?.content
if (response.code == 0 && !content.isNullOrBlank()) {
progressBar.visibility = View.GONE
webView.visibility = View.VISIBLE
webView.isNestedScrollingEnabled = false
webView.setBackgroundColor(0x00000000)
webView.loadDataWithBaseURL(
null,
content,
"text/html; charset=UTF-8",
"UTF-8",
null
)
} else {
progressBar.visibility = View.GONE
tvEmpty.visibility = View.VISIBLE
}
} catch (e: Exception) {
Log.e("CancelAccount", "loadWarningMessage exception", e)
if (!isAdded) return@launch
progressBar.visibility = View.GONE
tvEmpty.visibility = View.VISIBLE
}
}
}
private fun doCancelAccount() {
viewLifecycleOwner.lifecycleScope.launch {
try {
val response = RetrofitClient.apiService.cancelAccount()
if (!isAdded) return@launch
if (response.code == 0) {
EncryptedSharedPreferencesUtil.remove(requireContext(), "Personal_information")
EncryptedSharedPreferencesUtil.remove(requireContext(), "user")
AuthEventBus.emit(AuthEvent.Logout(returnTabTag = "tab_mine"))
} else {
Log.e("CancelAccount", "cancelAccount fail code=${response.code}")
}
} catch (e: Exception) {
Log.e("CancelAccount", "cancelAccount exception", e)
}
}
}
}

View File

@@ -31,6 +31,8 @@ import com.example.myapplication.network.RetrofitClient
import com.example.myapplication.network.User import com.example.myapplication.network.User
import com.example.myapplication.network.updateInfoRequest import com.example.myapplication.network.updateInfoRequest
import com.example.myapplication.ui.common.LoadingOverlay import com.example.myapplication.ui.common.LoadingOverlay
import com.example.myapplication.ui.mine.LogoutDialogFragment
import com.example.myapplication.utils.EncryptedSharedPreferencesUtil
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import de.hdodenhof.circleimageview.CircleImageView import de.hdodenhof.circleimageview.CircleImageView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -45,6 +47,10 @@ import android.util.Log
class PersonalSettings : BottomSheetDialogFragment() { class PersonalSettings : BottomSheetDialogFragment() {
companion object {
private const val TAG = "PersonalSettings"
}
private var user: User? = null private var user: User? = null
private lateinit var avatar: CircleImageView private lateinit var avatar: CircleImageView
@@ -178,8 +184,25 @@ class PersonalSettings : BottomSheetDialogFragment() {
Toast.makeText(requireContext(), getString(R.string.personal_settings_copy), Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), getString(R.string.personal_settings_copy), Toast.LENGTH_SHORT).show()
} }
//注销账号
view.findViewById<View>(R.id.row_cancel_account).setOnClickListener {
AuthEventBus.emit(AuthEvent.OpenGlobalPage(R.id.CancelAccount))
}
// ===================== load & render ===================== // ===================== load & render =====================
// 退出登录
view.findViewById<TextView>(R.id.logout).setOnClickListener {
LogoutDialogFragment { doLogout() }
.show(parentFragmentManager, "logout_dialog")
BehaviorReporter.report(
isNewUser = false,
"page_id" to "person_info",
"element_id" to "logout_btn",
)
}
loadUser() loadUser()
} }
@@ -213,7 +236,7 @@ class PersonalSettings : BottomSheetDialogFragment() {
Glide.with(this) Glide.with(this)
.load(u.avatarUrl) .load(u.avatarUrl)
.placeholder(R.drawable.component_loading) .placeholder(R.drawable.component_loading)
.error(R.drawable.no_search_result) .error(R.drawable.default_avatar)
.into(avatar) .into(avatar)
} }
@@ -229,11 +252,48 @@ class PersonalSettings : BottomSheetDialogFragment() {
cm.setPrimaryClip(ClipData.newPlainText("user_id", text)) cm.setPrimaryClip(ClipData.newPlainText("user_id", text))
} }
private suspend fun getUserdata(): ApiResponse<User>? = private suspend fun getUserdata(): ApiResponse<User>? {
runCatching { RetrofitClient.apiService.getUser() }.getOrNull() Log.d(TAG, "getUserdata: 开始请求用户数据")
return runCatching {
RetrofitClient.apiService.getUser()
}.onSuccess {
Log.d(TAG, "getUserdata: 请求成功 code=${it.code}, data=${it.data}")
}.onFailure {
Log.e(TAG, "getUserdata: 请求异常", it)
}.getOrNull()
}
private suspend fun setupdateUserInfo(body: updateInfoRequest): ApiResponse<Boolean>? = private suspend fun setupdateUserInfo(body: updateInfoRequest): ApiResponse<Boolean>? {
runCatching { RetrofitClient.apiService.updateUserInfo(body) }.getOrNull() Log.d(TAG, "setupdateUserInfo: 开始更新用户信息 body=$body")
return runCatching {
RetrofitClient.apiService.updateUserInfo(body)
}.onSuccess {
Log.d(TAG, "setupdateUserInfo: 更新成功 code=${it.code}, data=${it.data}")
}.onFailure {
Log.e(TAG, "setupdateUserInfo: 更新异常", it)
}.getOrNull()
}
private fun doLogout() {
viewLifecycleOwner.lifecycleScope.launch {
try {
val response = RetrofitClient.apiService.logout()
if (!isAdded) return@launch
if (response.code == 0) {
EncryptedSharedPreferencesUtil.remove(requireContext(), "Personal_information")
EncryptedSharedPreferencesUtil.remove(requireContext(), "user")
dismiss()
AuthEventBus.emit(AuthEvent.Logout(returnTabTag = "tab_mine"))
} else {
Log.e("PersonalSettings", "logout fail code=${response.code}")
}
} catch (e: Exception) {
Log.e("PersonalSettings", "logout exception", e)
}
}
}
private val cameraPermissionLauncher = registerForActivityResult( private val cameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission() ActivityResultContracts.RequestPermission()
@@ -285,6 +345,7 @@ class PersonalSettings : BottomSheetDialogFragment() {
} }
private fun handleImageResult(uri: Uri) { private fun handleImageResult(uri: Uri) {
// Log.d(TAG, "handleImageResult: 选择图片 uri=$uri")
Glide.with(this) Glide.with(this)
.load(uri) .load(uri)
.placeholder(R.drawable.component_loading) .placeholder(R.drawable.component_loading)
@@ -297,6 +358,7 @@ class PersonalSettings : BottomSheetDialogFragment() {
} }
private suspend fun uploadAvatar(uri: Uri) { private suspend fun uploadAvatar(uri: Uri) {
// Log.d(TAG, "1314520-uploadAvatar: 开始上传图片 uri=$uri")
BehaviorReporter.report( BehaviorReporter.report(
isNewUser = false, isNewUser = false,
"page_id" to "person_info", "page_id" to "person_info",
@@ -315,6 +377,8 @@ class PersonalSettings : BottomSheetDialogFragment() {
val fileExtension = if (isPng) ".png" else ".jpg" val fileExtension = if (isPng) ".png" else ".jpg"
val mediaType = if (isPng) "image/png" else "image/jpeg" val mediaType = if (isPng) "image/png" else "image/jpeg"
// Log.d(TAG, "1314520-uploadAvatar: mimeType=$mimeType, isPng=$isPng, fileExtension=$fileExtension")
// Temp file in app-private external files dir (no storage permission needed) // Temp file in app-private external files dir (no storage permission needed)
val storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES) val storageDir = requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
@@ -322,9 +386,16 @@ class PersonalSettings : BottomSheetDialogFragment() {
// 先读取尺寸信息inJustDecodeBounds // 先读取尺寸信息inJustDecodeBounds
val boundsOptions = BitmapFactory.Options().apply { inJustDecodeBounds = true } val boundsOptions = BitmapFactory.Options().apply { inJustDecodeBounds = true }
resolver.openInputStream(uri)?.use { ins -> val boundsStream = resolver.openInputStream(uri)
if (boundsStream == null) {
Log.e(TAG, "1314520-uploadAvatar: openInputStream 返回 null无法读取图片")
return
}
boundsStream.use { ins ->
BitmapFactory.decodeStream(ins, null, boundsOptions) BitmapFactory.decodeStream(ins, null, boundsOptions)
} ?: return }
// Log.d(TAG, "1314520-uploadAvatar: 图片尺寸 ${boundsOptions.outWidth}x${boundsOptions.outHeight}")
// Calculate inSampleSize (粗略控制内存) // Calculate inSampleSize (粗略控制内存)
var inSampleSize = 1 var inSampleSize = 1
@@ -352,10 +423,13 @@ class PersonalSettings : BottomSheetDialogFragment() {
} }
if (bitmap == null) { if (bitmap == null) {
// Log.e(TAG, "1314520-uploadAvatar: bitmap 解码失败,无法生成位图")
Toast.makeText(requireContext(),getString(R.string.Pop_up_window_PersonalSettings_3), Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(),getString(R.string.Pop_up_window_PersonalSettings_3), Toast.LENGTH_SHORT).show()
return return
} }
// Log.d(TAG, "1314520-uploadAvatar: bitmap 解码成功 ${bitmap.width}x${bitmap.height}, inSampleSize=$inSampleSize")
// Compress to tempFile // Compress to tempFile
// 注意:用 outputStream 反复 compress 时不要在同一个 stream 上 truncate // 注意:用 outputStream 反复 compress 时不要在同一个 stream 上 truncate
// 最稳是每次重新打开 stream 写入。 // 最稳是每次重新打开 stream 写入。
@@ -374,6 +448,7 @@ class PersonalSettings : BottomSheetDialogFragment() {
} }
if (tempFile.length() > maxSizeBytes) { if (tempFile.length() > maxSizeBytes) {
Log.e(TAG, "1314520-uploadAvatar: 压缩后仍超过5MB, fileSize=${tempFile.length()}")
Toast.makeText(requireContext(), getString(R.string.Pop_up_window_PersonalSettings_4), Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), getString(R.string.Pop_up_window_PersonalSettings_4), Toast.LENGTH_SHORT).show()
bitmap.recycle() bitmap.recycle()
tempFile.delete() tempFile.delete()
@@ -383,23 +458,30 @@ class PersonalSettings : BottomSheetDialogFragment() {
val requestFile: RequestBody = RequestBody.create(mediaType.toMediaTypeOrNull(), tempFile) val requestFile: RequestBody = RequestBody.create(mediaType.toMediaTypeOrNull(), tempFile)
val body = MultipartBody.Part.createFormData("file", tempFile.name, requestFile) val body = MultipartBody.Part.createFormData("file", tempFile.name, requestFile)
// Log.d(TAG, "1314520-uploadAvatar: 准备上传 fileName=${tempFile.name}, fileSize=${tempFile.length()}, mediaType=$mediaType")
val response = RetrofitClient.createFileUploadService() val response = RetrofitClient.createFileUploadService()
.uploadFile("avatar", body) .uploadFile("avatar", body)
// Log.d("1314520-PersonalSettings", "uploadAvatar: $response")
// Clean up // Clean up
bitmap.recycle() bitmap.recycle()
tempFile.delete() tempFile.delete()
if (response?.code == 0) { if (response?.code == 0) {
setupdateUserInfo(updateInfoRequest(avatarUrl = response.data)) // Log.d(TAG, "1314520-uploadAvatar: 上传成功, avatarUrl=${response.data}, 开始更新用户信息")
val updateResp = setupdateUserInfo(updateInfoRequest(avatarUrl = response.data))
// Log.d(TAG, "1314520-uploadAvatar: 更新用户信息结果 code=${updateResp?.code}, data=${updateResp?.data}")
Toast.makeText(requireContext(), R.string.personal_avatar_updated, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.personal_avatar_updated, Toast.LENGTH_SHORT).show()
user = user?.copy(avatarUrl = response.data) user = user?.copy(avatarUrl = response.data)
} else { } else {
// Log.e(TAG, "1314520-uploadAvatar: 上传失败 code=${response?.code}, message=${response?.message}")
Toast.makeText(requireContext(), R.string.personal_upload_failed, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.personal_upload_failed, Toast.LENGTH_SHORT).show()
} }
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(requireContext(), R.string.personal_upload_failed, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.personal_upload_failed, Toast.LENGTH_SHORT).show()
Log.e("PersonalSettings", "Upload avatar error", e) Log.e("1314520-PersonalSettings", "Upload avatar error", e)
} finally { } finally {
loadingOverlay.hide() loadingOverlay.hide()
} }

View File

@@ -139,7 +139,7 @@ class MySkin : Fragment() {
// 如果当前主题是被删除的主题之一,切换到默认主题 // 如果当前主题是被删除的主题之一,切换到默认主题
val currentTheme = com.example.myapplication.theme.ThemeManager.getCurrentThemeName() val currentTheme = com.example.myapplication.theme.ThemeManager.getCurrentThemeName()
if (currentTheme != null && ids.any { it.toString() == currentTheme }) { if (currentTheme != null && ids.any { it.toString() == currentTheme }) {
com.example.myapplication.theme.ThemeManager.setCurrentTheme(requireContext(), "default") com.example.myapplication.theme.ThemeManager.setCurrentTheme(requireContext(), "default", false)
} }
adapter.removeByIds(ids.toSet()) adapter.removeByIds(ids.toSet())

View File

@@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF0000" />
<corners android:radius="@dimen/sw_8dp" />
</shape>

View File

@@ -0,0 +1,133 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootCoordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F6F7FB"
tools:context=".ui.mine.myotherpages.CancelAccount">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 标题和返回(固定顶部) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="@dimen/sw_16dp"
android:paddingEnd="@dimen/sw_16dp"
android:paddingTop="@dimen/sw_16dp">
<!-- 返回按钮 -->
<FrameLayout
android:id="@+id/iv_close"
android:layout_width="@dimen/sw_46dp"
android:layout_height="@dimen/sw_46dp">
<ImageView
android:layout_width="@dimen/sw_13dp"
android:layout_height="@dimen/sw_13dp"
android:layout_gravity="center"
android:src="@drawable/more_icons"
android:rotation="180"
android:scaleType="fitCenter" />
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="@dimen/sw_49dp"
android:gravity="center"
android:textStyle="bold"
android:text="@string/cancel_account_title"
android:textColor="#1B1F1A"
android:textSize="@dimen/sw_16sp" />
</LinearLayout>
<!-- 提示信息 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="@dimen/sw_40dp"
android:textStyle="bold"
android:paddingStart="@dimen/sw_16dp"
android:paddingEnd="@dimen/sw_16dp"
android:gravity="center_vertical"
android:text="@string/cancel_account_instruction"
android:textColor="#1B1F1A"
android:textSize="@dimen/sw_16sp" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true"
android:overScrollMode="never">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:paddingStart="@dimen/sw_16dp"
android:paddingEnd="@dimen/sw_16dp"
android:orientation="vertical">
<!-- 内容 -->
<FrameLayout
android:id="@+id/click_Notice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/sw_64dp"
android:layout_marginTop="@dimen/sw_20dp">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/sw_16dp"
android:layout_marginBottom="@dimen/sw_16dp" />
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<TextView
android:id="@+id/tvEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/sw_16dp"
android:layout_marginBottom="@dimen/sw_16dp"
android:text="@string/search_not_data"
android:textColor="#999999"
android:textSize="@dimen/sw_14sp"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- 删除按钮,固定在页面底部 -->
<TextView
android:id="@+id/btn_delete"
android:layout_width="match_parent"
android:layout_height="@dimen/sw_50dp"
android:layout_marginStart="@dimen/sw_16dp"
android:layout_marginEnd="@dimen/sw_16dp"
android:layout_marginTop="@dimen/sw_10dp"
android:layout_marginBottom="@dimen/sw_20dp"
android:gravity="center"
android:text="@string/cancel_account_delete"
android:textColor="#FFFFFF"
android:textSize="@dimen/sw_16sp"
android:textStyle="bold"
android:background="@drawable/bg_black_round_8dp" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,54 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="@dimen/sw_24dp"
android:background="@drawable/bg_dialog_round"
android:layout_marginStart="@dimen/sw_24dp"
android:layout_marginEnd="@dimen/sw_24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 标题 -->
<TextView
android:text="@string/cancel_account_confirm_title"
android:textSize="@dimen/sw_18sp"
android:textStyle="bold"
android:textColor="#222222"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- 描述 -->
<TextView
android:layout_marginTop="@dimen/sw_12dp"
android:text="@string/cancel_account_confirm_msg"
android:textSize="@dimen/sw_14sp"
android:textColor="#666666"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- 按钮区 -->
<LinearLayout
android:layout_marginTop="@dimen/sw_24dp"
android:orientation="horizontal"
android:gravity="end"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/btn_cancel"
android:text="@string/cancel"
android:textSize="@dimen/sw_14sp"
android:textColor="#666666"
android:padding="@dimen/sw_12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/btn_confirm"
android:text="@string/cancel_account_confirm_btn"
android:textSize="@dimen/sw_14sp"
android:textColor="#F44336"
android:padding="@dimen/sw_12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@@ -508,18 +508,6 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/logout"
android:layout_width="match_parent"
android:layout_height="@dimen/sw_63dp"
android:layout_marginTop="@dimen/sw_20dp"
android:layout_marginBottom="@dimen/sw_20dp"
android:gravity="center"
android:text="@string/mine_logout"
android:textColor="#FF0000"
android:textSize="@dimen/sw_16sp"
android:textStyle="bold"
android:background="@drawable/settings"/>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -115,7 +115,7 @@
android:layout_marginTop="@dimen/sw_24dp" android:layout_marginTop="@dimen/sw_24dp"
android:background="@drawable/settings" android:background="@drawable/settings"
android:orientation="vertical"> android:orientation="vertical">
<!-- Nickname --> <!-- 昵称 -->
<LinearLayout <LinearLayout
android:id="@+id/row_nickname" android:id="@+id/row_nickname"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -159,7 +159,7 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<!-- Gender --> <!-- 性别 -->
<LinearLayout <LinearLayout
android:id="@+id/row_gender" android:id="@+id/row_gender"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -247,6 +247,57 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/sw_24dp"
android:background="@drawable/settings"
android:orientation="vertical">
<!-- 注销账号 -->
<LinearLayout
android:id="@+id/row_cancel_account"
android:layout_width="match_parent"
android:layout_height="@dimen/sw_64dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/sw_16dp"
android:text="@string/personal_settings_cancel_account"
android:textColor="#1B1F1A"
android:textStyle="bold"
android:textSize="@dimen/sw_16sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/sw_10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_userid_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text=""
android:gravity="end"
android:textColor="#1B1F1A"
android:textStyle="bold"
android:layout_weight="1"
android:textSize="@dimen/sw_16sp" />
<ImageView
android:layout_width="@dimen/sw_9dp"
android:layout_height="@dimen/sw_13dp"
android:tint="#AFAFAF"
android:layout_marginStart="@dimen/sw_12dp"
android:layout_marginEnd="@dimen/sw_16dp"
android:src="@drawable/more_icons" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
@@ -290,5 +341,21 @@
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 退出登录按钮,固定在页面底部 -->
<TextView
android:id="@+id/logout"
android:layout_width="match_parent"
android:layout_height="@dimen/sw_63dp"
android:layout_marginStart="@dimen/sw_16dp"
android:layout_marginEnd="@dimen/sw_16dp"
android:layout_marginTop="@dimen/sw_10dp"
android:layout_marginBottom="@dimen/sw_20dp"
android:gravity="center"
android:text="@string/mine_logout"
android:textColor="#FF0000"
android:textSize="@dimen/sw_16sp"
android:textStyle="bold"
android:background="@drawable/settings"/>
</LinearLayout> </LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -93,6 +93,13 @@
android:label="Personal Settings" android:label="Personal Settings"
tools:layout="@layout/personal_settings" /> tools:layout="@layout/personal_settings" />
<!-- 注销账号 -->
<fragment
android:id="@+id/CancelAccount"
android:name="com.example.myapplication.ui.mine.myotherpages.CancelAccount"
android:label="Cancel Account"
tools:layout="@layout/cancel_account" />
<!-- 键盘设置 --> <!-- 键盘设置 -->
<fragment <fragment
android:id="@+id/MyKeyboard" android:id="@+id/MyKeyboard"

View File

@@ -56,6 +56,9 @@
<string name="Logout_confirm_title">确认退出登录</string> <string name="Logout_confirm_title">确认退出登录</string>
<string name="Logout_confirm_msg">您确认退出登录吗?</string> <string name="Logout_confirm_msg">您确认退出登录吗?</string>
<string name="Logout_confirm_btn">退出登录</string> <string name="Logout_confirm_btn">退出登录</string>
<string name="cancel_account_confirm_title">确认注销账号</string>
<string name="cancel_account_confirm_msg">注销后账号将被停用,并清除本地登录数据,是否继续?</string>
<string name="cancel_account_confirm_btn">确认</string>
<!-- 个人设置页面 --> <!-- 个人设置页面 -->
<string name="personal_settings_title">个人设置</string> <string name="personal_settings_title">个人设置</string>
<string name="personal_settings_nickname">昵称</string> <string name="personal_settings_nickname">昵称</string>
@@ -68,6 +71,11 @@
<string name="personal_choose_from_gallery">从相册选择</string> <string name="personal_choose_from_gallery">从相册选择</string>
<string name="personal_take_photo">拍照</string> <string name="personal_take_photo">拍照</string>
<string name="personal_upload_failed">上传失败</string> <string name="personal_upload_failed">上传失败</string>
<string name="personal_settings_cancel_account">注销账号</string>
<!-- 注销页面 -->
<string name="cancel_account_instruction">注销账户须知</string>
<string name="cancel_account_title">注销账号</string>
<string name="cancel_account_delete">确认注销账户</string>
<!-- 键盘人设 --> <!-- 键盘人设 -->
<string name="keyboard_title">我的人设</string> <string name="keyboard_title">我的人设</string>
<string name="keyboard_delete_hint">删除人设</string> <string name="keyboard_delete_hint">删除人设</string>

View File

@@ -57,6 +57,9 @@
<string name="Logout_confirm_title">Confirm logging out</string> <string name="Logout_confirm_title">Confirm logging out</string>
<string name="Logout_confirm_msg">Are you sure you want to log out?</string> <string name="Logout_confirm_msg">Are you sure you want to log out?</string>
<string name="Logout_confirm_btn">Log out</string> <string name="Logout_confirm_btn">Log out</string>
<string name="cancel_account_confirm_title">Confirm account cancellation</string>
<string name="cancel_account_confirm_msg">After cancellation, your account will be deactivated and local login data will be cleared. Continue?</string>
<string name="cancel_account_confirm_btn">Confirm</string>
<!-- 个人设置页面 --> <!-- 个人设置页面 -->
@@ -71,6 +74,11 @@
<string name="personal_choose_from_gallery">Select from the photo album</string> <string name="personal_choose_from_gallery">Select from the photo album</string>
<string name="personal_take_photo">Take a photo</string> <string name="personal_take_photo">Take a photo</string>
<string name="personal_upload_failed">Upload failed</string> <string name="personal_upload_failed">Upload failed</string>
<string name="personal_settings_cancel_account">Cancel account</string>
<!-- 注销页面 -->
<string name="cancel_account_instruction">Cancel Account Notice</string>
<string name="cancel_account_title">Cancel Account</string>
<string name="cancel_account_delete">Confirm Cancel Account</string>
<!-- 键盘人设 --> <!-- 键盘人设 -->
<string name="keyboard_title">My keyboard</string> <string name="keyboard_title">My keyboard</string>
<string name="keyboard_delete_hint">Delete character design</string> <string name="keyboard_delete_hint">Delete character design</string>