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 // 默认偏移
|
|
}
|
|
} |