实现了 Agent 和 Profile 数据类,添加了 AddAgent 界面

This commit is contained in:
weber
2025-07-31 15:22:34 +08:00
parent 6ec732b996
commit 29d2bb753f
28 changed files with 1181 additions and 72 deletions

View File

@@ -61,6 +61,7 @@ import com.aiosman.ravenow.Messaging
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.index.tabs.add.AddPage
import com.aiosman.ravenow.ui.index.tabs.ai.Agent
import com.aiosman.ravenow.ui.index.tabs.message.NotificationsScreen
import com.aiosman.ravenow.ui.index.tabs.moment.MomentsList
import com.aiosman.ravenow.ui.index.tabs.profile.ProfileWrap
@@ -332,7 +333,7 @@ fun IndexScreen() {
) { page ->
when (page) {
0 -> Home()
1 -> DiscoverScreen()
1 -> Agent()
2 -> Add()
3 -> Notifications()
4 -> Profile()

View File

@@ -0,0 +1,250 @@
package com.aiosman.ravenow.ui.index.tabs.ai
import androidx.compose.foundation.ExperimentalFoundationApi
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.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch
@OptIn( ExperimentalFoundationApi::class)
@Composable
fun Agent() {
val AppColors = LocalAppTheme.current
val navController = LocalNavController.current
val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
var pagerState = rememberPagerState { 4 }
var scope = rememberCoroutineScope()
Column(
modifier = Modifier
.fillMaxSize()
.padding(
top = statusBarPaddingValues.calculateTopPadding()+18.dp,
bottom = navigationBarPaddings,
start = 16.dp,
end = 16.dp
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier
.height(36.dp) // 设置高度为36dp
.fillMaxWidth(), // 占据整行宽度
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
) {
// 搜索框 - 占据剩余空间
Row(
modifier = Modifier
.height(36.dp)
.weight(1f) // 权重为1占据剩余空间
.clip(shape = RoundedCornerShape(18.dp))
.background(AppColors.inputBackground)
.padding(horizontal = 8.dp, vertical = 0.dp)
.noRippleClickable {
// 搜索框点击事件
},
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = null,
tint = AppColors.inputHint
)
Box {
Text(
text = stringResource(R.string.search),
modifier = Modifier.padding(start = 8.dp),
color = AppColors.inputHint,
fontSize = 17.sp
)
}
}
// 间隔
Spacer(modifier = Modifier.width(16.dp))
// 新增
Icon(
modifier = Modifier
.size(36.dp)
.noRippleClickable {
// 图标点击事件
navController.navigate(
NavigationRoute.AddAgent.route
)
},
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
contentDescription = null,
tint = AppColors.text
)
}
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
// 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.agent_mine),
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
) {
Text(
text = stringResource(R.string.agent_hot),
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
) {
Text(
text = stringResource(R.string.agent_recommend),
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)
)
}
Spacer(modifier = Modifier.width(8.dp))
Column(
modifier = Modifier
.noRippleClickable {
scope.launch {
pagerState.animateScrollToPage(3)
}
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.agent_other),
fontSize = 14.sp,
color = if (pagerState.currentPage == 3) AppColors.mainText else AppColors.checkedBackground,
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(if (pagerState.currentPage == 3) AppColors.checkedBackground else AppColors.unCheckedBackground)
.padding(horizontal = 11.dp, vertical = 4.dp)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
when (it) {
0 -> {
// ExploreMomentsList()
}
1 -> {
//TimelineMomentsList()
}
2 -> {
//HotMomentsList()
}
3 -> {
//MineAgent()
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
package com.aiosman.ravenow.ui.index.tabs.ai
import androidx.lifecycle.ViewModel
object AgentViewModel: ViewModel() {
}

View File

@@ -0,0 +1,59 @@
package com.aiosman.ravenow.ui.index.tabs.ai
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.data.AgentService
import com.aiosman.ravenow.data.MomentService
import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.entity.AgentLoader
import com.aiosman.ravenow.entity.AgentLoaderExtraArgs
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
open class BaseAgentModel :ViewModel(){
// private val agentService: AgentService = AgentServiceImpl()
val agentLoader = AgentLoader().apply {
onListChanged = {
agentList = it
}
}
var refreshing by mutableStateOf(false)
var isFirstLoad = true
var agentList by mutableStateOf<List<AgentEntity>>(listOf())
open fun extraArgs(): AgentLoaderExtraArgs {
return AgentLoaderExtraArgs()
}
fun refreshPager(pullRefresh: Boolean = false) {
if (!isFirstLoad && !pullRefresh) {
return
}
isFirstLoad = false
agentLoader.clear()
viewModelScope.launch {
agentLoader.loadData(extraArgs())
}
}
fun loadMore() {
viewModelScope.launch {
agentLoader.loadMore(extraArgs())
agentList = agentLoader.list
}
}
fun ResetModel() {
agentLoader.clear()
isFirstLoad = true
}
override fun onCleared() {
super.onCleared()
EventBus.getDefault().unregister(this)
}
}

View File

@@ -0,0 +1,119 @@
package com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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 com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.AgentCard
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MineAgent() {
val AppColors = LocalAppTheme.current
val model = MineAgentViewModel
var agentList = model.agentList
val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.refreshPager(
pullRefresh = true
)
})
val listState = rememberLazyListState()
// observe list scrolling
val reachedBottom by remember {
derivedStateOf {
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()
lastVisibleItem?.index != 0 && lastVisibleItem?.index == listState.layoutInfo.totalItemsCount - 2
}
}
// load more if scrolled to bottom
LaunchedEffect(reachedBottom) {
if (reachedBottom) {
model.loadMore()
}
}
LaunchedEffect(Unit) {
model.refreshPager()
}
Column(
modifier = Modifier
.fillMaxSize()
) {
if(agentList.size == 0) {
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.mipmap.rider_pro_following_empty),
contentDescription = null,
modifier = Modifier.size(140.dp)
)
Spacer(modifier = Modifier.size(32.dp))
androidx.compose.material.Text(
text = "You haven't followed anyone yet",
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.size(16.dp))
androidx.compose.material.Text(
text = "Click start your social journey.",
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W400
)
}
}
}else{
Box(Modifier.pullRefresh(state)) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState
) {
items(
agentList.size,
key = { idx -> idx }
) { idx ->
val agentItem = agentList[idx] ?: return@items
AgentCard(agentEntity = agentItem,
)
}
}
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
}
}
}
}

