添加新的表单文本输入组件 FormTextInput2,包含错误提示和动态显示功能;新增图标和图片资源。
This commit is contained in:
@@ -4,6 +4,11 @@ import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -31,6 +36,8 @@ import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -42,6 +49,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -49,20 +58,25 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.entity.createAgent
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.composables.DraggableGrid
|
||||
@@ -78,6 +92,10 @@ import java.io.File
|
||||
@Composable
|
||||
fun NewPostScreen() {
|
||||
val AppColors = LocalAppTheme.current
|
||||
var isAiEnabled by remember { mutableStateOf(false) }
|
||||
var isRotating by remember { mutableStateOf(false) }
|
||||
var isRequesting by remember { mutableStateOf(false) }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current // 添加这行
|
||||
|
||||
val model = NewPostViewModel
|
||||
val systemUiController = rememberSystemUiController()
|
||||
@@ -102,9 +120,6 @@ fun NewPostScreen() {
|
||||
) {
|
||||
NewPostTopBar {
|
||||
}
|
||||
NewPostTextField("Share your adventure…", NewPostViewModel.textContent) {
|
||||
NewPostViewModel.textContent = it
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -128,7 +143,168 @@ fun NewPostScreen() {
|
||||
}
|
||||
|
||||
AddImageGrid()
|
||||
// AdditionalPostItem()
|
||||
NewPostTextField(stringResource(R.string.moment_content_hint), NewPostViewModel.textContent) {
|
||||
NewPostViewModel.textContent = it
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(AppColors.divider)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp, start = 16.dp, end = 16.dp, bottom = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_moment_ai),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.moment_ai_co),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.weight(1f),
|
||||
color = AppColors.text,
|
||||
)
|
||||
Switch(
|
||||
checked = isAiEnabled,
|
||||
onCheckedChange = {
|
||||
isChecked ->
|
||||
isAiEnabled = isChecked
|
||||
if (isChecked) {
|
||||
// 收起键盘
|
||||
keyboardController?.hide()
|
||||
isRequesting = true
|
||||
isRotating = true
|
||||
model.viewModelScope.launch {
|
||||
try {
|
||||
model.agentMoment(model.textContent)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}finally {
|
||||
isRequesting = false
|
||||
isRotating = false
|
||||
isAiEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
},
|
||||
enabled = !isRequesting && model.textContent.isNotEmpty(),
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = Color.White,
|
||||
checkedTrackColor = AppColors.brandColorsColor,
|
||||
uncheckedThumbColor = Color.White,
|
||||
uncheckedTrackColor = Color(0xFFE9E9EA),
|
||||
uncheckedBorderColor = Color.White,
|
||||
disabledCheckedTrackColor = AppColors.brandColorsColor.copy(alpha = 0.8f),
|
||||
disabledCheckedThumbColor= Color.White,
|
||||
disabledUncheckedTrackColor = Color(0xFFE9E9EA),
|
||||
disabledUncheckedThumbColor= Color.White
|
||||
|
||||
),
|
||||
modifier = Modifier.scale(0.8f)
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
BasicTextField(
|
||||
value = model.aiTextContent,
|
||||
onValueChange = { newValue ->
|
||||
model.aiTextContent = newValue
|
||||
},
|
||||
modifier = Modifier
|
||||
.height(160.dp)
|
||||
.heightIn(160.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp)
|
||||
.fillMaxWidth(),
|
||||
cursorBrush = SolidColor(AppColors.text),
|
||||
textStyle = TextStyle(
|
||||
lineHeight = 24.sp,
|
||||
color = AppColors.text,
|
||||
),
|
||||
readOnly = true
|
||||
)
|
||||
if (model.aiTextContent.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.End // 靠右对齐
|
||||
) {
|
||||
// 删除按钮
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.noRippleClickable {
|
||||
model.aiTextContent = ""
|
||||
}
|
||||
.background(
|
||||
color = AppColors.basicMain,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_moment_delete),
|
||||
contentDescription = "delete",
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = AppColors.text
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.moment_ai_delete),
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(14.dp))
|
||||
//应用生成文案
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.noRippleClickable {
|
||||
if (model.aiTextContent.isNotEmpty()) {
|
||||
model.textContent = model.aiTextContent
|
||||
}
|
||||
}
|
||||
.background(
|
||||
color = AppColors.basicMain,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_moment_apply),
|
||||
contentDescription = "apply",
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = AppColors.text
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.moment_ai_apply),
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,7 +348,7 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
|
||||
modifier = Modifier.align(Alignment.CenterStart),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||
painter = painterResource(id = R.drawable.rider_pro_close),
|
||||
contentDescription = "Back",
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
@@ -182,12 +358,11 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
|
||||
colorFilter = ColorFilter.tint(AppColors.text)
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.rider_pro_video_share),
|
||||
tint = AppColors.text,
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.rider_pro_moment_post),
|
||||
contentDescription = "Send",
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.size(24.dp)
|
||||
.noRippleClickable {
|
||||
// 检查输入
|
||||
val errorMessage = model.validateMoment()
|
||||
@@ -208,7 +383,7 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
|
||||
uploading = false
|
||||
}
|
||||
|
||||
// 上传完成后隐藏进度条
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -227,13 +402,15 @@ fun NewPostTextField(hint: String, value: String, onValueChange: (String) -> Uni
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(200.dp)
|
||||
.padding(horizontal = 18.dp, vertical = 10.dp),
|
||||
.height(160.dp)
|
||||
.heightIn(160.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
cursorBrush = SolidColor(AppColors.text),
|
||||
textStyle = TextStyle(
|
||||
lineHeight = 24.sp,
|
||||
color = AppColors.text,
|
||||
)
|
||||
|
||||
),
|
||||
|
||||
)
|
||||
if (value.isEmpty()) {
|
||||
@@ -326,11 +503,11 @@ fun AddImageGrid() {
|
||||
}
|
||||
}
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
columns = GridCells.Fixed(5),
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(18.dp),
|
||||
.padding(horizontal = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
@@ -339,18 +516,8 @@ fun AddImageGrid() {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.drawBehind {
|
||||
val strokeWidth = 1.dp.toPx()
|
||||
val dashLength = 10f
|
||||
val dashGap = 10f
|
||||
val pathEffect =
|
||||
PathEffect.dashPathEffect(floatArrayOf(dashLength, dashGap))
|
||||
drawRoundRect(
|
||||
color = Color(0xFFD6D6D6),
|
||||
style = Stroke(strokeWidth, pathEffect = pathEffect),
|
||||
cornerRadius = CornerRadius(8.dp.toPx())
|
||||
)
|
||||
}
|
||||
.clip(RoundedCornerShape(16.dp)) // 设置圆角
|
||||
.background(Color(0xFFFAF9FB)) // 设置背景色
|
||||
.noRippleClickable {
|
||||
pickImagesLauncher.launch("image/*")
|
||||
},
|
||||
@@ -359,7 +526,7 @@ fun AddImageGrid() {
|
||||
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
|
||||
contentDescription = "Add Image",
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.size(24.dp)
|
||||
.align(Alignment.Center),
|
||||
tint = Color(0xFFD6D6D6)
|
||||
|
||||
@@ -371,18 +538,8 @@ fun AddImageGrid() {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.drawBehind {
|
||||
val strokeWidth = 1.dp.toPx()
|
||||
val dashLength = 10f
|
||||
val dashGap = 10f
|
||||
val pathEffect =
|
||||
PathEffect.dashPathEffect(floatArrayOf(dashLength, dashGap))
|
||||
drawRoundRect(
|
||||
color = Color(0xFFD6D6D6),
|
||||
style = Stroke(strokeWidth, pathEffect = pathEffect),
|
||||
cornerRadius = CornerRadius(8.dp.toPx())
|
||||
)
|
||||
}
|
||||
.clip(RoundedCornerShape(16.dp)) // 设置圆角
|
||||
.background(Color(0xFFFAF9FB)) // 设置背景色
|
||||
.noRippleClickable {
|
||||
val photoFile = File(context.cacheDir, "photo.jpg")
|
||||
val photoUri: Uri = FileProvider.getUriForFile(
|
||||
@@ -398,7 +555,7 @@ fun AddImageGrid() {
|
||||
painter = painterResource(id = R.drawable.rider_pro_camera),
|
||||
contentDescription = "Take Photo",
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.size(24.dp)
|
||||
.align(Alignment.Center),
|
||||
tint = Color(0xFFD6D6D6)
|
||||
)
|
||||
|
||||
@@ -8,12 +8,18 @@ import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.aiosman.ravenow.data.MomentService
|
||||
import com.aiosman.ravenow.data.ServiceException
|
||||
import com.aiosman.ravenow.data.UploadImage
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.AgentEntity
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import com.aiosman.ravenow.entity.createMultipartBody
|
||||
import com.aiosman.ravenow.event.MomentAddEvent
|
||||
import com.aiosman.ravenow.exp.rotate
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||
@@ -21,6 +27,9 @@ import com.aiosman.ravenow.ui.modification.Modification
|
||||
import com.aiosman.ravenow.utils.FileUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -96,6 +105,7 @@ data class Draft(
|
||||
object NewPostViewModel : ViewModel() {
|
||||
var momentService: MomentService = MomentServiceImpl()
|
||||
var textContent by mutableStateOf("")
|
||||
var aiTextContent by mutableStateOf("")
|
||||
var searchPlaceAddressResult by mutableStateOf<SearchPlaceAddressResult?>(null)
|
||||
var modificationList by mutableStateOf<List<Modification>>(listOf())
|
||||
var imageList by mutableStateOf(listOf<ImageItem>())
|
||||
@@ -111,6 +121,7 @@ object NewPostViewModel : ViewModel() {
|
||||
// }
|
||||
fun asNewPost() {
|
||||
textContent = ""
|
||||
aiTextContent = ""
|
||||
searchPlaceAddressResult = null
|
||||
modificationList = listOf()
|
||||
imageList = listOf()
|
||||
@@ -163,12 +174,17 @@ object NewPostViewModel : ViewModel() {
|
||||
onUploadProgress(((index / imageList.size).toFloat())) // progressValue 是当前上传进度,例如 0.5 表示 50%
|
||||
index += 1
|
||||
}
|
||||
aiTextContent = ""
|
||||
val result = momentService.createMoment(textContent, 1, uploadImageList, relPostId)
|
||||
// 刷新个人动态
|
||||
MyProfileViewModel.loadProfile(pullRefresh = true)
|
||||
EventBus.getDefault().post(MomentAddEvent(result))
|
||||
}
|
||||
|
||||
suspend fun agentMoment(textContent: String,) {
|
||||
aiTextContent = momentService.agentMoment(textContent)
|
||||
}
|
||||
|
||||
suspend fun init() {
|
||||
relPostId?.let {
|
||||
val moment = momentService.getMomentById(it)
|
||||
|
||||
Reference in New Issue
Block a user