新增头像裁剪
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@
|
|||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
/.idea/*
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ dependencies {
|
|||||||
implementation("com.google.firebase:firebase-messaging-ktx")
|
implementation("com.google.firebase:firebase-messaging-ktx")
|
||||||
implementation ("cn.jiguang.sdk:jpush-google:5.4.0")
|
implementation ("cn.jiguang.sdk:jpush-google:5.4.0")
|
||||||
api ("com.tencent.imsdk:imsdk-plus:8.1.6116")
|
api ("com.tencent.imsdk:imsdk-plus:8.1.6116")
|
||||||
|
implementation("io.github.rroohit:ImageCropView:3.0.1")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import com.aiosman.riderpro.ui.account.ResetPasswordScreen
|
|||||||
import com.aiosman.riderpro.ui.chat.ChatScreen
|
import com.aiosman.riderpro.ui.chat.ChatScreen
|
||||||
import com.aiosman.riderpro.ui.comment.CommentsScreen
|
import com.aiosman.riderpro.ui.comment.CommentsScreen
|
||||||
import com.aiosman.riderpro.ui.comment.notice.CommentNoticeScreen
|
import com.aiosman.riderpro.ui.comment.notice.CommentNoticeScreen
|
||||||
|
import com.aiosman.riderpro.ui.crop.ImageCropScreen
|
||||||
import com.aiosman.riderpro.ui.favourite.FavouriteListPage
|
import com.aiosman.riderpro.ui.favourite.FavouriteListPage
|
||||||
import com.aiosman.riderpro.ui.favourite.FavouriteNoticeScreen
|
import com.aiosman.riderpro.ui.favourite.FavouriteNoticeScreen
|
||||||
import com.aiosman.riderpro.ui.follower.FollowerListScreen
|
import com.aiosman.riderpro.ui.follower.FollowerListScreen
|
||||||
@@ -88,6 +89,7 @@ sealed class NavigationRoute(
|
|||||||
data object FavouriteList : NavigationRoute("FavouriteList")
|
data object FavouriteList : NavigationRoute("FavouriteList")
|
||||||
data object Chat : NavigationRoute("Chat/{id}")
|
data object Chat : NavigationRoute("Chat/{id}")
|
||||||
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
|
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
|
||||||
|
data object ImageCrop : NavigationRoute("ImageCrop")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -361,6 +363,13 @@ fun NavigationController(
|
|||||||
CommentNoticeScreen()
|
CommentNoticeScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
composable(route = NavigationRoute.ImageCrop.route) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalAnimatedContentScope provides this,
|
||||||
|
) {
|
||||||
|
ImageCropScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.aiosman.riderpro.ui.account
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.aiosman.riderpro.data.AccountService
|
||||||
|
import com.aiosman.riderpro.data.AccountServiceImpl
|
||||||
|
import com.aiosman.riderpro.data.UploadImage
|
||||||
|
import com.aiosman.riderpro.entity.AccountProfileEntity
|
||||||
|
import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object AccountEditViewModel : ViewModel() {
|
||||||
|
var name by mutableStateOf("")
|
||||||
|
var bio by mutableStateOf("")
|
||||||
|
var imageUrl by mutableStateOf<Uri?>(null)
|
||||||
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
|
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
||||||
|
var croppedBitmap by mutableStateOf<Bitmap?>(null)
|
||||||
|
suspend fun reloadProfile() {
|
||||||
|
accountService.getMyAccountProfile().let {
|
||||||
|
profile = it
|
||||||
|
name = it.nickName
|
||||||
|
bio = it.bio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateUserProfile(context: Context) {
|
||||||
|
|
||||||
|
val newAvatar = croppedBitmap?.let {
|
||||||
|
val file = File(context.cacheDir, "avatar.jpg")
|
||||||
|
it.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream())
|
||||||
|
UploadImage(file, "avatar.jpg", "", "jpg")
|
||||||
|
}
|
||||||
|
val newName = if (name == profile?.nickName) null else name
|
||||||
|
accountService.updateProfile(
|
||||||
|
avatar = newAvatar,
|
||||||
|
banner = null,
|
||||||
|
nickName = newName,
|
||||||
|
bio = bio
|
||||||
|
)
|
||||||
|
// 刷新用户资料
|
||||||
|
reloadProfile()
|
||||||
|
// 刷新个人资料页面的用户资料
|
||||||
|
MyProfileViewModel.loadUserProfile()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
package com.aiosman.riderpro.ui.account
|
package com.aiosman.riderpro.ui.account
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -18,14 +14,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -35,47 +29,30 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.aiosman.riderpro.AppColors
|
import com.aiosman.riderpro.AppColors
|
||||||
import com.aiosman.riderpro.ConstVars
|
|
||||||
import com.aiosman.riderpro.LocalNavController
|
import com.aiosman.riderpro.LocalNavController
|
||||||
import com.aiosman.riderpro.R
|
import com.aiosman.riderpro.R
|
||||||
import com.aiosman.riderpro.data.AccountService
|
import com.aiosman.riderpro.ui.NavigationRoute
|
||||||
import com.aiosman.riderpro.data.AccountServiceImpl
|
|
||||||
import com.aiosman.riderpro.data.UploadImage
|
|
||||||
import com.aiosman.riderpro.entity.AccountProfileEntity
|
|
||||||
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
|
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
|
||||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
|
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
|
||||||
import com.aiosman.riderpro.ui.composables.form.FormTextInput
|
import com.aiosman.riderpro.ui.composables.form.FormTextInput
|
||||||
import com.aiosman.riderpro.ui.composables.pickupAndCompressLauncher
|
|
||||||
import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel
|
|
||||||
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
import com.aiosman.riderpro.ui.modifiers.noRippleClickable
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编辑用户资料界面
|
* 编辑用户资料界面
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountEditScreen2() {
|
fun AccountEditScreen2() {
|
||||||
val accountService: AccountService = AccountServiceImpl()
|
val model = AccountEditViewModel
|
||||||
var name by remember { mutableStateOf("") }
|
|
||||||
var bio by remember { mutableStateOf("") }
|
|
||||||
var imageUrl by remember { mutableStateOf<Uri?>(null) }
|
|
||||||
var imageFile by remember { mutableStateOf<File?>(null) }
|
|
||||||
var profile by remember {
|
|
||||||
mutableStateOf<AccountProfileEntity?>(
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var usernameError by remember { mutableStateOf<String?>(null) }
|
var usernameError by remember { mutableStateOf<String?>(null) }
|
||||||
var bioError by remember { mutableStateOf<String?>(null) }
|
var bioError by remember { mutableStateOf<String?>(null) }
|
||||||
fun onNicknameChange(value: String) {
|
fun onNicknameChange(value: String) {
|
||||||
name = value
|
model.name = value
|
||||||
usernameError = when {
|
usernameError = when {
|
||||||
value.isEmpty() -> "昵称不能为空"
|
value.isEmpty() -> "昵称不能为空"
|
||||||
value.length < 3 -> "昵称长度不能小于3"
|
value.length < 3 -> "昵称长度不能小于3"
|
||||||
@@ -85,7 +62,7 @@ fun AccountEditScreen2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onBioChange(value: String) {
|
fun onBioChange(value: String) {
|
||||||
bio = value
|
model.bio = value
|
||||||
bioError = when {
|
bioError = when {
|
||||||
value.length > 100 -> "个人简介长度不能大于24"
|
value.length > 100 -> "个人简介长度不能大于24"
|
||||||
else -> null
|
else -> null
|
||||||
@@ -96,76 +73,11 @@ fun AccountEditScreen2() {
|
|||||||
return usernameError == null && bioError == null
|
return usernameError == null && bioError == null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载用户资料
|
|
||||||
*/
|
|
||||||
suspend fun reloadProfile() {
|
|
||||||
accountService.getMyAccountProfile().let {
|
|
||||||
profile = it
|
|
||||||
name = it.nickName
|
|
||||||
bio = it.bio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新用户资料
|
|
||||||
*/
|
|
||||||
fun updateUserProfile() {
|
|
||||||
if (!validate()) {
|
|
||||||
Toast.makeText(context, "请检查输入", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scope.launch {
|
|
||||||
val newAvatar = imageUrl?.let {
|
|
||||||
// 检查图片文件
|
|
||||||
val avatarFile = imageFile ?: return@let null
|
|
||||||
// 读取文件名
|
|
||||||
val cursor = context.contentResolver.query(it, null, null, null, null)
|
|
||||||
var newAvatar: UploadImage? = null
|
|
||||||
cursor?.use { cur ->
|
|
||||||
val columnIndex = cur.getColumnIndex("_display_name")
|
|
||||||
if (columnIndex != -1 && cur.moveToFirst()) {
|
|
||||||
val displayName = cur.getString(columnIndex)
|
|
||||||
val extension = displayName.substringAfterLast(".")
|
|
||||||
Log.d("Profile Edit", "File name: $displayName, extension: $extension")
|
|
||||||
// read as file
|
|
||||||
Log.d("Profile Edit", "File size: ${avatarFile.length()}")
|
|
||||||
newAvatar = UploadImage(avatarFile, displayName, it.toString(), extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newAvatar
|
|
||||||
}
|
|
||||||
val newName = if (name == profile?.nickName) null else name
|
|
||||||
accountService.updateProfile(
|
|
||||||
avatar = newAvatar,
|
|
||||||
banner = null,
|
|
||||||
nickName = newName,
|
|
||||||
bio = bio
|
|
||||||
)
|
|
||||||
// 刷新用户资料
|
|
||||||
reloadProfile()
|
|
||||||
// 刷新个人资料页面的用户资料
|
|
||||||
MyProfileViewModel.loadUserProfile()
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val pickImageLauncher = pickupAndCompressLauncher(
|
|
||||||
context = context,
|
|
||||||
scope = scope,
|
|
||||||
maxSize = ConstVars.AVATAR_IMAGE_MAX_SIZE
|
|
||||||
) { uri, file ->
|
|
||||||
if (file.length() <= ConstVars.AVATAR_FILE_SIZE_LIMIT) {
|
|
||||||
imageUrl = uri
|
|
||||||
imageFile = file
|
|
||||||
} else {
|
|
||||||
scope.launch(Dispatchers.Main) {
|
|
||||||
Toast.makeText(context, "图片过大", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
reloadProfile()
|
if (model.profile == null){
|
||||||
|
model.reloadProfile()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -186,7 +98,10 @@ fun AccountEditScreen2() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
updateUserProfile()
|
model.viewModelScope.launch {
|
||||||
|
model.updateUserProfile(context)
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
imageVector = Icons.Default.Check,
|
imageVector = Icons.Default.Check,
|
||||||
contentDescription = "保存",
|
contentDescription = "保存",
|
||||||
@@ -195,14 +110,14 @@ fun AccountEditScreen2() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
profile?.let {
|
model.profile?.let {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(112.dp),
|
modifier = Modifier.size(112.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
context,
|
context,
|
||||||
imageUrl?.toString() ?: it.avatar,
|
model.croppedBitmap ?: it.avatar,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(112.dp)
|
.size(112.dp)
|
||||||
.clip(
|
.clip(
|
||||||
@@ -218,10 +133,7 @@ fun AccountEditScreen2() {
|
|||||||
.background(AppColors.mainColor)
|
.background(AppColors.mainColor)
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
Intent(Intent.ACTION_PICK).apply {
|
navController.navigate(NavigationRoute.ImageCrop.route)
|
||||||
type = "image/*"
|
|
||||||
pickImageLauncher.launch(this)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
@@ -240,7 +152,7 @@ fun AccountEditScreen2() {
|
|||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
FormTextInput(
|
FormTextInput(
|
||||||
value = name,
|
value = model.name,
|
||||||
label = stringResource(R.string.nickname),
|
label = stringResource(R.string.nickname),
|
||||||
hint = "Input nickname",
|
hint = "Input nickname",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -250,7 +162,7 @@ fun AccountEditScreen2() {
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
FormTextInput(
|
FormTextInput(
|
||||||
value = bio,
|
value = model.bio,
|
||||||
label = stringResource(R.string.bio),
|
label = stringResource(R.string.bio),
|
||||||
hint = "Input bio",
|
hint = "Input bio",
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ fun rememberImageBitmap(imageUrl: String, imageLoader: ImageLoader): Bitmap? {
|
|||||||
@Composable
|
@Composable
|
||||||
fun CustomAsyncImage(
|
fun CustomAsyncImage(
|
||||||
context: Context? = null,
|
context: Context? = null,
|
||||||
imageUrl: String?,
|
imageUrl: Any?,
|
||||||
contentDescription: String?,
|
contentDescription: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
blurHash: String? = null,
|
blurHash: String? = null,
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package com.aiosman.riderpro.ui.crop
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.aiosman.riderpro.LocalNavController
|
||||||
|
import com.aiosman.riderpro.R
|
||||||
|
import com.aiosman.riderpro.ui.account.AccountEditViewModel
|
||||||
|
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
|
||||||
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
|
import com.image.cropview.CropType
|
||||||
|
import com.image.cropview.EdgeType
|
||||||
|
import com.image.cropview.ImageCrop
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImageCropScreen() {
|
||||||
|
var imageCrop by remember { mutableStateOf<ImageCrop?>(null) }
|
||||||
|
val context = LocalContext.current
|
||||||
|
val configuration = LocalConfiguration.current
|
||||||
|
val screenWidth = configuration.screenWidthDp
|
||||||
|
var imageWidthInDp by remember { mutableStateOf(0) }
|
||||||
|
var imageHeightInDp by remember { mutableStateOf(0) }
|
||||||
|
var density = LocalDensity.current
|
||||||
|
var navController = LocalNavController.current
|
||||||
|
var imagePickLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.GetContent()
|
||||||
|
) { uri: Uri? ->
|
||||||
|
uri?.let {
|
||||||
|
val bitmap = uriToBitmap(context = context, uri = it)
|
||||||
|
if (bitmap != null) {
|
||||||
|
val aspectRatio = bitmap.height.toFloat() / bitmap.width.toFloat()
|
||||||
|
imageHeightInDp = (imageWidthInDp.toFloat() * aspectRatio).toInt()
|
||||||
|
imageCrop = ImageCrop(bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uri == null) {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val systemUiController = rememberSystemUiController()
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
systemUiController.setStatusBarColor(darkIcons = false, color = Color.Black)
|
||||||
|
imagePickLauncher.launch("image/*")
|
||||||
|
}
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
imageCrop = null
|
||||||
|
systemUiController.setStatusBarColor(darkIcons = true, color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.background(Color.Black).fillMaxSize()
|
||||||
|
) {
|
||||||
|
StatusBarSpacer()
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.rider_pro_nav_back),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
colorFilter = ColorFilter.tint(Color.White)
|
||||||
|
)
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
imageCrop?.let {
|
||||||
|
val bitmap = it.onCrop()
|
||||||
|
AccountEditViewModel.croppedBitmap = bitmap
|
||||||
|
AccountEditViewModel.viewModelScope.launch {
|
||||||
|
AccountEditViewModel.updateUserProfile(context)
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Spacer(
|
||||||
|
// modifier = Modifier.height(120.dp)
|
||||||
|
// )
|
||||||
|
// ActionButton(
|
||||||
|
// modifier = Modifier.fillMaxWidth(),
|
||||||
|
// text = "选择图片"
|
||||||
|
// ) {
|
||||||
|
// imagePickLauncher.launch("image/*")
|
||||||
|
// }
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(24.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(imageHeightInDp.dp)
|
||||||
|
.onGloballyPositioned {
|
||||||
|
with(density) {
|
||||||
|
imageWidthInDp = it.size.width.toDp().value.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
imageCrop?.ImageCropView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
guideLineColor = Color.White,
|
||||||
|
guideLineWidth = 2.dp,
|
||||||
|
edgeCircleSize = 5.dp,
|
||||||
|
cropType = CropType.SQUARE,
|
||||||
|
edgeType = EdgeType.CIRCULAR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Configure ImageCropView.
|
||||||
|
|
||||||
|
|
||||||
|
fun uriToBitmap(context: Context, uri: Uri): Bitmap? {
|
||||||
|
return try {
|
||||||
|
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
|
||||||
|
BitmapFactory.decodeStream(inputStream)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user