添加新的表单文本输入组件 FormTextInput2,包含错误提示和动态显示功能;新增图标和图片资源。

This commit is contained in:
weber
2025-08-05 13:56:03 +08:00
parent 29d2bb753f
commit 3e544844bb
22 changed files with 712 additions and 116 deletions

View File

@@ -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)
)

View File

@@ -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)