修改头像

This commit is contained in:
2026-05-26 11:50:01 +08:00
parent 5b7d20d38c
commit c073c88e10
7 changed files with 145 additions and 144 deletions

View File

@@ -0,0 +1,13 @@
package com.example.defenseapplication.bean
data class UploadResponse(
val code: Int,
val msg: String,
val data: UploadData?
)
data class UploadData(
val url: String,
val fileName: String?,
val ossId: String?
)

View File

@@ -4,8 +4,6 @@ import android.Manifest
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Bundle
@@ -26,16 +24,19 @@ import com.example.defenseapplication.R
import com.example.defenseapplication.bean.UpdateUserInfo
import com.example.defenseapplication.bean.UserInfoBean
import com.example.defenseapplication.databinding.PersonalProfileActivityBinding
import com.example.defenseapplication.utils.OssUtil
import com.example.defenseapplication.utils.RetrofitNetwork
import com.example.defenseapplication.view.AvatarSelectDialog
import com.example.defenseapplication.view.CustomDialogFragmentText
import com.example.defenseapplication.view.LoadingDialog
import com.example.defenseapplication.view.PhoneModifyDialogFragment
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@@ -51,6 +52,9 @@ class PersonalProfileActivity : AppCompatActivity() {
// 拍照返回的文件
private var photoFile: File? = null
// 加载弹窗
private lateinit var loadingDialog: LoadingDialog
// 请求权限回调
private val requestPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
@@ -116,6 +120,8 @@ class PersonalProfileActivity : AppCompatActivity() {
.statusBarDarkFont(true)
.titleBar(binding.topBar)
.init()
// 初始化加载弹窗
loadingDialog = LoadingDialog(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
userInfo = intent.getParcelableExtra("userInfo", UserInfoBean::class.java)
} else {
@@ -327,7 +333,6 @@ class PersonalProfileActivity : AppCompatActivity() {
".jpg",
storageDir
).apply {
// 保存文件路径以便后续使用
Log.d("PersonalProfile", "创建图片文件: $absolutePath")
}
}
@@ -352,7 +357,7 @@ class PersonalProfileActivity : AppCompatActivity() {
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val file = File(storageDir, "JPEG_${timeStamp}_temp.jpg")
FileOutputStream(file).use { outputStream ->
val buffer = ByteArray(1024)
var bytesRead: Int
@@ -370,18 +375,34 @@ class PersonalProfileActivity : AppCompatActivity() {
}
/**
* 上传头像到 OSS 并更新个人信息
* 上传头像到服务器并更新个人信息
*/
private fun uploadAvatar(filePath: String) {
Toast.makeText(this, getString(R.string.uploading), Toast.LENGTH_SHORT).show()
// 显示加载弹窗
loadingDialog.show()
OssUtil.uploadFileAsync(this, filePath, "avatar") { ossUrl ->
runOnUiThread {
if (ossUrl != null) {
Log.d("PersonalProfile", "OSS 上传成功URL: $ossUrl")
updateAvatar(ossUrl)
} else {
Toast.makeText(this@PersonalProfileActivity, getString(R.string.upload_failed), Toast.LENGTH_SHORT).show()
CoroutineScope(Dispatchers.IO).launch {
try {
val file = File(filePath)
val requestBody = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
val part = MultipartBody.Part.createFormData("file", file.name, requestBody)
val uploadResponse = RetrofitNetwork.getNetworkService().uploadFile(part)
withContext(Dispatchers.Main) {
if (uploadResponse.code == 200 && uploadResponse.data?.url != null) {
Log.d("PersonalProfile", "文件上传成功URL: ${uploadResponse.data?.url}")
updateAvatar(uploadResponse.data!!.url)
} else {
loadingDialog.dismiss()
Toast.makeText(this@PersonalProfileActivity, getString(R.string.upload_failed), Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
Toast.makeText(this@PersonalProfileActivity, getString(R.string.network_error), Toast.LENGTH_SHORT).show()
}
}
}
@@ -399,8 +420,8 @@ class PersonalProfileActivity : AppCompatActivity() {
)
val response = RetrofitNetwork.getNetworkService().updateUserInfo(request)
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
if (response.code == 200) {
// 更新本地头像显示
Glide.with(this@PersonalProfileActivity)
.load(avatarUrl)
.apply(RequestOptions()
@@ -417,6 +438,7 @@ class PersonalProfileActivity : AppCompatActivity() {
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
Toast.makeText(this@PersonalProfileActivity, getString(R.string.network_error), Toast.LENGTH_SHORT).show()
}
}

View File

@@ -19,10 +19,13 @@ import com.example.defenseapplication.bean.ObtainInviterInformationRequest
import com.example.defenseapplication.bean.SwitchFamilyRequest
import com.example.defenseapplication.bean.UpdatePassword
import com.example.defenseapplication.bean.UpdateUserInfo
import com.example.defenseapplication.bean.UploadResponse
import com.example.defenseapplication.bean.UserInfoBean
import com.example.defenseapplication.bean.VerifyOptionPasswordRequest
import okhttp3.MultipartBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Query
@@ -206,6 +209,12 @@ interface ApiService {
@POST("/app/revokeInvitation")
suspend fun revokeInvitation(@Body request: VerifyOptionPasswordRequest): GetCodeBean<Any>
/**
* 上传文件接口
*/
@Multipart
@POST("/app/upload")
suspend fun uploadFile(@retrofit2.http.Part file: MultipartBody.Part): UploadResponse
/**
* 修改家庭用户状态

View File

@@ -1,129 +0,0 @@
package com.example.defenseapplication.utils
import android.content.Context
import android.util.Log
import com.alibaba.sdk.android.oss.ClientConfiguration
import com.alibaba.sdk.android.oss.OSS
import com.alibaba.sdk.android.oss.OSSClient
import com.alibaba.sdk.android.oss.common.auth.OSSCredentialProvider
import com.alibaba.sdk.android.oss.common.auth.OSSPlainTextAKSKCredentialProvider
import com.alibaba.sdk.android.oss.model.PutObjectRequest
import com.alibaba.sdk.android.oss.model.PutObjectResult
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* OSS 工具类
* 用于上传文件到阿里云 OSS
*/
object OssUtil {
private const val TAG = "OssUtil"
// OSS 配置信息 - 需要根据实际情况配置
private const val ENDPOINT = "your-oss-endpoint" // 例如: oss-cn-hangzhou.aliyuncs.com
private const val ACCESS_KEY_ID = "your-access-key-id"
private const val ACCESS_KEY_SECRET = "your-access-key-secret"
private const val BUCKET_NAME = "your-bucket-name"
private var ossClient: OSS? = null
/**
* 初始化 OSS 客户端
*/
private fun initOssClient(context: Context): OSS {
if (ossClient == null) {
// 创建凭证提供者
val credentialProvider: OSSCredentialProvider =
OSSPlainTextAKSKCredentialProvider(ACCESS_KEY_ID, ACCESS_KEY_SECRET)
// 创建配置
val conf = ClientConfiguration().apply {
connectionTimeout = 15 * 1000 // 连接超时默认15秒
socketTimeout = 15 * 1000 // socket超时默认15秒
maxConcurrentRequest = 5 // 最大并发请求数默认5
maxErrorRetry = 2 // 失败后最大重试次数默认2
}
// 创建 OSS 客户端
ossClient = OSSClient(context.applicationContext, ENDPOINT, credentialProvider, conf)
}
return ossClient!!
}
/**
* 上传文件到 OSS
* @param context 上下文
* @param filePath 文件路径
* @param folderName 文件夹名称(可选,用于分类存储)
* @return OSS 文件 URL
*/
fun uploadFile(context: Context, filePath: String, folderName: String = "avatar"): String? {
return try {
val file = File(filePath)
if (!file.exists()) {
Log.e(TAG, "File not found: $filePath")
return null
}
// 生成文件名:时间戳 + 原文件名
val timestamp = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date())
val originalFileName = file.name
val fileName = "${timestamp}_${originalFileName}"
val objectKey = "$folderName/$fileName"
Log.d(TAG, "Uploading file: $filePath to OSS key: $objectKey")
// 创建上传请求
val putObjectRequest = PutObjectRequest(BUCKET_NAME, objectKey, filePath)
// 执行上传
val oss = initOssClient(context)
val result: PutObjectResult = oss.putObject(putObjectRequest)
Log.d(TAG, "Upload success, ETAG: ${result.eTag}")
// 返回文件 URL
"https://$BUCKET_NAME.$ENDPOINT/$objectKey"
} catch (e: Exception) {
Log.e(TAG, "Upload failed: ${e.message}", e)
null
}
}
/**
* 异步上传文件到 OSS
* @param context 上下文
* @param filePath 文件路径
* @param folderName 文件夹名称
* @param callback 回调函数
*/
fun uploadFileAsync(
context: Context,
filePath: String,
folderName: String = "avatar",
callback: (String?) -> Unit
) {
Thread {
val result = uploadFile(context, filePath, folderName)
callback(result)
}.start()
}
/**
* 配置 OSS 参数(动态配置)
* @param endpoint OSS 端点
* @param accessKeyId Access Key ID
* @param accessKeySecret Access Key Secret
* @param bucketName Bucket 名称
*/
fun setOssConfig(endpoint: String, accessKeyId: String, accessKeySecret: String, bucketName: String) {
// 由于常量无法修改,这里需要重新创建客户端
ossClient = null
// 注意:实际项目中应该使用动态配置,这里简化处理
Log.d(TAG, "OSS config updated")
}
}

View File

@@ -0,0 +1,52 @@
package com.example.defenseapplication.view
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.LayoutInflater
import android.view.WindowManager
import com.example.defenseapplication.databinding.DialogLoadingBinding
class LoadingDialog(context: Context) {
private val dialog: Dialog = Dialog(context)
private val binding: DialogLoadingBinding
init {
binding = DialogLoadingBinding.inflate(LayoutInflater.from(context))
dialog.setContentView(binding.root)
dialog.window?.apply {
setGravity(Gravity.CENTER)
setLayout(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT
)
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
setDimAmount(0.5f)
}
dialog.setCancelable(false)
}
fun setMessage(message: String): LoadingDialog {
binding.tvLoading.text = message
return this
}
fun show() {
if (!dialog.isShowing) {
dialog.show()
}
}
fun dismiss() {
if (dialog.isShowing) {
dialog.dismiss()
}
}
fun isShowing(): Boolean {
return dialog.isShowing
}
}