249 lines
8.7 KiB
Kotlin
249 lines
8.7 KiB
Kotlin
|
package com.taskttl.ui.screen
|
||
|
|
||
|
import androidx.compose.foundation.Image
|
||
|
import androidx.compose.foundation.background
|
||
|
import androidx.compose.foundation.layout.Box
|
||
|
import androidx.compose.foundation.layout.Column
|
||
|
import androidx.compose.foundation.layout.Row
|
||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||
|
import androidx.compose.foundation.layout.height
|
||
|
import androidx.compose.foundation.layout.padding
|
||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||
|
import androidx.compose.foundation.lazy.items
|
||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||
|
import androidx.compose.material3.Card
|
||
|
import androidx.compose.material3.CardDefaults
|
||
|
import androidx.compose.material3.HorizontalDivider
|
||
|
import androidx.compose.material3.SnackbarHostState
|
||
|
import androidx.compose.material3.Text
|
||
|
import androidx.compose.runtime.Composable
|
||
|
import androidx.compose.runtime.LaunchedEffect
|
||
|
import androidx.compose.runtime.collectAsState
|
||
|
import androidx.compose.runtime.derivedStateOf
|
||
|
import androidx.compose.runtime.getValue
|
||
|
import androidx.compose.runtime.mutableStateOf
|
||
|
import androidx.compose.runtime.remember
|
||
|
import androidx.compose.runtime.setValue
|
||
|
import androidx.compose.ui.Alignment
|
||
|
import androidx.compose.ui.Modifier
|
||
|
import androidx.compose.ui.geometry.Offset
|
||
|
import androidx.compose.ui.graphics.Color
|
||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||
|
import androidx.compose.ui.layout.ContentScale
|
||
|
import androidx.compose.ui.res.painterResource
|
||
|
import androidx.compose.ui.res.stringResource
|
||
|
import androidx.compose.ui.text.font.FontWeight
|
||
|
import androidx.compose.ui.unit.Dp
|
||
|
import androidx.compose.ui.unit.dp
|
||
|
import androidx.compose.ui.unit.sp
|
||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||
|
import androidx.navigation.NavController
|
||
|
import androidx.navigation.NavGraphBuilder
|
||
|
import androidx.navigation.NavOptions
|
||
|
import androidx.navigation.NavType
|
||
|
import androidx.navigation.compose.composable
|
||
|
import androidx.navigation.navArgument
|
||
|
import com.google.gson.Gson
|
||
|
import com.taskttl.R
|
||
|
import com.taskttl.data.composableWithCompositionLocal
|
||
|
import com.taskttl.data.ext.cancelRipperClick
|
||
|
import com.taskttl.data.local.entity.TaskEntity
|
||
|
import com.taskttl.ui.components.NotTask
|
||
|
import com.taskttl.ui.components.TaskItem
|
||
|
import com.taskttl.ui.viewmodel.IndexViewModel
|
||
|
import com.taskttl.utils.DateUtil
|
||
|
import com.taskttl.utils.Logger
|
||
|
|
||
|
/** 首页 */
|
||
|
const val INDEX_ROUTE = "index"
|
||
|
const val HOME_DETAIL_ROUTE = "home_detail"
|
||
|
|
||
|
fun NavController.navigateToIndex(navOptions: NavOptions) = navigate(INDEX_ROUTE, navOptions)
|
||
|
|
||
|
fun NavGraphBuilder.indexScreen(
|
||
|
snackBarHostState: SnackbarHostState,
|
||
|
// onSettingClick: () -> Unit,
|
||
|
// navigateToDetail: (ContentBean) -> Unit
|
||
|
) {
|
||
|
composable(INDEX_ROUTE) {
|
||
|
IndexScreen(
|
||
|
snackBarHostState,
|
||
|
// onSettingClick,
|
||
|
// navigateToDetail
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fun NavGraphBuilder.homeDetailScreen(
|
||
|
navigateToHome: () -> Unit
|
||
|
) {
|
||
|
composableWithCompositionLocal(
|
||
|
route = "$HOME_DETAIL_ROUTE/{content}",
|
||
|
arguments = listOf(navArgument("content") { NavType.StringType })
|
||
|
) {
|
||
|
val contentBean = try {
|
||
|
Gson().fromJson(it.arguments?.getString("content") ?: "", TaskEntity::class.java)
|
||
|
} catch (e: Exception) {
|
||
|
null
|
||
|
}
|
||
|
HomeDetailRoute()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Composable
|
||
|
fun HomeDetailRoute() {
|
||
|
Box(
|
||
|
modifier = Modifier
|
||
|
.fillMaxSize(),
|
||
|
contentAlignment = Alignment.Center
|
||
|
) {
|
||
|
Text(text = "Message")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 首页
|
||
|
* @param [navController] NAV 控制器
|
||
|
* @param [indexViewModel] 索引视图模型
|
||
|
*/
|
||
|
@Composable
|
||
|
fun IndexScreen(snackBarHostState: SnackbarHostState) {
|
||
|
val indexViewModel = hiltViewModel<IndexViewModel>();
|
||
|
val state by indexViewModel.uiState.collectAsState()
|
||
|
|
||
|
var isButtonVisible by remember { mutableStateOf(true) }
|
||
|
val listState = rememberLazyListState()
|
||
|
|
||
|
var backgroundHeight by remember { mutableStateOf(160.dp) }
|
||
|
|
||
|
val textTopPadding = remember(backgroundHeight) {
|
||
|
mutableStateOf(calculateTextTopPadding(backgroundHeight))
|
||
|
}
|
||
|
|
||
|
val nestedScrollConnection = remember {
|
||
|
object : NestedScrollConnection {
|
||
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||
|
val delta = available.y
|
||
|
var dp = backgroundHeight + delta.dp
|
||
|
if (dp >= 160.dp) {
|
||
|
dp = 160.dp
|
||
|
}
|
||
|
backgroundHeight = maxOf(96.dp, dp) // 最小高度 100.dp
|
||
|
textTopPadding.value = calculateTextTopPadding(backgroundHeight)
|
||
|
return Offset.Zero
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 根据滚动状态来更新按钮的可见性
|
||
|
LaunchedEffect(remember { derivedStateOf { listState.firstVisibleItemIndex } }) {
|
||
|
isButtonVisible = listState.firstVisibleItemIndex == 0
|
||
|
}
|
||
|
|
||
|
Column(
|
||
|
modifier = Modifier
|
||
|
.fillMaxSize()
|
||
|
.background(Color(0xFFF1F5F9))
|
||
|
) {
|
||
|
Card(
|
||
|
modifier = Modifier
|
||
|
.fillMaxWidth()
|
||
|
.height(backgroundHeight),
|
||
|
shape = RoundedCornerShape(0.dp)
|
||
|
) {
|
||
|
Box() {
|
||
|
// 背景图
|
||
|
Image(
|
||
|
painter = painterResource(R.mipmap.header),
|
||
|
modifier = Modifier
|
||
|
.fillMaxWidth()
|
||
|
.height(backgroundHeight),
|
||
|
contentScale = ContentScale.Crop,
|
||
|
contentDescription = "Header"
|
||
|
)
|
||
|
if (textTopPadding.value > 50.dp) {
|
||
|
Text(
|
||
|
DateUtil.today(),
|
||
|
fontSize = 16.sp,
|
||
|
fontWeight = FontWeight.Bold,
|
||
|
color = Color.White,
|
||
|
modifier = Modifier
|
||
|
.align(Alignment.TopCenter)
|
||
|
.padding(top = 36.dp)
|
||
|
)
|
||
|
}
|
||
|
|
||
|
Text(
|
||
|
stringResource(R.string.index_title),
|
||
|
fontSize = 30.sp,
|
||
|
fontWeight = FontWeight.Bold,
|
||
|
color = Color.White,
|
||
|
modifier = Modifier
|
||
|
.align(Alignment.TopCenter)
|
||
|
.padding(top = textTopPadding.value)
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (state.uncompletedList.isEmpty() && state.completedList.isEmpty()) {
|
||
|
NotTask()
|
||
|
} else {
|
||
|
Card(
|
||
|
modifier = Modifier
|
||
|
// .align(Alignment.TopCenter)
|
||
|
.padding(all = 10.dp),
|
||
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||
|
shape = RoundedCornerShape(16.dp)
|
||
|
) {
|
||
|
LazyColumn(
|
||
|
state = listState,
|
||
|
modifier = Modifier
|
||
|
.fillMaxSize()
|
||
|
.nestedScroll(nestedScrollConnection)
|
||
|
) {
|
||
|
items(state.uncompletedList) {
|
||
|
TaskItem(it)
|
||
|
if (state.uncompletedList.indexOf(it) != state.uncompletedList.lastIndex) {
|
||
|
HorizontalDivider(
|
||
|
modifier = Modifier
|
||
|
.fillMaxWidth()
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if (isButtonVisible) {
|
||
|
Row(
|
||
|
modifier = Modifier
|
||
|
// .align(Alignment.BottomCenter)
|
||
|
.padding(start = 16.dp, end = 16.dp, bottom = 24.dp)
|
||
|
.fillMaxWidth()
|
||
|
.height(56.dp)
|
||
|
.cancelRipperClick {
|
||
|
// navigateToDetail(contentBean)
|
||
|
// navController.navigate(RouteConfig.ADD)
|
||
|
},
|
||
|
// shape = RoundedCornerShape(50.dp)
|
||
|
) {
|
||
|
Text(text = stringResource(R.string.add_new_task))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 计算 Text 组件的顶部偏移
|
||
|
fun calculateTextTopPadding(currentBackgroundHeight: Dp): Dp {
|
||
|
// 这里可以根据 backgroundHeight 的不同值返回不同的顶部偏移量
|
||
|
// 例如,保持文本始终位于背景图像的中心或某个固定位置。
|
||
|
return when (currentBackgroundHeight.value) {
|
||
|
in 96.0..160.0 -> (96 + (currentBackgroundHeight.value - 160)).dp
|
||
|
else -> 96.dp // 默认偏移
|
||
|
}
|
||
|
}
|