View File

@@ -0,0 +1,22 @@
package com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.aiosman.ravenow.entity.AgentLoaderExtraArgs
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
import com.aiosman.ravenow.ui.index.tabs.ai.BaseAgentModel
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
import org.greenrobot.eventbus.EventBus
object MineAgentViewModel : BaseAgentModel() {
init {
//EventBus.getDefault().register(this)
}
override fun extraArgs(): AgentLoaderExtraArgs {
return AgentLoaderExtraArgs()
}
}

View File

@@ -93,9 +93,12 @@ fun NotificationsScreen() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
.padding(horizontal = 15.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 左侧占位元素
Box(modifier = Modifier.size(24.dp))
// 左侧 Columnlabel 居中显示
Column(
modifier = Modifier
@@ -105,21 +108,24 @@ fun NotificationsScreen() {
) {
Text(
text = stringResource(R.string.main_message),
fontSize = 16.sp,
fontSize = 17.sp,
fontWeight = FontWeight.W700,
color = AppColors.text
)
}
// 右侧图片按钮:靠右显示
// 右侧图标
Image(
painter = painterResource(id = R.drawable.rider_pro_favourite),
contentDescription = stringResource(R.string.main_message),
painter = painterResource(id = R.drawable.rider_pro_group),
contentDescription = "add",
modifier = Modifier
.size(24.dp)
.noRippleClickable {
// 点击事件
}
},
colorFilter = ColorFilter.tint(AppColors.text)
)
}
Row(
modifier = Modifier

View File

@@ -53,6 +53,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
@@ -61,6 +62,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppState
@@ -384,11 +386,22 @@ fun ProfileV3(
),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon), // Replace with your image resource
contentDescription = "Back",
modifier = Modifier
.noRippleClickable {
navController.navigateUp()
}
.size(24.dp),
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(8.dp))
CustomAsyncImage(
LocalContext.current,
profile?.avatar,
modifier = Modifier
.size(48.dp)
.size(32.dp)
.clip(CircleShape),
contentDescription = "",
contentScale = ContentScale.Crop