田子悦 7 days ago
parent ad929e15b1
commit ccedbc10df

@ -1,2 +1,2 @@
#Fri Apr 25 19:53:19 CST 2025 #Wed Jun 04 10:47:17 CST 2025
gradle.version=8.5 gradle.version=8.5

@ -1,2 +0,0 @@
#Sat May 24 23:39:46 CST 2025
java.home=D\:\\Andr\\jbr

Binary file not shown.

@ -3,6 +3,18 @@
<component name="DeviceStreaming"> <component name="DeviceStreaming">
<option name="deviceSelectionList"> <option name="deviceSelectionList">
<list> <list>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="Sony" />
<option name="codename" value="A402SO" />
<option name="id" value="A402SO" />
<option name="labId" value="google" />
<option name="manufacturer" value="Sony" />
<option name="name" value="Xperia 10" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2520" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="27" /> <option name="api" value="27" />
<option name="brand" value="DOCOMO" /> <option name="brand" value="DOCOMO" />
@ -207,6 +219,18 @@
<option name="screenX" value="960" /> <option name="screenX" value="960" />
<option name="screenY" value="2142" /> <option name="screenY" value="2142" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="caiman" />
<option name="id" value="caiman" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro" />
<option name="screenDensity" value="360" />
<option name="screenX" value="960" />
<option name="screenY" value="2142" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="34" /> <option name="api" value="34" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@ -269,6 +293,18 @@
<option name="screenX" value="1440" /> <option name="screenX" value="1440" />
<option name="screenY" value="3088" /> <option name="screenY" value="3088" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="motorola" />
<option name="codename" value="dubai" />
<option name="id" value="dubai" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="edge 30" />
<option name="screenDensity" value="405" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="34" /> <option name="api" value="34" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />
@ -536,6 +572,18 @@
<option name="screenX" value="1080" /> <option name="screenX" value="1080" />
<option name="screenY" value="2400" /> <option name="screenY" value="2400" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="samsung" />
<option name="codename" value="pa3q" />
<option name="id" value="pa3q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S25 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3120" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="33" /> <option name="api" value="33" />
<option name="brand" value="google" /> <option name="brand" value="google" />

@ -38,6 +38,7 @@
android:name=".SignupActivity" android:name=".SignupActivity"
android:exported="false" /> android:exported="false" />
<activity android:name=".ExerciseDetailActivity" /> <activity android:name=".ExerciseDetailActivity" />
<activity android:name=".EditProfileActivity"/>
</application> </application>
</manifest> </manifest>

