修改个人资料修改逻辑
This commit is contained in:
@@ -10,8 +10,25 @@ object ConstVars {
|
|||||||
const val MOMENT_LIKE_CHANNEL_ID = "moment_like"
|
const val MOMENT_LIKE_CHANNEL_ID = "moment_like"
|
||||||
const val MOMENT_LIKE_CHANNEL_NAME = "Moment Like"
|
const val MOMENT_LIKE_CHANNEL_NAME = "Moment Like"
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* 上传头像图片大小限制
|
||||||
|
* 10M
|
||||||
|
*/
|
||||||
|
const val AVATAR_FILE_SIZE_LIMIT = 1024 * 1024 * 10
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传头像图片压缩时最大的尺寸
|
||||||
|
* 512
|
||||||
|
*/
|
||||||
|
const val AVATAR_IMAGE_MAX_SIZE = 512
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传 banner 图片大小限制
|
||||||
|
*/
|
||||||
|
const val BANNER_IMAGE_MAX_SIZE = 2048
|
||||||
|
|
||||||
|
}
|
||||||
|
//
|
||||||
enum class ErrorCode(val code: Int) {
|
enum class ErrorCode(val code: Int) {
|
||||||
USER_EXIST(10001)
|
USER_EXIST(10001)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,25 @@
|
|||||||
package com.aiosman.riderpro.ui.account
|
package com.aiosman.riderpro.ui.account
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
|
||||||
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.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FloatingActionButton
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
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
|
||||||
@@ -47,14 +33,10 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
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.data.AccountService
|
||||||
@@ -64,21 +46,24 @@ 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.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 com.aiosman.riderpro.ui.post.NewPostViewModel.uriToFile
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编辑用户资料界面
|
* 编辑用户资料界面
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountEditScreen2() {
|
fun AccountEditScreen2() {
|
||||||
val accountService: AccountService = AccountServiceImpl()
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
var name by remember { mutableStateOf("") }
|
var name by remember { mutableStateOf("") }
|
||||||
var bio by remember { mutableStateOf("") }
|
var bio by remember { mutableStateOf("") }
|
||||||
var imageUrl by remember { mutableStateOf<Uri?>(null) }
|
var imageUrl by remember { mutableStateOf<Uri?>(null) }
|
||||||
var bannerImageUrl by remember { mutableStateOf<Uri?>(null) }
|
var imageFile by remember { mutableStateOf<File?>(null) }
|
||||||
var profile by remember {
|
var profile by remember {
|
||||||
mutableStateOf<AccountProfileEntity?>(
|
mutableStateOf<AccountProfileEntity?>(
|
||||||
null
|
null
|
||||||
@@ -87,6 +72,29 @@ fun AccountEditScreen2() {
|
|||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
var usernameError by remember { mutableStateOf<String?>(null) }
|
||||||
|
var bioError by remember { mutableStateOf<String?>(null) }
|
||||||
|
fun onNicknameChange(value: String) {
|
||||||
|
name = value
|
||||||
|
usernameError = when {
|
||||||
|
value.isEmpty() -> "昵称不能为空"
|
||||||
|
value.length < 3 -> "昵称长度不能小于3"
|
||||||
|
value.length > 20 -> "昵称长度不能大于20"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBioChange(value: String) {
|
||||||
|
bio = value
|
||||||
|
bioError = when {
|
||||||
|
value.length > 100 -> "个人简介长度不能大于24"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validate(): Boolean {
|
||||||
|
return usernameError == null && bioError == null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载用户资料
|
* 加载用户资料
|
||||||
@@ -99,69 +107,70 @@ fun AccountEditScreen2() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户资料
|
||||||
|
*/
|
||||||
fun updateUserProfile() {
|
fun updateUserProfile() {
|
||||||
|
if (!validate()) {
|
||||||
|
Toast.makeText(context, "请检查输入", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val newAvatar = imageUrl?.let {
|
val newAvatar = imageUrl?.let {
|
||||||
|
// 检查图片文件
|
||||||
|
val avatarFile = imageFile ?: return@let null
|
||||||
|
// 读取文件名
|
||||||
val cursor = context.contentResolver.query(it, null, null, null, null)
|
val cursor = context.contentResolver.query(it, null, null, null, null)
|
||||||
var newAvatar: UploadImage? = null
|
var newAvatar: UploadImage? = null
|
||||||
cursor?.use { cur ->
|
cursor?.use { cur ->
|
||||||
if (cur.moveToFirst()) {
|
val columnIndex = cur.getColumnIndex("_display_name")
|
||||||
val displayName = cur.getString(cur.getColumnIndex("_display_name"))
|
if (columnIndex != -1 && cur.moveToFirst()) {
|
||||||
|
val displayName = cur.getString(columnIndex)
|
||||||
val extension = displayName.substringAfterLast(".")
|
val extension = displayName.substringAfterLast(".")
|
||||||
Log.d("NewPost", "File name: $displayName, extension: $extension")
|
Log.d("Profile Edit", "File name: $displayName, extension: $extension")
|
||||||
// read as file
|
// read as file
|
||||||
val file = uriToFile(context, it)
|
Log.d("Profile Edit", "File size: ${avatarFile.length()}")
|
||||||
Log.d("NewPost", "File size: ${file.length()}")
|
newAvatar = UploadImage(avatarFile, displayName, it.toString(), extension)
|
||||||
newAvatar = UploadImage(file, displayName, it.toString(), extension)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newAvatar
|
newAvatar
|
||||||
}
|
}
|
||||||
var newBanner = bannerImageUrl?.let {
|
|
||||||
val cursor = context.contentResolver.query(it, null, null, null, null)
|
|
||||||
var newBanner: UploadImage? = null
|
|
||||||
cursor?.use { cur ->
|
|
||||||
if (cur.moveToFirst()) {
|
|
||||||
val displayName = cur.getString(cur.getColumnIndex("_display_name"))
|
|
||||||
val extension = displayName.substringAfterLast(".")
|
|
||||||
Log.d("NewPost", "File name: $displayName, extension: $extension")
|
|
||||||
// read as file
|
|
||||||
val file = uriToFile(context, it)
|
|
||||||
Log.d("NewPost", "File size: ${file.length()}")
|
|
||||||
newBanner = UploadImage(file, displayName, it.toString(), extension)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newBanner
|
|
||||||
}
|
|
||||||
val newName = if (name == profile?.nickName) null else name
|
val newName = if (name == profile?.nickName) null else name
|
||||||
accountService.updateProfile(
|
accountService.updateProfile(
|
||||||
avatar = newAvatar,
|
avatar = newAvatar,
|
||||||
banner = newBanner,
|
banner = null,
|
||||||
nickName = newName,
|
nickName = newName,
|
||||||
bio = bio
|
bio = bio
|
||||||
)
|
)
|
||||||
|
// 刷新用户资料
|
||||||
reloadProfile()
|
reloadProfile()
|
||||||
|
// 刷新个人资料页面的用户资料
|
||||||
|
MyProfileViewModel.loadUserProfile()
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val pickImageLauncher = rememberLauncherForActivityResult(
|
val pickImageLauncher = pickupAndCompressLauncher(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
context = context,
|
||||||
) { result ->
|
scope = scope,
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
maxSize = ConstVars.AVATAR_IMAGE_MAX_SIZE
|
||||||
val uri = result.data?.data
|
) { uri, file ->
|
||||||
uri?.let {
|
if (file.length() <= ConstVars.AVATAR_FILE_SIZE_LIMIT) {
|
||||||
imageUrl = it
|
imageUrl = uri
|
||||||
|
imageFile = file
|
||||||
|
} else {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
Toast.makeText(context, "图片过大", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
reloadProfile()
|
reloadProfile()
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize().background(Color.White),
|
.fillMaxSize()
|
||||||
|
.background(Color.White),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
StatusBarSpacer()
|
StatusBarSpacer()
|
||||||
@@ -180,25 +189,22 @@ fun AccountEditScreen2() {
|
|||||||
updateUserProfile()
|
updateUserProfile()
|
||||||
},
|
},
|
||||||
imageVector = Icons.Default.Check,
|
imageVector = Icons.Default.Check,
|
||||||
contentDescription = "保存"
|
contentDescription = "保存",
|
||||||
|
tint = if (validate()) Color.Black else Color.Gray
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
profile?.let {
|
profile?.let {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(width = 112.dp, height = 112.dp),
|
modifier = Modifier.size(112.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Image(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
painter = painterResource(id = R.drawable.avatar_bold), contentDescription = ""
|
|
||||||
)
|
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
context,
|
context,
|
||||||
imageUrl?.toString() ?: it.avatar,
|
imageUrl?.toString() ?: it.avatar,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 88.dp, height = 88.dp)
|
.size(112.dp)
|
||||||
.clip(
|
.clip(
|
||||||
RoundedCornerShape(88.dp)
|
RoundedCornerShape(88.dp)
|
||||||
),
|
),
|
||||||
@@ -230,72 +236,27 @@ fun AccountEditScreen2() {
|
|||||||
Spacer(modifier = Modifier.height(46.dp))
|
Spacer(modifier = Modifier.height(46.dp))
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
FormTextInput(
|
||||||
modifier = Modifier
|
value = name,
|
||||||
.clip(RoundedCornerShape(24.dp))
|
label = stringResource(R.string.nickname),
|
||||||
.background(Color(0xfff8f8f8))
|
hint = "Input nickname",
|
||||||
.padding(16.dp),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
error = usernameError
|
||||||
|
) { value ->
|
||||||
) {
|
onNicknameChange(value)
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.nickname),
|
|
||||||
modifier = Modifier
|
|
||||||
.widthIn(100.dp),
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = Color(0xFF333333)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
BasicTextField(
|
|
||||||
maxLines = 1,
|
|
||||||
value = name,
|
|
||||||
onValueChange = {
|
|
||||||
name = it
|
|
||||||
},
|
|
||||||
textStyle = TextStyle(
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.Normal
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(start = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Row(
|
FormTextInput(
|
||||||
modifier = Modifier
|
value = bio,
|
||||||
.clip(RoundedCornerShape(16.dp))
|
label = stringResource(R.string.bio),
|
||||||
.background(Color(0xfff8f8f8))
|
hint = "Input bio",
|
||||||
.padding(16.dp),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
error = bioError
|
||||||
) {
|
) { value ->
|
||||||
Text(
|
onBioChange(value)
|
||||||
text = stringResource(R.string.bio),
|
|
||||||
modifier = Modifier
|
|
||||||
.widthIn(100.dp),
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = Color(0xFF333333)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
BasicTextField(
|
|
||||||
value = bio,
|
|
||||||
onValueChange = {
|
|
||||||
bio = it
|
|
||||||
},
|
|
||||||
textStyle = TextStyle(
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.Normal
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(start = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.aiosman.riderpro.ui.composables
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import com.aiosman.riderpro.utils.Utils
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择图片并压缩
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun pickupAndCompressLauncher(
|
||||||
|
context: Context,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
maxSize: Int = 512,
|
||||||
|
quality: Int = 85,
|
||||||
|
onImagePicked: (Uri, File) -> Unit
|
||||||
|
) = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
val uri = result.data?.data
|
||||||
|
uri?.let {
|
||||||
|
scope.launch {
|
||||||
|
// Compress the image
|
||||||
|
val file = Utils.compressImage(context, it, maxSize = maxSize, quality = quality)
|
||||||
|
// Check the compressed image size
|
||||||
|
onImagePicked(it, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.aiosman.riderpro.ui.composables.form
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
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.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.aiosman.riderpro.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FormTextInput(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
value: String,
|
||||||
|
label: String? = null,
|
||||||
|
error: String? = null,
|
||||||
|
hint: String? = null,
|
||||||
|
onValueChange: (String) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(24.dp))
|
||||||
|
.background(Color(0xfff8f8f8))
|
||||||
|
.let {
|
||||||
|
if (error != null) {
|
||||||
|
it.border(1.dp, Color(0xFFE53935), RoundedCornerShape(24.dp))
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
label?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
modifier = Modifier
|
||||||
|
.widthIn(100.dp),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color(0xFF333333)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(start = 16.dp)
|
||||||
|
) {
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = hint ?: "",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
color = Color(0xFFCCCCCC)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
maxLines = 1,
|
||||||
|
value = value,
|
||||||
|
onValueChange = {
|
||||||
|
onValueChange(it)
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
textStyle = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Normal
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = error != null,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut()
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.rider_pro_input_error),
|
||||||
|
contentDescription = "Error",
|
||||||
|
modifier = Modifier.size(8.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.size(4.dp))
|
||||||
|
AnimatedContent(targetState = error) { targetError ->
|
||||||
|
Text(targetError ?: "", color = Color(0xFFE53935), fontSize = 12.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
object MyProfileViewModel : ViewModel() {
|
object MyProfileViewModel : ViewModel() {
|
||||||
val accountService: AccountService = AccountServiceImpl()
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
@@ -38,33 +39,39 @@ object MyProfileViewModel : ViewModel() {
|
|||||||
|
|
||||||
var refreshing by mutableStateOf(false)
|
var refreshing by mutableStateOf(false)
|
||||||
var firstLoad = true
|
var firstLoad = true
|
||||||
|
suspend fun loadUserProfile() {
|
||||||
|
val profile = accountService.getMyAccountProfile()
|
||||||
|
MyProfileViewModel.profile = profile
|
||||||
|
}
|
||||||
|
|
||||||
fun loadProfile(pullRefresh: Boolean = false) {
|
fun loadProfile(pullRefresh: Boolean = false) {
|
||||||
if (!firstLoad) return
|
if (!firstLoad && !pullRefresh) return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (pullRefresh) {
|
if (pullRefresh) {
|
||||||
refreshing = true
|
refreshing = true
|
||||||
}
|
}
|
||||||
firstLoad = false
|
firstLoad = false
|
||||||
val profile = accountService.getMyAccountProfile()
|
loadUserProfile()
|
||||||
MyProfileViewModel.profile = profile
|
|
||||||
refreshing = false
|
refreshing = false
|
||||||
try {
|
profile?.let {
|
||||||
// Collect shared flow
|
try {
|
||||||
Pager(
|
// Collect shared flow
|
||||||
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
Pager(
|
||||||
pagingSourceFactory = {
|
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
|
||||||
MomentPagingSource(
|
pagingSourceFactory = {
|
||||||
MomentRemoteDataSource(momentService),
|
MomentPagingSource(
|
||||||
author = profile.id
|
MomentRemoteDataSource(momentService),
|
||||||
)
|
author = AppState.UserId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
).flow.cachedIn(viewModelScope).collectLatest {
|
||||||
|
_sharedFlow.value = it
|
||||||
}
|
}
|
||||||
).flow.cachedIn(viewModelScope).collectLatest {
|
} catch (e: Exception) {
|
||||||
_sharedFlow.value = it
|
Log.e("MyProfileViewModel", "loadProfile: ", e)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("MyProfileViewModel", "loadProfile: ", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,19 +84,19 @@ object MyProfileViewModel : ViewModel() {
|
|||||||
AppState.ReloadAppState()
|
AppState.ReloadAppState()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateUserProfileBanner(bannerImageUrl: Uri?, context: Context) {
|
fun updateUserProfileBanner(bannerImageUrl: Uri?,file:File, context: Context) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
var newBanner = bannerImageUrl?.let {
|
val newBanner = bannerImageUrl?.let {
|
||||||
val cursor = context.contentResolver.query(it, null, null, null, null)
|
val cursor = context.contentResolver.query(it, null, null, null, null)
|
||||||
var newBanner: UploadImage? = null
|
var newBanner: UploadImage? = null
|
||||||
cursor?.use { cur ->
|
cursor?.use { cur ->
|
||||||
if (cur.moveToFirst()) {
|
val columnIndex = cur.getColumnIndex("_display_name")
|
||||||
val displayName = cur.getString(cur.getColumnIndex("_display_name"))
|
if (cur.moveToFirst() && columnIndex != -1) {
|
||||||
|
val displayName = cur.getString(columnIndex)
|
||||||
val extension = displayName.substringAfterLast(".")
|
val extension = displayName.substringAfterLast(".")
|
||||||
Log.d("NewPost", "File name: $displayName, extension: $extension")
|
Log.d("Change banner", "File name: $displayName, extension: $extension")
|
||||||
// read as file
|
// read as file
|
||||||
val file = uriToFile(context, it)
|
Log.d("Change banner", "File size: ${file.length()}")
|
||||||
Log.d("NewPost", "File size: ${file.length()}")
|
|
||||||
newBanner = UploadImage(file, displayName, it.toString(), extension)
|
newBanner = UploadImage(file, displayName, it.toString(), extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.ScrollState
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -27,17 +26,18 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.Text
|
|
||||||
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.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -47,28 +47,21 @@ 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
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.media3.common.util.Log
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import com.aiosman.riderpro.AppState
|
import com.aiosman.riderpro.AppState
|
||||||
|
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.entity.AccountProfileEntity
|
import com.aiosman.riderpro.entity.AccountProfileEntity
|
||||||
@@ -78,6 +71,7 @@ import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
|||||||
import com.aiosman.riderpro.ui.composables.DropdownMenu
|
import com.aiosman.riderpro.ui.composables.DropdownMenu
|
||||||
import com.aiosman.riderpro.ui.composables.MenuItem
|
import com.aiosman.riderpro.ui.composables.MenuItem
|
||||||
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
|
import com.aiosman.riderpro.ui.composables.StatusBarSpacer
|
||||||
|
import com.aiosman.riderpro.ui.composables.pickupAndCompressLauncher
|
||||||
import com.aiosman.riderpro.ui.composables.toolbar.CollapsingToolbarScaffold
|
import com.aiosman.riderpro.ui.composables.toolbar.CollapsingToolbarScaffold
|
||||||
import com.aiosman.riderpro.ui.composables.toolbar.ScrollStrategy
|
import com.aiosman.riderpro.ui.composables.toolbar.ScrollStrategy
|
||||||
import com.aiosman.riderpro.ui.composables.toolbar.rememberCollapsingToolbarScaffoldState
|
import com.aiosman.riderpro.ui.composables.toolbar.rememberCollapsingToolbarScaffoldState
|
||||||
@@ -94,11 +88,12 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileV3(
|
fun ProfileV3(
|
||||||
onUpdateBanner: ((Uri, Context) -> Unit)? = null,
|
onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,
|
||||||
profile: AccountProfileEntity? = null,
|
profile: AccountProfileEntity? = null,
|
||||||
onLogout: () -> Unit = {},
|
onLogout: () -> Unit = {},
|
||||||
onFollowClick: () -> Unit = {},
|
onFollowClick: () -> Unit = {},
|
||||||
@@ -108,6 +103,7 @@ fun ProfileV3(
|
|||||||
).asStateFlow(),
|
).asStateFlow(),
|
||||||
isSelf: Boolean = true
|
isSelf: Boolean = true
|
||||||
) {
|
) {
|
||||||
|
val model = MyProfileViewModel
|
||||||
val state = rememberCollapsingToolbarScaffoldState()
|
val state = rememberCollapsingToolbarScaffoldState()
|
||||||
val pagerState = rememberPagerState(pageCount = { 2 })
|
val pagerState = rememberPagerState(pageCount = { 2 })
|
||||||
var enabled by remember { mutableStateOf(true) }
|
var enabled by remember { mutableStateOf(true) }
|
||||||
@@ -117,19 +113,21 @@ fun ProfileV3(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
var bannerHeight = 400
|
var bannerHeight = 400
|
||||||
val pickBannerImageLauncher = rememberLauncherForActivityResult(
|
val pickBannerImageLauncher = pickupAndCompressLauncher(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
context,
|
||||||
) { result ->
|
scope,
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
maxSize = ConstVars.BANNER_IMAGE_MAX_SIZE,
|
||||||
val uri = result.data?.data
|
quality = 100
|
||||||
uri?.let {
|
) { uri, file ->
|
||||||
onUpdateBanner?.invoke(it, context)
|
onUpdateBanner?.invoke(uri, file, context)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val moments = sharedFlow.collectAsLazyPagingItems()
|
val moments = sharedFlow.collectAsLazyPagingItems()
|
||||||
|
val refreshState = rememberPullRefreshState(model.refreshing, onRefresh = {
|
||||||
Box {
|
model.loadProfile(pullRefresh = true)
|
||||||
|
})
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.pullRefresh(refreshState)
|
||||||
|
) {
|
||||||
CollapsingToolbarScaffold(
|
CollapsingToolbarScaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -447,5 +445,6 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
PullRefreshIndicator(model.refreshing, refreshState, Modifier.align(Alignment.TopCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ fun ProfileWrap(
|
|||||||
// sharedFlow = MyProfileViewModel.sharedFlow
|
// sharedFlow = MyProfileViewModel.sharedFlow
|
||||||
// )
|
// )
|
||||||
ProfileV3(
|
ProfileV3(
|
||||||
onUpdateBanner = { uri, context ->
|
onUpdateBanner = { uri, file, context ->
|
||||||
MyProfileViewModel.updateUserProfileBanner(uri, context)
|
MyProfileViewModel.updateUserProfileBanner(uri, file, context)
|
||||||
},
|
},
|
||||||
onLogout = {
|
onLogout = {
|
||||||
MyProfileViewModel.viewModelScope.launch {
|
MyProfileViewModel.viewModelScope.launch {
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ import com.aiosman.riderpro.ui.NavigationRoute
|
|||||||
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
import com.aiosman.riderpro.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.riderpro.ui.composables.DropdownMenu
|
import com.aiosman.riderpro.ui.composables.DropdownMenu
|
||||||
import com.aiosman.riderpro.ui.composables.MenuItem
|
import com.aiosman.riderpro.ui.composables.MenuItem
|
||||||
|
import com.aiosman.riderpro.ui.composables.pickupAndCompressLauncher
|
||||||
import com.aiosman.riderpro.ui.index.tabs.profile.composable.EmptyMomentPostUnit
|
import com.aiosman.riderpro.ui.index.tabs.profile.composable.EmptyMomentPostUnit
|
||||||
import com.aiosman.riderpro.ui.index.tabs.profile.composable.GalleryItem
|
import com.aiosman.riderpro.ui.index.tabs.profile.composable.GalleryItem
|
||||||
import com.aiosman.riderpro.ui.index.tabs.profile.composable.MomentPostUnit
|
import com.aiosman.riderpro.ui.index.tabs.profile.composable.MomentPostUnit
|
||||||
@@ -90,12 +91,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileV2(
|
fun ProfileV2(
|
||||||
onUpdateBanner: ((Uri, Context) -> Unit)? = null,
|
onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,
|
||||||
profile: AccountProfileEntity? = null,
|
profile: AccountProfileEntity? = null,
|
||||||
onLogout: () -> Unit = {},
|
onLogout: () -> Unit = {},
|
||||||
onFollowClick: () -> Unit = {},
|
onFollowClick: () -> Unit = {},
|
||||||
@@ -121,15 +123,11 @@ fun ProfileV2(
|
|||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val moments = sharedFlow.collectAsLazyPagingItems()
|
val moments = sharedFlow.collectAsLazyPagingItems()
|
||||||
val rootScrollState = rememberScrollState()
|
val rootScrollState = rememberScrollState()
|
||||||
val pickBannerImageLauncher = rememberLauncherForActivityResult(
|
val pickBannerImageLauncher = pickupAndCompressLauncher(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
context,
|
||||||
) { result ->
|
scope
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
) { uri, file ->
|
||||||
val uri = result.data?.data
|
onUpdateBanner?.invoke(uri, file, context)
|
||||||
uri?.let {
|
|
||||||
onUpdateBanner?.invoke(it, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val parentScrollConnection = remember {
|
val parentScrollConnection = remember {
|
||||||
object : NestedScrollConnection {
|
object : NestedScrollConnection {
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
package com.aiosman.riderpro.utils
|
package com.aiosman.riderpro.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.disk.DiskCache
|
import coil.disk.DiskCache
|
||||||
import coil.memory.MemoryCache
|
import coil.memory.MemoryCache
|
||||||
import coil.request.CachePolicy
|
import coil.request.CachePolicy
|
||||||
import com.aiosman.riderpro.data.api.AuthInterceptor
|
import com.aiosman.riderpro.data.api.AuthInterceptor
|
||||||
import com.aiosman.riderpro.data.api.getUnsafeOkHttpClient
|
import com.aiosman.riderpro.data.api.getUnsafeOkHttpClient
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object Utils {
|
object Utils {
|
||||||
@@ -61,4 +67,32 @@ object Utils {
|
|||||||
fun getCurrentLanguage(): String {
|
fun getCurrentLanguage(): String {
|
||||||
return Locale.getDefault().language
|
return Locale.getDefault().language
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun compressImage(context: Context, uri: Uri, maxSize: Int = 512, quality: Int = 85): File {
|
||||||
|
val inputStream = context.contentResolver.openInputStream(uri)
|
||||||
|
val originalBitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
val (width, height) = originalBitmap.width to originalBitmap.height
|
||||||
|
|
||||||
|
val (newWidth, newHeight) = if (width > height) {
|
||||||
|
maxSize to (height * maxSize / width)
|
||||||
|
} else {
|
||||||
|
(width * maxSize / height) to maxSize
|
||||||
|
}
|
||||||
|
|
||||||
|
val scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true)
|
||||||
|
val uuidImageName = UUID.randomUUID().toString().let { "$it.jpg" }
|
||||||
|
val compressedFile = File(context.cacheDir, uuidImageName)
|
||||||
|
val outputStream = FileOutputStream(compressedFile)
|
||||||
|
if (quality > 100) {
|
||||||
|
throw IllegalArgumentException("Quality must be less than 100")
|
||||||
|
}
|
||||||
|
if (quality < 0) {
|
||||||
|
throw IllegalArgumentException("Quality must be greater than 0")
|
||||||
|
}
|
||||||
|
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
|
||||||
|
outputStream.flush()
|
||||||
|
outputStream.close()
|
||||||
|
|
||||||
|
return compressedFile
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user