diff --git a/app/src/main/java/com/aiosman/ravenow/Colors.kt b/app/src/main/java/com/aiosman/ravenow/Colors.kt index c8fdf92..7d939ae 100644 --- a/app/src/main/java/com/aiosman/ravenow/Colors.kt +++ b/app/src/main/java/com/aiosman/ravenow/Colors.kt @@ -20,6 +20,7 @@ open class AppThemeData( var decentBackground: Color, var divider: Color, var inputBackground: Color, + var inputBackground2: Color, var inputHint: Color, var error: Color, var checkedBackground: Color, @@ -30,7 +31,7 @@ open class AppThemeData( ) class LightThemeColors : AppThemeData( - main = Color(0xffda3832), + main = Color(0xffD80264), mainText = Color(0xffffffff), basicMain = Color(0xfff0f0f0), nonActive = Color(0xfff5f5f5), @@ -43,6 +44,7 @@ class LightThemeColors : AppThemeData( background = Color(0xFFFFFFFF), divider = Color(0xFFEbEbEb), inputBackground = Color(0xFFF7f7f7), + inputBackground2 = Color(0xFFFFFFFF), inputHint = Color(0xffdadada), error = Color(0xffFF0000), checkedBackground = Color(0xff000000), @@ -68,6 +70,7 @@ class DarkThemeColors : AppThemeData( background = Color(0xFF121212), divider = Color(0xFF282828), inputBackground = Color(0xFF1C1C1C), + inputBackground2 = Color(0xFF1C1C1C), inputHint = Color(0xff888888), error = Color(0xffFF0000), checkedBackground = Color(0xffffffff), diff --git a/app/src/main/java/com/aiosman/ravenow/data/MomentService.kt b/app/src/main/java/com/aiosman/ravenow/data/MomentService.kt index 4a99938..ebab19f 100644 --- a/app/src/main/java/com/aiosman/ravenow/data/MomentService.kt +++ b/app/src/main/java/com/aiosman/ravenow/data/MomentService.kt @@ -148,6 +148,10 @@ interface MomentService { relPostId: Int? = null ): MomentEntity + suspend fun agentMoment( + content: String, + ): String + /** * 收藏动态 * @param id 动态ID diff --git a/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt b/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt index 475fd1c..2d8be6a 100644 --- a/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt +++ b/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt @@ -31,6 +31,12 @@ data class RegisterRequestBody( @SerializedName("password") val password: String ) +data class AgentMomentRequestBody( + @SerializedName("generateText") + val generateText: String, + @SerializedName("sessionId") + val sessionId: String +) data class LoginUserRequestBody( @SerializedName("username") @@ -472,6 +478,17 @@ interface RaveNowAPI { @Query("pageSize") pageSize: Int = 20, ): Response> + @Multipart + @POST("outside/prompts") + suspend fun createAgent( + @Part avatar: MultipartBody.Part?, + @Part("title") title: RequestBody?, + @Part("desc") desc: RequestBody?, + ): Response> + + + @POST("generate/postText") + suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response> } diff --git a/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt b/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt index d3800d0..4e8f5d8 100644 --- a/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt @@ -5,14 +5,35 @@ import androidx.paging.PagingState import com.aiosman.ravenow.data.ListContainer import com.aiosman.ravenow.data.AgentService import com.aiosman.ravenow.data.ServiceException +import com.aiosman.ravenow.data.UploadImage import com.aiosman.ravenow.data.api.ApiClient +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File import java.io.IOException /** * 智能体 */ +suspend fun createAgent( + title: String, + desc: String, + avatar: UploadImage, +): AgentEntity { + val textTitle = title.toRequestBody("text/plain".toMediaTypeOrNull()) + val textDesc = desc.toRequestBody("text/plain".toMediaTypeOrNull()) + val avatarField: MultipartBody.Part? = avatar?.let { + createMultipartBody(it.file, it.filename, "avatar") + } + val response = ApiClient.api.createAgent(avatarField, textTitle ,textDesc) + val body = response.body()?.data ?: throw ServiceException("Failed to create agent") + return body.toAgentEntity() +} data class AgentEntity( val author: String, val avatar: String, @@ -39,7 +60,10 @@ data class ProfileEntity( val trtcUserId: String, val username: String ) - +fun createMultipartBody(file: File, filename: String, name: String): MultipartBody.Part { + val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull()) + return MultipartBody.Part.createFormData(name, filename, requestFile) +} class AgentLoaderExtraArgs( ) diff --git a/app/src/main/java/com/aiosman/ravenow/entity/Moment.kt b/app/src/main/java/com/aiosman/ravenow/entity/Moment.kt index 2b34906..38ecc6b 100644 --- a/app/src/main/java/com/aiosman/ravenow/entity/Moment.kt +++ b/app/src/main/java/com/aiosman/ravenow/entity/Moment.kt @@ -7,6 +7,7 @@ import com.aiosman.ravenow.data.ListContainer import com.aiosman.ravenow.data.MomentService import com.aiosman.ravenow.data.ServiceException import com.aiosman.ravenow.data.UploadImage +import com.aiosman.ravenow.data.api.AgentMomentRequestBody import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.parseErrorResponse import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -127,6 +128,10 @@ class MomentServiceImpl() : MomentService { return momentBackend.createMoment(content, authorId, images, relPostId) } + override suspend fun agentMoment(content: String): String { + return momentBackend.agentMoment(content) + } + override suspend fun favoriteMoment(id: Int) { momentBackend.favoriteMoment(id) } @@ -212,6 +217,17 @@ class MomentBackend { } + suspend fun agentMoment( + content: String, + ): String { + val textContent = content.toRequestBody("text/plain".toMediaTypeOrNull()) + val sessionId = "" + val response = ApiClient.api.agentMoment(AgentMomentRequestBody(generateText = content, sessionId =sessionId )) + val body = response.body()?.data ?: throw ServiceException("Failed to agent moment") + return body.toString() + + } + suspend fun favoriteMoment(id: Int) { ApiClient.api.favoritePost(id) } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt b/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt index ecc003a..ae4e29b 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgent.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext @@ -42,6 +43,7 @@ import com.aiosman.ravenow.ui.composables.ActionButton import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.StatusBarSpacer import com.aiosman.ravenow.ui.composables.form.FormTextInput +import com.aiosman.ravenow.ui.composables.form.FormTextInput2 import com.aiosman.ravenow.ui.modifiers.noRippleClickable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -84,12 +86,13 @@ fun AddAgentScreen() { Column( modifier = Modifier .fillMaxSize() - .background(color = appColors.background), + .background(color = appColors.decentBackground), horizontalAlignment = Alignment.CenterHorizontally ) { StatusBarSpacer() Box( modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) + .background(color = appColors.decentBackground) ) { ScreenHeader ( title = stringResource(R.string.agent_add), @@ -150,15 +153,17 @@ fun AddAgentScreen() { value = model.name, label = stringResource(R.string.agent_name), hint = stringResource(R.string.agent_name_hint), + background = appColors.inputBackground2, modifier = Modifier.fillMaxWidth(), ) { value -> onNicknameChange(value) } // Spacer(modifier = Modifier.height(16.dp)) - FormTextInput( + FormTextInput2( value = model.desc, label = stringResource(R.string.agent_desc), hint = stringResource(R.string.agent_desc_hint), + background = appColors.inputBackground2, modifier = Modifier.fillMaxWidth(), ) { value -> onDescChange(value) @@ -167,7 +172,20 @@ fun AddAgentScreen() { Spacer(modifier = Modifier.height(58.dp)) ActionButton( modifier = Modifier - .width(345.dp), + .width(345.dp) + .padding(horizontal = 16.dp) + .background( + brush = Brush.linearGradient( + colors = listOf( + Color(0xFFEE2A33), + Color(0xFFD80264), + Color(0xFF8468BC) + ) + ), + shape = RoundedCornerShape(24.dp) + ), + color = Color.White, + backgroundColor = Color.Transparent, text = stringResource(R.string.agent_create), ) { diff --git a/app/src/main/java/com/aiosman/ravenow/ui/comment/CommentsScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/comment/CommentsScreen.kt index aa35158..ee87dc5 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/comment/CommentsScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/comment/CommentsScreen.kt @@ -129,7 +129,7 @@ fun ScreenHeader( ) Spacer(modifier = Modifier.size(12.dp)) Text(title, - fontWeight = FontWeight.W800, + fontWeight = FontWeight.W600, modifier = Modifier.weight(1f), textAlign = TextAlign.Center, fontSize = 17.sp, diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/DragAndDrop.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/DragAndDrop.kt index 5d329db..be02b6c 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/DragAndDrop.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/DragAndDrop.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridItemInfo import androidx.compose.foundation.lazy.grid.LazyGridItemScope @@ -18,6 +19,7 @@ import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -26,6 +28,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.graphicsLayer @@ -58,12 +61,12 @@ fun DraggableGrid( val dragDropState = rememberGridDragDropState(gridState, onMove, onDragModeStart, onDragModeEnd, lockedIndices) LazyVerticalGrid( - columns = GridCells.Fixed(3), - modifier = Modifier.dragContainer(dragDropState), + columns = GridCells.Fixed(5), + modifier = Modifier.dragContainer(dragDropState).padding(horizontal = 8.dp), state = gridState, - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { itemsIndexed(items, key = { _, item -> @@ -122,7 +125,7 @@ fun LazyGridItemScope.DraggableItem( } else { Modifier.animateItemPlacement() } - Box(modifier = modifier.then(draggingModifier), propagateMinConstraints = true) { + Box(modifier = modifier.then(draggingModifier).clip(RoundedCornerShape(8.dp)), propagateMinConstraints = true) { content(dragging) } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/Image.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/Image.kt index 1c786e7..05e50ab 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/Image.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/Image.kt @@ -57,10 +57,29 @@ fun CustomAsyncImage( val imageLoader = getImageLoader(context ?: localContext) + // 处理 imageUrl 为 null 的情况 + if (imageUrl == null|| imageUrl == "") { + // 如果 imageUrl 为 null 且有占位符,则直接显示占位符 + if (placeholderRes != null) { + androidx.compose.foundation.Image( + painter = androidx.compose.ui.res.painterResource(placeholderRes), + contentDescription = contentDescription, + modifier = modifier, + contentScale = contentScale + ) + return + } + } AsyncImage( model = ImageRequest.Builder(context ?: localContext) .data(imageUrl) .crossfade(200) + .apply { + // 设置占位符图片 + if (placeholderRes != null) { + placeholder(placeholderRes) + } + } .build(), contentDescription = contentDescription, modifier = modifier, diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/form/FormTextInput.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/form/FormTextInput.kt index 9e75adc..def4dab 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/form/FormTextInput.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/form/FormTextInput.kt @@ -23,6 +23,7 @@ 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.graphics.SolidColor import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle @@ -32,6 +33,9 @@ import androidx.compose.ui.unit.sp import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.R +/** + * 水平布局的输入框 + */ @Composable fun FormTextInput( modifier: Modifier = Modifier, @@ -39,6 +43,7 @@ fun FormTextInput( label: String? = null, error: String? = null, hint: String? = null, + background: Color? = null, onValueChange: (String) -> Unit ) { val AppColors = LocalAppTheme.current @@ -48,7 +53,7 @@ fun FormTextInput( Row( modifier = Modifier.fillMaxWidth() .clip(RoundedCornerShape(16.dp)) - .background(AppColors.inputBackground) + .background(background ?: AppColors.inputBackground) .let { if (error != null) { it.border(1.dp, AppColors.error, RoundedCornerShape(24.dp)) @@ -66,7 +71,7 @@ fun FormTextInput( .widthIn(100.dp), style = TextStyle( fontSize = 16.sp, - fontWeight = FontWeight.Bold, + fontWeight = FontWeight.W600, color = AppColors.text ) ) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/form/FormTextInput2.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/form/FormTextInput2.kt new file mode 100644 index 0000000..2055c4c --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/form/FormTextInput2.kt @@ -0,0 +1,142 @@ +package com.aiosman.ravenow.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.graphics.SolidColor +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.ravenow.LocalAppTheme +import com.aiosman.ravenow.R + +/** + * 垂直布局的输入框 + */ +@Composable +fun FormTextInput2( + modifier: Modifier = Modifier, + value: String, + label: String? = null, + error: String? = null, + hint: String? = null, + background: Color? = null, + onValueChange: (String) -> Unit +) { + val AppColors = LocalAppTheme.current + Column( + modifier = modifier.height(150.dp) + ) { + Column( + modifier = Modifier.fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(background ?: AppColors.inputBackground) + .let { + if (error != null) { + it.border(1.dp, AppColors.error, RoundedCornerShape(24.dp)) + } else { + it + } + } + .padding(17.dp), + + ) { + label?.let { + Text( + text = it, + modifier = Modifier + .widthIn(100.dp), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.W600, + color = AppColors.text + ) + ) + } + Box( + modifier = Modifier + .weight(1f) + .padding(top = 8.dp) + ) { + if (value.isEmpty()) { + Text( + text = hint ?: "", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = AppColors.inputHint + ) + ) + } + + BasicTextField( + maxLines = 5, + value = value, + onValueChange = { + onValueChange(it) + }, + textStyle = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = AppColors.text, + lineHeight = 20.sp + ), + cursorBrush = SolidColor(AppColors.text), + ) + + } + + + + } + 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 = AppColors.error, fontSize = 12.sp) + } + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt index 701bd3b..0d56698 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt @@ -62,16 +62,16 @@ fun Agent() { ) { Row( modifier = Modifier - .height(36.dp) // 设置高度为36dp - .fillMaxWidth(), // 占据整行宽度 + .height(36.dp) + .fillMaxWidth(), horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.Bottom ) { - // 搜索框 - 占据剩余空间 + // 搜索框 Row( modifier = Modifier .height(36.dp) - .weight(1f) // 权重为1,占据剩余空间 + .weight(1f) .clip(shape = RoundedCornerShape(18.dp)) .background(AppColors.inputBackground) .padding(horizontal = 8.dp, vertical = 0.dp) @@ -94,16 +94,13 @@ fun Agent() { ) } } - - // 间隔 Spacer(modifier = Modifier.width(16.dp)) - // 新增 Icon( modifier = Modifier .size(36.dp) .noRippleClickable { - // 图标点击事件 + // navController.navigate( NavigationRoute.AddAgent.route ) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageList.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageList.kt index 1c9c8ea..8fe00bb 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageList.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageList.kt @@ -1,6 +1,7 @@ package com.aiosman.ravenow.ui.index.tabs.message import android.widget.Toast +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -14,7 +15,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi @@ -28,6 +32,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -60,13 +65,15 @@ import kotlinx.coroutines.launch /** * 消息列表界面 */ -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable fun NotificationsScreen() { val AppColors = LocalAppTheme.current val navController = LocalNavController.current val systemUiController = rememberSystemUiController() val context = LocalContext.current + var pagerState = rememberPagerState (pageCount = { 4 }) + var scope = rememberCoroutineScope() val state = rememberPullRefreshState(MessageListViewModel.isLoading, onRefresh = { MessageListViewModel.viewModelScope.launch { MessageListViewModel.initData(context, force = true, loadChat = AppState.enableChat) @@ -96,10 +103,9 @@ fun NotificationsScreen() { .padding(horizontal = 15.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - // 左侧占位元素 + Box(modifier = Modifier.size(24.dp)) - // 左侧 Column:label 居中显示 Column( modifier = Modifier .weight(1f) @@ -113,7 +119,7 @@ fun NotificationsScreen() { color = AppColors.text ) } - // 右侧图标 + Image( painter = painterResource(id = R.drawable.rider_pro_group), contentDescription = "add", @@ -169,8 +175,111 @@ fun NotificationsScreen() { navController.navigate(NavigationRoute.CommentNoticeScreen.route) } } - HorizontalDivider(color = AppColors.divider, modifier = Modifier.padding(16.dp)) - Box( + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 16.dp), + // center the tabs + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.Bottom + ) { + Column( + modifier = Modifier + .noRippleClickable { + scope.launch { + pagerState.animateScrollToPage(0) + } + }, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + + ) { + Text( + text = stringResource(R.string.chat_ai), + fontSize = 14.sp, + color = if (pagerState.currentPage == 0) AppColors.mainText else AppColors.checkedBackground, + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(if (pagerState.currentPage == 0) AppColors.checkedBackground else AppColors.unCheckedBackground) + .padding(horizontal = 11.dp, vertical = 4.dp) + ) + } + Spacer(modifier = Modifier.width(8.dp)) + Column( + modifier = Modifier + .noRippleClickable { + scope.launch { + pagerState.animateScrollToPage(1) + } + }, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + + ) { + androidx.compose.material.Text( + text = stringResource(R.string.chat_group), + fontSize = 14.sp, + color = if (pagerState.currentPage == 1) AppColors.mainText else AppColors.checkedBackground, + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(if (pagerState.currentPage == 1) AppColors.checkedBackground else AppColors.unCheckedBackground) + .padding(horizontal = 11.dp, vertical = 4.dp) + ) + + + } + Spacer(modifier = Modifier.width(8.dp)) + Column( + modifier = Modifier + .noRippleClickable { + scope.launch { + pagerState.animateScrollToPage(2) + } + }, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + + ) { + androidx.compose.material.Text( + text = stringResource(R.string.chat_friend), + fontSize = 14.sp, + color = if (pagerState.currentPage == 2) AppColors.mainText else AppColors.checkedBackground, + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(if (pagerState.currentPage == 2) AppColors.checkedBackground else AppColors.unCheckedBackground) + .padding(horizontal = 11.dp, vertical = 4.dp) + ) + + + } + + } + HorizontalPager( + state = pagerState, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + when (it) { + 0 -> { + + } + + 1 -> { + + } + + 2 -> { + + } + + + + } + } + + /*Box( modifier = Modifier .weight(1f) .fillMaxWidth(), @@ -194,7 +303,7 @@ fun NotificationsScreen() { ) } - } + }*/ } PullRefreshIndicator( MessageListViewModel.isLoading, @@ -226,28 +335,14 @@ fun NotificationIndicator( onClick() } ) { - if (notificationCount > 0) { - Box( - modifier = Modifier - .background(AppColors.main, RoundedCornerShape(16.dp)) - .padding(4.dp) - .align(Alignment.TopEnd) - ) { - Text( - text = notificationCount.toString(), - color = AppColors.mainText, - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.align(Alignment.Center) - ) - } - } + Column( horizontalAlignment = Alignment.CenterHorizontally, ) { Box( modifier = Modifier - .size(64.dp) + .size(69.dp) + .padding(5.dp) .background(color = backgroundColor, shape = RoundedCornerShape(16.dp)), contentAlignment = Alignment.Center @@ -265,6 +360,22 @@ fun NotificationIndicator( } } + if (notificationCount > 0) { + Box( + modifier = Modifier + .background(AppColors.main, RoundedCornerShape(16.dp)) + .padding(horizontal = 8.dp, vertical = 4.dp) + .align(Alignment.TopEnd) + ) { + Text( + text = if (notificationCount > 99) "99+" else notificationCount.toString(), + color = AppColors.mainText, + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.align(Alignment.Center) + ) + } + } } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/post/NewPost.kt b/app/src/main/java/com/aiosman/ravenow/ui/post/NewPost.kt index a8b7f3b..3b7133d 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/post/NewPost.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/post/NewPost.kt @@ -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) ) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/post/NewPostViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/post/NewPostViewModel.kt index b64434d..30a530d 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/post/NewPostViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/post/NewPostViewModel.kt @@ -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(null) var modificationList by mutableStateOf>(listOf()) var imageList by mutableStateOf(listOf()) @@ -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) diff --git a/app/src/main/res/drawable/rider_pro_close.xml b/app/src/main/res/drawable/rider_pro_close.xml index 7ed4f20..cfa0192 100644 --- a/app/src/main/res/drawable/rider_pro_close.xml +++ b/app/src/main/res/drawable/rider_pro_close.xml @@ -1,12 +1,34 @@ + - - + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rider_pro_group.xml b/app/src/main/res/drawable/rider_pro_group.xml index 67acce1..4ebb84f 100644 --- a/app/src/main/res/drawable/rider_pro_group.xml +++ b/app/src/main/res/drawable/rider_pro_group.xml @@ -7,32 +7,27 @@ + android:translateY="2.945"> + android:strokeWidth="2" + android:pathData="M13 14.055c3.314 0 6 1.44 6 3.215 0 0.564-0.272 1.095-0.749 1.556-0.324 0.147 -0.684 0.23 -1.063 0.23 H8.812c-0.379 0-0.739-0.083-1.062-0.23-0.478-0.461-0.75-0.992-0.75-1.556 0-1.775 2.686-3.215 6-3.215zm0-3a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" /> + + - - + android:pathData="M16.364 2h4M18.364 0v4" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/rider_pro_moment_apply.xml b/app/src/main/res/drawable/rider_pro_moment_apply.xml new file mode 100644 index 0000000..1ef5f36 --- /dev/null +++ b/app/src/main/res/drawable/rider_pro_moment_apply.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-xhdpi/rider_pro_moment_ai.png b/app/src/main/res/mipmap-xhdpi/rider_pro_moment_ai.png new file mode 100644 index 0000000..851c14a Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/rider_pro_moment_ai.png differ diff --git a/app/src/main/res/mipmap-xhdpi/rider_pro_moment_post.png b/app/src/main/res/mipmap-xhdpi/rider_pro_moment_post.png new file mode 100644 index 0000000..e64d7ab Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/rider_pro_moment_post.png differ diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 307a229..03cd8b5 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -125,5 +125,13 @@ 设定描述 示例: 一位经验丰富的销售员,擅长通过幽默风趣的语言和生动的案例,将复杂的产品转化为客户易于理解并感兴趣的话题 创建智能体 + 需要一些灵感来写文章吗?让人工智能来帮你! + AI文案优化 + 删除 + 应用 + 智能体 + 群聊 + 朋友 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index adea928..54b5239 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -124,4 +124,11 @@ 设定描述 示例: 一位经验丰富的销售员,擅长通过幽默风趣的语言和生动的案例,将复杂的产品转化为客户易于理解并感兴趣的话题 创建智能体 + Need some inspiration for your post? Let AI assist you! + AI copywriting optimization + Delete + Apply + Ai + Group + Friends \ No newline at end of file