@ -0,0 +1,141 @@
package org.tensorflow.lite.examples.poseestimation
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
class EditProfileActivity : AppCompatActivity() {
private lateinit var editUsername: EditText
private lateinit var editSignature: EditText
private lateinit var imageAvatar: ImageView
private lateinit var btnSelectPhoto: ImageView
private lateinit var btnSave: com.google.android.material.button.MaterialButton
private lateinit var btnBack: ImageView
private var currentUsername: String? = null
private var selectedImageUri: Uri? = null
// 用于启动相册选择图片并获取结果
private val selectPhotoLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val imageUri = result.data?.data
imageUri?.let {
// 在ImageView中显示选中的图片
imageAvatar.setImageURI(it)
selectedImageUri = it
// TODO: 在这里添加保存头像图片的逻辑例如保存URI或将图片复制到内部存储
// TODO: 需要修改User或UserProfile表结构来存储头像路径
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_profile)
// 获取当前登录用户名
val prefs = getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
currentUsername = prefs.getString("username", null)
// 如果没有获取到用户名,说明未登录,提示并关闭页面
if (currentUsername == null) {
Toast.makeText(this, "未登录,无法编辑资料", Toast.LENGTH_SHORT).show()
finish()
return
}
// 绑定布局中的控件
editUsername = findViewById(R.id.edit_username)
editSignature = findViewById(R.id.edit_signature)
imageAvatar = findViewById(R.id.image_avatar)
btnSelectPhoto = findViewById(R.id.btn_select_photo)
btnSave = findViewById(R.id.btn_save)
btnBack = findViewById(R.id.btn_back)
// 加载并显示当前用户信息
loadUserProfile()
// 设置选择头像按钮的点击事件
btnSelectPhoto.setOnClickListener {
openGallery()
}
// 设置保存按钮的点击事件
btnSave.setOnClickListener {
saveProfile()
}
// 设置返回按钮的点击事件
btnBack.setOnClickListener {
finish() // 结束当前Activity返回上一个Activity即SettingFragment所在的Activity
}
}
// 从数据库加载用户数据并显示
private fun loadUserProfile() {
currentUsername?.let { username ->
lifecycleScope.launch(Dispatchers.IO) {
val db = AppDatabase.getDatabase(this@EditProfileActivity)
// 使用getUserByUsername查询User对象其中包含signature字段
val user = db.userDao().getUserByUsername(username).first()
withContext(Dispatchers.Main) {
if (user != null) {
editUsername.setText(user.username)
editSignature.setText(user.signature)
// TODO: 添加加载用户头像的逻辑(如果已保存)
// TODO: 需要从数据库或SharedPreferences获取头像路径并加载到imageAvatar
} else {
// 用户不存在,这通常不应该发生如果已经登录
Toast.makeText(this@EditProfileActivity, "加载用户信息失败", Toast.LENGTH_SHORT).show()
}
}
}
}
}
// 打开相册选择图片
private fun openGallery() {
val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
selectPhotoLauncher.launch(galleryIntent)
}
// 保存修改后的个人资料
private fun saveProfile() {
currentUsername?.let { username ->
val newSignature = editSignature.text.toString().trim()
// 用户名暂时不可编辑,所以只更新个性签名
lifecycleScope.launch(Dispatchers.IO) {
val db = AppDatabase.getDatabase(this@EditProfileActivity)
// 调用UserDao中的updateSignature方法更新个性签名
db.userDao().updateSignature(username, newSignature)
// 保存头像URI到数据库
db.userDao().updateAvatarUri(username, selectedImageUri?.toString())
withContext(Dispatchers.Main) {
Toast.makeText(this@EditProfileActivity, "资料保存成功", Toast.LENGTH_SHORT).show()
// 设置Result为RESULT_OK通知SettingFragment数据已更新 (可选取决于SettingFragment是否需要知道保存成功)
setResult(Activity.RESULT_OK)
finish() // 保存成功后结束当前Activity返回Setting页面
}
}
}
}
}

