Feat: Add Create Bottom Sheet and icons
- Implemented a new `CreateBottomSheet` Composable to provide users with options to create AI, Group Chat, or Moment. - Added new drawable resources for the create options: `ic_create_ai.xml`, `ic_create_group_chat.xml`, `ic_create_monent.xml`, and `ic_create_close.xml`. - Integrated the `CreateBottomSheet` into the `IndexScreen`. Clicking the "+" button now opens this bottom sheet instead of directly navigating to new post creation. - Updated `IndexViewModel` to manage the visibility state of the `CreateBottomSheet`. - Added string resources for the Create Bottom Sheet in English, Chinese, and Japanese. - Ensured proper navigation and tourist mode checks for each create option. - Implemented graceful dismissal of the bottom sheet with animations.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package com.aiosman.ravenow.ui.index
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.SheetState
|
||||
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.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CreateBottomSheet(
|
||||
sheetState: SheetState,
|
||||
onDismiss: () -> Unit,
|
||||
onAiClick: () -> Unit,
|
||||
onGroupChatClick: () -> Unit,
|
||||
onMomentClick: () -> Unit
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState,
|
||||
windowInsets = BottomSheetDefaults.windowInsets,
|
||||
containerColor = appColors.background,
|
||||
dragHandle = null,
|
||||
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 24.dp, bottom = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// 标题
|
||||
Text(
|
||||
text = stringResource(R.string.create_title),
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = appColors.text,
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
|
||||
// 三个创建选项
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
// 群聊选项
|
||||
CreateOption(
|
||||
icon = R.drawable.ic_create_group_chat,
|
||||
label = stringResource(R.string.create_group_chat_option),
|
||||
onClick = onGroupChatClick
|
||||
)
|
||||
// 动态选项
|
||||
CreateOption(
|
||||
icon = R.drawable.ic_create_monent,
|
||||
label = stringResource(R.string.create_moment),
|
||||
onClick = onMomentClick
|
||||
)
|
||||
// AI选项
|
||||
CreateOption(
|
||||
icon = R.drawable.ic_create_ai,
|
||||
label = stringResource(R.string.create_ai),
|
||||
onClick = onAiClick
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
|
||||
// 关闭按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.clip(CircleShape)
|
||||
.noRippleClickable { onDismiss() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_create_close),
|
||||
contentDescription = stringResource(R.string.create_close),
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CreateOption(
|
||||
icon: Int,
|
||||
label: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.noRippleClickable { onClick() }
|
||||
) {
|
||||
// 直接使用图标,不要背景
|
||||
Image(
|
||||
painter = painterResource(icon),
|
||||
contentDescription = label,
|
||||
modifier = Modifier.size(72.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// 文字标签
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = appColors.text
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.NavigationBar
|
||||
@@ -34,6 +35,7 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
@@ -78,9 +80,10 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.ui.post.NewPostViewModel
|
||||
import com.aiosman.ravenow.utils.ResourceCleanupManager
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun IndexScreen() {
|
||||
val AppColors = LocalAppTheme.current
|
||||
@@ -101,6 +104,7 @@ fun IndexScreen() {
|
||||
val pagerState = rememberPagerState(pageCount = { item.size })
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
val bottomSheetState = rememberModalBottomSheetState()
|
||||
val context = LocalContext.current
|
||||
|
||||
// 注意:不要在离开 Index 路由时全量清理资源,以免返回后列表被重置
|
||||
@@ -292,8 +296,8 @@ fun IndexScreen() {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
return@noRippleClickable
|
||||
}
|
||||
NewPostViewModel.asNewPost()
|
||||
navController.navigate(NavigationRoute.NewPost.route)
|
||||
// 显示创建底部弹窗
|
||||
model.showCreateBottomSheet = true
|
||||
return@noRippleClickable
|
||||
}
|
||||
|
||||
@@ -389,6 +393,56 @@ fun IndexScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建底部弹窗
|
||||
if (model.showCreateBottomSheet) {
|
||||
CreateBottomSheet(
|
||||
sheetState = bottomSheetState,
|
||||
onDismiss = {
|
||||
// 使用协程来优雅地关闭弹窗
|
||||
coroutineScope.launch {
|
||||
bottomSheetState.hide()
|
||||
model.showCreateBottomSheet = false
|
||||
}
|
||||
},
|
||||
onAiClick = {
|
||||
// 使用协程来优雅地关闭弹窗并导航
|
||||
coroutineScope.launch {
|
||||
bottomSheetState.hide() // 触发关闭动画
|
||||
model.showCreateBottomSheet = false
|
||||
// 检查游客模式,如果是游客则跳转登录
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CREATE_AGENT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
navController.navigate(NavigationRoute.AddAgent.route)
|
||||
}
|
||||
}
|
||||
},
|
||||
onGroupChatClick = {
|
||||
// 使用协程来优雅地关闭弹窗并导航
|
||||
coroutineScope.launch {
|
||||
bottomSheetState.hide() // 触发关闭动画
|
||||
model.showCreateBottomSheet = false
|
||||
// 检查游客模式,如果是游客则跳转登录
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.JOIN_GROUP_CHAT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
navController.navigate(NavigationRoute.CreateGroupChat.route)
|
||||
}
|
||||
}
|
||||
},
|
||||
onMomentClick = {
|
||||
// 使用协程来优雅地关闭弹窗并导航
|
||||
coroutineScope.launch {
|
||||
bottomSheetState.hide() // 触发关闭动画
|
||||
model.showCreateBottomSheet = false
|
||||
// 导航到动态创建页面
|
||||
NewPostViewModel.asNewPost()
|
||||
navController.navigate(NavigationRoute.NewPost.route)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@ object IndexViewModel:ViewModel() {
|
||||
var tabIndex by mutableStateOf(0)
|
||||
|
||||
var openDrawer by mutableStateOf(false)
|
||||
|
||||
var showCreateBottomSheet by mutableStateOf(false)
|
||||
|
||||
fun ResetModel(){
|
||||
tabIndex = 0
|
||||
showCreateBottomSheet = false
|
||||
}
|
||||
|
||||
}
|
||||
19
app/src/main/res/drawable/ic_create_ai.xml
Normal file
19
app/src/main/res/drawable/ic_create_ai.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="72dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="72"
|
||||
android:viewportHeight="72">
|
||||
<path
|
||||
android:pathData="M36,36m-36,0a36,36 0,1 1,72 0a36,36 0,1 1,-72 0"
|
||||
android:fillColor="#8FBFFA"
|
||||
android:fillAlpha="0.08"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M36.804,31.654h-0.108c-2.5,0 -5.222,0 -7.559,0.409a4.115,4.115 0,0 0,-3.3 3.202c-0.337,1.56 -0.337,2.86 -0.337,5.224v0.175c0,2.365 0,3.665 0.338,5.225a4.115,4.115 0,0 0,3.3 3.2c2.336,0.411 5.058,0.411 7.558,0.411h0.108c2.5,0 5.222,0 7.559,-0.41a4.115,4.115 0,0 0,3.3 -3.2C48,44.328 48,43.03 48,40.663v-0.175c0,-2.364 0,-3.663 -0.338,-5.224a4.115,4.115 0,0 0,-3.3 -3.202c-2.336,-0.409 -5.058,-0.409 -7.558,-0.409z"
|
||||
android:fillColor="#8FBFFA"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M36.75,22.5c-1.06,0 -2.031,0.302 -2.738,1.006 -0.704,0.704 -1.004,1.673 -1.004,2.735 0,1.058 0.302,2.028 1.006,2.732 0.35,0.35 0.765,0.601 1.228,0.764v1.921c0.49,-0.004 0.977,-0.004 1.454,-0.004h0.108c0.48,0 0.967,0 1.456,0.004v-1.92c0.461,-0.16 0.881,-0.421 1.228,-0.765 0.704,-0.704 1.006,-1.674 1.006,-2.734s-0.302,-2.03 -1.006,-2.733c-0.705,-0.704 -1.677,-1.006 -2.738,-1.006zM32.682,35.919c0.694,0 1.258,0.563 1.258,1.257v1.213a1.258,1.258 0,0 1,-2.516 0v-1.213c0,-0.694 0.564,-1.257 1.258,-1.257zM40.818,35.919c0.694,0 1.258,0.563 1.258,1.257v1.213a1.258,1.258 0,0 1,-2.516 0v-1.213c0,-0.694 0.564,-1.257 1.258,-1.257zM32.193,41.933a1.259,1.259 0,0 1,1.7 0.48l0.013,0.019c0.16,0.227 0.354,0.43 0.573,0.601 0.435,0.342 1.154,0.718 2.271,0.718 1.115,0 1.836,-0.378 2.273,-0.718a2.87,2.87 0,0 0,0.571 -0.603l0.012,-0.016A1.259,1.259 0,0 1,41.8 43.64l-1.1,-0.606 1.1,0.608v0.004l-0.004,0.006 -0.008,0.014 -0.022,0.036 -0.066,0.105a5.392,5.392 0,0 1,-1.128 1.202c-0.82,0.646 -2.077,1.256 -3.824,1.256 -1.743,0 -3,-0.61 -3.822,-1.256a5.392,5.392 0,0 1,-1.128 -1.202,2.99 2.99,0 0,1 -0.064,-0.105l-0.02,-0.036 -0.01,-0.014 -0.002,-0.006 -0.002,-0.002c0,-0.002 0,-0.004 1.1,-0.61l-1.102,0.608a1.256,1.256 0,0 1,0.495 -1.71z"
|
||||
android:fillColor="#2859C5"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/ic_create_close.xml
Normal file
15
app/src/main/res/drawable/ic_create_close.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="33dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="33">
|
||||
<path
|
||||
android:pathData="M15.478,0.75L16.522,0.75A15.478,15.478 0,0 1,32 16.228L32,17.522A15.478,15.478 0,0 1,16.522 33L15.478,33A15.478,15.478 0,0 1,0 17.522L0,16.228A15.478,15.478 0,0 1,15.478 0.75z"
|
||||
android:fillColor="#7C7480"
|
||||
android:fillAlpha="0.08"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M20.782,12.155a1.29,1.29 0,0 1,0 1.824l-2.889,2.889 2.86,2.859a1.29,1.29 0,0 1,0 1.824l-0.077,0.076a1.29,1.29 0,0 1,-1.824 0l-2.859,-2.86 -2.889,2.89a1.29,1.29 0,0 1,-1.824 0l-0.062,-0.062a1.29,1.29 0,0 1,0 -1.824l2.89,-2.89 -2.86,-2.858a1.29,1.29 0,0 1,0 -1.824l0.076,-0.076a1.29,1.29 0,0 1,1.824 0l2.86,2.859 2.888,-2.889a1.29,1.29 0,0 1,1.824 0l0.062,0.062z"
|
||||
android:fillColor="#918E93"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
28
app/src/main/res/drawable/ic_create_group_chat.xml
Normal file
28
app/src/main/res/drawable/ic_create_group_chat.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="72dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="72"
|
||||
android:viewportHeight="72">
|
||||
<path
|
||||
android:pathData="M36,36m-36,0a36,36 0,1 1,72 0a36,36 0,1 1,-72 0"
|
||||
android:fillColor="#FC0"
|
||||
android:fillAlpha="0.08"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M31.206,26.187c1.179,-1.192 2.856,-1.826 4.61,-1.57l8.182,1.196c3.012,0.44 5.11,3.336 4.687,6.468l-1.533,11.34 -1.75,-0.255"
|
||||
android:strokeWidth="2.147"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FECE51"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="m28.156,30.17 l7.756,-1.134a5.726,5.726 0,0 1,6.502 4.899l0.766,5.662a5.726,5.726 0,0 1,-4.847 6.433L24.85,48l-1.54,-11.398a5.726,5.726 0,0 1,4.846 -6.433z"
|
||||
android:strokeWidth="2.147"
|
||||
android:fillColor="#FECE51"
|
||||
android:strokeColor="#FECE51"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="m27.74,39.562 l11.024,-0.06 -0.015,2.861 -11.024,0.06z"
|
||||
android:fillColor="#D86002"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
21
app/src/main/res/drawable/ic_create_monent.xml
Normal file
21
app/src/main/res/drawable/ic_create_monent.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="72dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="72"
|
||||
android:viewportHeight="72">
|
||||
<path
|
||||
android:pathData="M36,36m-36,0a36,36 0,1 1,72 0a36,36 0,1 1,-72 0"
|
||||
android:fillColor="#3DC779"
|
||||
android:fillAlpha="0.08"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M31.555,24.228a0.964,0.964 0,0 0,-0.845 0.498l-1.958,3.548 -0.636,0.054 -0.154,0.012a5,5 0,0 0,-4.504 4.233c-0.246,1.664 -0.476,3.433 -0.476,5.253 0,1.82 0.232,3.59 0.476,5.256a5,5 0,0 0,4.504 4.231c2.64,0.228 5.33,0.459 8.038,0.459s5.398,-0.231 8.038,-0.459a5,5 0,0 0,4.503 -4.231c0.247,-1.667 0.477,-3.433 0.477,-5.256 0,-1.82 -0.232,-3.587 -0.477,-5.253a5,5 0,0 0,-4.503 -4.233l-0.127,-0.01 -0.663,-0.058 -1.958,-3.546a0.964,0.964 0,0 0,-0.845 -0.498h-8.89z"
|
||||
android:fillColor="#61CD8C"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M36,33.345c1.253,0 2.26,0.306 2.934,0.98 0.674,0.674 0.98,1.68 0.98,2.932 0,1.253 -0.306,2.26 -0.98,2.934 -0.674,0.674 -1.681,0.98 -2.935,0.98 -1.252,0.002 -2.26,-0.305 -2.933,-0.978 -0.674,-0.674 -0.98,-1.681 -0.98,-2.934 0,-1.253 0.306,-2.26 0.98,-2.934 0.674,-0.674 1.68,-0.98 2.934,-0.98z"
|
||||
android:strokeWidth="2.25"
|
||||
android:fillColor="#61CD8C"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="#FFF"/>
|
||||
</vector>
|
||||
9
app/src/main/res/values-ja/strings.xml
Normal file
9
app/src/main/res/values-ja/strings.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Create Bottom Sheet -->
|
||||
<string name="create_title">作成</string>
|
||||
<string name="create_ai">AI</string>
|
||||
<string name="create_group_chat_option">グループチャット</string>
|
||||
<string name="create_moment">モーメント</string>
|
||||
<string name="create_close">閉じる</string>
|
||||
</resources>
|
||||
@@ -190,5 +190,12 @@
|
||||
<string name="agent_createing">创建中...</string>
|
||||
<string name="agent_find">发现</string>
|
||||
<string name="text_error_password_too_long">密码不能超过 %1$d 个字符</string>
|
||||
|
||||
<!-- Create Bottom Sheet -->
|
||||
<string name="create_title">创建</string>
|
||||
<string name="create_ai">AI</string>
|
||||
<string name="create_group_chat_option">群聊</string>
|
||||
<string name="create_moment">动态</string>
|
||||
<string name="create_close">关闭</string>
|
||||
|
||||
</resources>
|
||||
@@ -186,5 +186,12 @@
|
||||
<string name="agent_createing">创建中...</string>
|
||||
<string name="agent_find">发现</string>
|
||||
<string name="text_error_password_too_long">Password cannot exceed %1$d characters</string>
|
||||
|
||||
<!-- Create Bottom Sheet -->
|
||||
<string name="create_title">Create</string>
|
||||
<string name="create_ai">AI</string>
|
||||
<string name="create_group_chat_option">Group Chat</string>
|
||||
<string name="create_moment">Moment</string>
|
||||
<string name="create_close">Close</string>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user