@ -7,6 +7,7 @@ import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Button import android.widget.Button
import android.view.View
class ExerciseDetailActivity : AppCompatActivity() { class ExerciseDetailActivity : AppCompatActivity() {
@ -27,7 +28,7 @@ class ExerciseDetailActivity : AppCompatActivity() {
// 设置页面内容 // 设置页面内容
exerciseNameFromIntent?.let { name -> exerciseNameFromIntent?.let { name ->
exerciseName.text = name exerciseName.text = name
// 根据动作名称设置对应的图片 // 根据动作名称设置对应的图片和描述
val imageResId = when (name) { val imageResId = when (name) {
"硬拉" -> R.drawable.deadlift "硬拉" -> R.drawable.deadlift
"深蹲" -> R.drawable.deep_squats "深蹲" -> R.drawable.deep_squats
@ -37,6 +38,20 @@ class ExerciseDetailActivity : AppCompatActivity() {
else -> R.drawable.placeholder_image else -> R.drawable.placeholder_image
} }
exerciseImage.setImageResource(imageResId) exerciseImage.setImageResource(imageResId)
val descriptionText = when (name) {
"硬拉" -> "硬拉是锻炼下背部、臀部和腿部后侧链肌群的重要力量训练动作,开始时双脚与肩同宽或略窄,脚尖略微外展,杠铃置于胫骨中上段前方,双手采用正握或一正一反握法,宽度略宽于肩,背部保持自然挺直,胸部抬起,下背部收紧,避免弓背或塌腰,动作启动时臀部稍下降,膝盖微屈,通过脚掌发力\"推地\",将杠铃沿小腿贴近身体的方向上拉,直至身体完全伸直,站起时肩膀向后收拢,但不要过度后仰,放下时先屈髋再屈膝,保持背部紧绷,控制杠铃缓慢下降,整个过程中保持重心在脚中部,呼吸节奏为提起时呼气,放下时吸气,硬拉强调臀腿力量带动全身发力,而不是单纯弯腰提重物,新手应从空杆开始,掌握正确动作模式后再逐渐增加重量,以避免受伤。"
"深蹲" -> "深蹲是提升下肢力量和肌肉耐力的重要训练动作,主要锻炼股四头肌、臀大肌、腘绳肌及核心肌群。进行深蹲时,双脚与肩同宽或略宽,脚尖略微外展,双手可伸直前平举、抱头或交叉胸前以保持平衡,屈髋屈膝缓慢下蹲,想象臀部向后下方坐下,膝盖弯曲至大腿与地面平行或更低,过程中始终保持背部挺直、核心收紧,避免膝盖内扣或过度前伸,脚跟始终贴地发力,起身时脚掌踩实地面,臀部向前上方收缩,将身体推回起始位置,整个动作应控制节奏,避免借力或猛冲,呼吸上通常是在下蹲时吸气,起身时呼气,初学者建议从徒手开始,掌握正确姿势后再逐步增加负重,确保动作标准以避免受伤。"
"平板支撑" -> "平板支撑是一项强化核心肌群的经典训练动作主要锻炼腹部、下背部和骨盆周围的稳定肌群能够有效提升身体的整体控制力和平衡能力。进行时应以小臂和脚尖作为支撑点手肘位于肩膀正下方前臂与上臂呈90度夹角身体从头到脚踝保持一条直线避免臀部抬得过高或塌陷下去防止出现弓背或塌腰的现象。整个过程中要始终保持核心收紧尤其是腹部发力帮助维持身体稳定同时注意呼吸节奏不要憋气通常采用均匀的腹式呼吸方式。双脚并拢或略微分开脚尖着地以增加稳定性身体重心落在躯干中部避免用手臂或肩膀过多承重。初学者可以从20-30秒开始随着力量增强逐渐延长时间但应以动作标准为前提避免因坚持时间过长而牺牲姿势正确性。坚持练习可以明显提升核心力量、改善体态并为其他运动动作打下良好的基础。"
"引体向上" -> "引体向上是锻炼背部、手臂和核心肌群的重要复合动作,主要依靠背阔肌发力带动身体上升。开始时双手宽握单杠,手掌朝外,身体自然下垂,双脚可交叉或并拢,保持稳定。动作过程中,利用背部力量将身体向上拉起,避免借助惯性或摆动,肩胛骨收紧下沉,胸部尽量靠近单杠,同时保持核心收紧,避免腰部过度晃动或弓背、塌腰。在最高点稍作停顿后,缓慢控制身体下降至手臂完全伸直,不要突然放松或耸肩。整个过程以背阔肌为主导,肱二头肌辅助发力,初学者若力量不足可借助弹力带辅助或进行反向慢速下落来逐步提升力量,同时注意动作节奏与呼吸配合,上拉时呼气,下降时吸气,确保动作标准比完成次数更重要,以避免受伤并有效激活目标肌群。"
"俯卧撑" -> "俯卧撑是一项经典的自重训练动作主要锻炼胸大肌、三角肌前束和肱三头肌同时也能强化核心稳定性。进行时双手略宽于肩撑地手指朝前或微外展身体从头到脚呈一条直线避免塌腰或翘臀核心肌群始终保持收紧状态。下降过程中控制身体缓慢下压肘部向后约45度弯曲胸部接近地面但不贴地随后以手掌为支点推起身体回到起始位置注意保持动作稳定不要借助惯性或弓背塌腰。呼吸节奏应配合动作下落时吸气推起时呼气避免憋气影响发力。初学者可根据自身力量选择跪姿俯卧撑或斜式俯卧撑作为过渡逐步提升至标准动作。俯卧撑的关键在于动作质量而非数量保持正确姿势不仅能提高训练效果还能有效预防受伤。坚持练习可增强上肢力量、提升身体协调性和耐力是居家锻炼不可或缺的基础动作之一。"
// TODO: 添加其他运动的描述
else -> "暂无详细描述。"
}
exerciseDescription.text = descriptionText
// 显示描述 TextView
exerciseDescription.visibility = View.VISIBLE
} }
// 设置返回按钮点击事件 // 设置返回按钮点击事件

@ -54,6 +54,8 @@ class LoginActivity : AppCompatActivity() {
val user = db.userDao().getUser(username, password).first() val user = db.userDao().getUser(username, password).first()
if (user != null) { if (user != null) {
// 登录成功 // 登录成功
val prefs = getSharedPreferences("user_prefs", MODE_PRIVATE)
prefs.edit().putString("username", username).apply()
val intent = Intent(this@LoginActivity, org.tensorflow.lite.examples.poseestimation.MainTabActivity::class.java) val intent = Intent(this@LoginActivity, org.tensorflow.lite.examples.poseestimation.MainTabActivity::class.java)
intent.putExtra("selected_gender", selectedGender) intent.putExtra("selected_gender", selectedGender)
intent.putExtra("selected_age", selectedAge) intent.putExtra("selected_age", selectedAge)

@ -1,17 +1,175 @@
package org.tensorflow.lite.examples.poseestimation package org.tensorflow.lite.examples.poseestimation
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.tensorflow.lite.examples.poseestimation.R import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.Dispatchers
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
import org.tensorflow.lite.examples.poseestimation.data.UserProfile
class SettingFragment : Fragment() { class SettingFragment : Fragment() {
private var isPersonalInfoExpanded = false
private lateinit var tvUsername: TextView
private lateinit var tvSignature: TextView
private lateinit var btnPersonalInfo: TextView
private lateinit var btnLogout: TextView
private lateinit var layoutPersonalDetail: View
private lateinit var tvGender: TextView
private lateinit var tvAge: TextView
private lateinit var tvWeight: TextView
private lateinit var tvHeight: TextView
private lateinit var btnAccountInfo: TextView
private lateinit var imageAvatar: ImageView
private var currentUsername: String? = null
// 注册用于启动EditProfileActivity并处理结果的Launcher
private val editProfileResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
// 如果EditProfileActivity返回成功结果重新加载用户数据
loadUserData()
// 如果个人信息当前是展开的,也重新加载个人详细信息
if (isPersonalInfoExpanded) {
loadPersonalInfo()
}
}
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
return inflater.inflate(R.layout.fragment_setting, container, false) return inflater.inflate(R.layout.fragment_setting, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val context = requireContext()
val db = AppDatabase.getDatabase(context)
// 获取当前登录用户名假设用SharedPreferences存储
val prefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
currentUsername = prefs.getString("username", null)?.trim()
if (currentUsername == null) {
Toast.makeText(context, "未登录,请重新登录", Toast.LENGTH_SHORT).show()
startActivity(Intent(context, LoginActivity::class.java))
requireActivity().finish()
return
}
// 绑定控件
tvUsername = view.findViewById(R.id.tv_username)
tvSignature = view.findViewById(R.id.tv_signature)
btnPersonalInfo = view.findViewById(R.id.btn_personal_info)
btnLogout = view.findViewById(R.id.btn_logout)
layoutPersonalDetail = view.findViewById(R.id.layout_personal_detail)
tvGender = view.findViewById(R.id.tv_gender)
tvAge = view.findViewById(R.id.tv_age)
tvWeight = view.findViewById(R.id.tv_weight)
tvHeight = view.findViewById(R.id.tv_height)
btnAccountInfo = view.findViewById(R.id.btn_account_info)
imageAvatar = view.findViewById(R.id.image_avatar)
// 页面创建时加载并显示用户数据
loadUserData()
// 账户信息点击事件使用Launcher启动EditProfileActivity
btnAccountInfo.setOnClickListener {
val intent = Intent(requireContext(), EditProfileActivity::class.java)
editProfileResultLauncher.launch(intent)
}
// 个人信息展开/收起
btnPersonalInfo.setOnClickListener {
isPersonalInfoExpanded = !isPersonalInfoExpanded
layoutPersonalDetail.visibility = if (isPersonalInfoExpanded) View.VISIBLE else View.GONE
if (isPersonalInfoExpanded) {
// 展开时加载个人详细信息
loadPersonalInfo()
}
}
// 注销
btnLogout.setOnClickListener {
prefs.edit().remove("username").apply()
startActivity(Intent(context, LoginActivity::class.java))
requireActivity().finish()
}
}
// 提取加载用户名和签名的逻辑
private fun loadUserData() {
currentUsername?.let { username ->
lifecycleScope.launch {
val db = AppDatabase.getDatabase(requireContext())
// 使用 Flow 查询,并在协程中收集第一个结果
val user = withContext(Dispatchers.IO) {
db.userDao().getUserByUsername(username).first()
}
// 在主线程更新UI
withContext(Dispatchers.Main) {
tvUsername.text = user?.username ?: "-"
tvSignature.text = user?.signature ?: "这个人很懒,什么都没写"
// 加载并显示用户头像
user?.avatarUri?.let { uriString ->
try {
val imageUri = Uri.parse(uriString)
imageAvatar.setImageURI(imageUri)
} catch (e: Exception) {
// 处理URI无效或图片加载失败的情况可以显示默认头像或日志
e.printStackTrace()
imageAvatar.setImageResource(R.drawable.placeholder_image) // 显示默认头像
}
} ?: run { // 如果avatarUri为null也显示默认头像
imageAvatar.setImageResource(R.drawable.placeholder_image)
}
}
}
}
}
// 提取加载个人详细信息的逻辑
private fun loadPersonalInfo() {
currentUsername?.let { username ->
lifecycleScope.launch {
val db = AppDatabase.getDatabase(requireContext())
// 使用 Flow 进行单次查询
val profile = withContext(Dispatchers.IO) {
db.userProfileDao().getUserProfileByUsername(username).firstOrNull()
}
// 在主线程更新UI
withContext(Dispatchers.Main) {
if (profile != null) {
tvGender.text = "性别:${profile.gender}"
tvAge.text = "年龄:${profile.age}"
tvWeight.text = "体重:${profile.weight} kg"
tvHeight.text = "身高:${profile.height} cm"
} else {
tvGender.text = "性别:-"
tvAge.text = "年龄:-"
tvWeight.text = "体重:-"
tvHeight.text = "身高:-"
}
}
}
}
}
} }

@ -13,6 +13,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.tensorflow.lite.examples.poseestimation.data.AppDatabase import org.tensorflow.lite.examples.poseestimation.data.AppDatabase
import org.tensorflow.lite.examples.poseestimation.data.User import org.tensorflow.lite.examples.poseestimation.data.User
import org.tensorflow.lite.examples.poseestimation.data.UserProfile
class SignupActivity : AppCompatActivity() { class SignupActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -72,6 +73,10 @@ class SignupActivity : AppCompatActivity() {
val newUser = User(username, password) val newUser = User(username, password)
db.userDao().insertUser(newUser) db.userDao().insertUser(newUser)
// 同步插入user_profiles表默认值
val newProfile = UserProfile(username, "-", 0, 0, 0)
db.userProfileDao().insertUserProfile(newProfile)
runOnUiThread { runOnUiThread {
Toast.makeText(this@SignupActivity, "注册成功", Toast.LENGTH_SHORT).show() Toast.makeText(this@SignupActivity, "注册成功", Toast.LENGTH_SHORT).show()
// 注册成功后跳转到OnboardingActivity // 注册成功后跳转到OnboardingActivity

@ -5,7 +5,7 @@ import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
@Database(entities = [User::class, UserProfile::class], version = 2, exportSchema = false) @Database(entities = [User::class, UserProfile::class], version = 4, exportSchema = false)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao abstract fun userDao(): UserDao
abstract fun userProfileDao(): UserProfileDao abstract fun userProfileDao(): UserProfileDao

@ -7,5 +7,7 @@ import androidx.room.PrimaryKey
data class User( data class User(
@PrimaryKey @PrimaryKey
val username: String, val username: String,
val password: String val password: String,
val signature: String? = null,
val avatarUri: String? = null
) )

@ -15,4 +15,13 @@ interface UserDao {
@Query("SELECT * FROM users WHERE username = :username") @Query("SELECT * FROM users WHERE username = :username")
fun getUserByUsername(username: String): Flow<User?> fun getUserByUsername(username: String): Flow<User?>
@Query("UPDATE users SET signature = :signature WHERE username = :username")
fun updateSignature(username: String, signature: String)
@Query("SELECT signature FROM users WHERE username = :username")
fun getSignature(username: String): Flow<String?>
@Query("UPDATE users SET avatarUri = :avatarUri WHERE username = :username")
fun updateAvatarUri(username: String, avatarUri: String?)
} }

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#222" />
<stroke android:width="4dp" android:color="#FFA500" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1C1C1E"
android:orientation="vertical">
<!-- 顶部标题栏 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:paddingHorizontal="16dp">
<ImageView
android:id="@+id/btn_back"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerVertical="true"
android:src="@drawable/ic_back"
app:tint="#FFF"
android:contentDescription="Back" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Edit Profile"
android:textColor="#FFF"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
<!-- 头像区域 -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp">
<ImageView
android:id="@+id/image_avatar"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/avatar_circle_bg"
android:scaleType="centerCrop"
android:src="@drawable/placeholder_image"
android:contentDescription="User Avatar"/> <!-- 默认头像 -->
<ImageView
android:id="@+id/btn_select_photo"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="bottom|end"
android:layout_margin="4dp"
android:background="@drawable/circle_button_background"
android:padding="8dp"
android:src="@drawable/ic_back"
app:tint="#FFF"
android:contentDescription="Select Photo"/> <!-- 注意这里的ic_back需要替换为你项目中实际的相机图标资源例如camera.png -->
</FrameLayout>
<!-- 用户名和个性签名输入框 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:orientation="vertical"
android:paddingHorizontal="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户名"
android:textColor="#AAA"
android:textSize="14sp" />
<EditText
android:id="@+id/edit_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/transparent"
android:textColor="#FFF"
android:textSize="18sp"
android:enabled="false"
android:focusable="false"
android:focusableInTouchMode="false"/> <!-- 用户名暂时设置为不可编辑 -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="#333" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="个性签名"
android:textColor="#AAA"
android:textSize="14sp" />
<EditText
android:id="@+id/edit_signature"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@android:color/transparent"
android:textColor="#FFF"
android:textSize="18sp"
android:hint="添加个性签名"
android:textColorHint="#555"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="#333" />
</LinearLayout>
<!-- 保存按钮 -->
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="24dp"
android:text="Save"
android:textColor="#FFF"
android:textSize="18sp"
app:cornerRadius="28dp" />
</LinearLayout>

@ -55,7 +55,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="#CCCCCC" android:textColor="#CCCCCC"
android:textSize="16sp" android:textSize="18sp"
android:text="这里是动作的详细描述和注意事项..." /> android:text="这里是动作的详细描述和注意事项..." />
</ScrollView> </ScrollView>

@ -1,14 +1,116 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#1C1C1E"> android:orientation="vertical"
android:background="#1C1C1E"
android:padding="24dp">
<TextView <!-- 用户头像和信息区 -->
android:layout_width="wrap_content" <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="设置页面" android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingBottom="24dp">
<ImageView
android:id="@+id/image_avatar"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/placeholder_image"
android:background="@drawable/avatar_circle_bg"
android:scaleType="centerCrop" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingStart="16dp">
<TextView
android:id="@+id/tv_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用户名"
android:textColor="#FFF"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="个性签名"
android:textColor="#AAA"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
<!-- 菜单区 -->
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#333" />
<!-- 个人信息按钮和展开区包裹在一起 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/btn_personal_info"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:text="个人信息"
android:textColor="#FFF"
android:textSize="20sp" />
<!-- 个人信息展开区 -->
<LinearLayout
android:id="@+id/layout_personal_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:padding="12dp"
android:background="#222">
<TextView android:id="@+id/tv_gender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="性别:" android:textColor="#FFF" android:textSize="18sp" />
<TextView android:id="@+id/tv_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="年龄:" android:textColor="#FFF" android:textSize="18sp" />
<TextView android:id="@+id/tv_weight" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="体重:" android:textColor="#FFF" android:textSize="18sp" />
<TextView android:id="@+id/tv_height" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="身高:" android:textColor="#FFF" android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
<!-- 账户信息和设置按钮 -->
<TextView
android:id="@+id/btn_account_info"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:text="账户信息"
android:textColor="#FFF"
android:textSize="20sp" />
<TextView
android:id="@+id/btn_setting"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:text="设置"
android:textColor="#FFF" android:textColor="#FFF"
android:textSize="24sp" android:textSize="20sp" />
android:layout_gravity="center"/> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#333" />
</FrameLayout>
<View android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" />
<!-- 注销按钮 -->
<TextView
android:id="@+id/btn_logout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center"
android:text="注销"
android:textColor="#FF3B30"
android:textSize="20sp"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
Loading…
Cancel
Save