diff --git a/core/data-base/src/main/java/com/linky/core/data_base/link/dao/LinkDao.kt b/core/data-base/src/main/java/com/linky/core/data_base/link/dao/LinkDao.kt index fb2467b..a60d9d7 100644 --- a/core/data-base/src/main/java/com/linky/core/data_base/link/dao/LinkDao.kt +++ b/core/data-base/src/main/java/com/linky/core/data_base/link/dao/LinkDao.kt @@ -29,7 +29,7 @@ interface LinkDao { suspend fun deleteRemovedAll() @Transaction - @Query("SELECT * FROM link WHERE isRemove == 0") + @Query("SELECT * FROM link WHERE isRemove == 0 ORDER BY link.createAt DESC") fun selectPage(): PagingSource @Query("SELECT * FROM link WHERE pk == :id") @@ -51,6 +51,7 @@ interface LinkDao { ) ) AND isRemove == 0 + ORDER BY link.createAt DESC """ ) fun selectLinksByTagName(tagName: String): PagingSource diff --git a/feature/timeline/src/main/java/com/linky/timeline/TimeLineViewModel.kt b/feature/timeline/src/main/java/com/linky/timeline/TimeLineViewModel.kt index 794cae9..64ae6de 100644 --- a/feature/timeline/src/main/java/com/linky/timeline/TimeLineViewModel.kt +++ b/feature/timeline/src/main/java/com/linky/timeline/TimeLineViewModel.kt @@ -49,7 +49,7 @@ class TimeLineViewModel @Inject constructor( } } - init { + private fun getLinks() { intent { val links = savedStateHandle.get(INTENT_KEY_TAG_NAME) ?.let { selectLinkByTagNameUseCase.invoke(it) } @@ -58,6 +58,10 @@ class TimeLineViewModel @Inject constructor( reduce { state.copy(links = links.cachedIn(viewModelScope)) } } } + + init { + getLinks() + } } sealed interface TimeLineAction { diff --git a/feature/timeline/src/main/java/com/linky/timeline/component/TimeLineList.kt b/feature/timeline/src/main/java/com/linky/timeline/component/TimeLineList.kt index a793d70..38aaebd 100644 --- a/feature/timeline/src/main/java/com/linky/timeline/component/TimeLineList.kt +++ b/feature/timeline/src/main/java/com/linky/timeline/component/TimeLineList.kt @@ -23,6 +23,7 @@ import androidx.compose.material.Card import androidx.compose.material.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -49,6 +50,7 @@ import com.linky.design_system.ui.component.text.LinkyText import com.linky.design_system.ui.theme.ColorFamilyGray300AndGray800 import com.linky.design_system.ui.theme.ColorFamilyGray600AndGray400 import com.linky.design_system.ui.theme.ColorFamilyGray800AndGray300 +import com.linky.design_system.ui.theme.ColorFamilyGray800AndGray400 import com.linky.design_system.ui.theme.ColorFamilyGray900AndGray100 import com.linky.design_system.ui.theme.ColorFamilyWhiteAndGray900 import com.linky.design_system.ui.theme.ColorFamilyWhiteAndGray999 @@ -59,6 +61,9 @@ import com.linky.model.Link import com.skydoves.balloon.compose.Balloon import com.skydoves.balloon.compose.rememberBalloonBuilder import com.skydoves.balloon.compose.setBackgroundColor +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId @Composable internal fun TimeLineList( @@ -72,140 +77,175 @@ internal fun TimeLineList( onClick: (Link) -> Unit, onCopyLink: (Link) -> Unit, ) { + val today = remember { LocalDate.now() } + val yesterday = remember { today.minusDays(1) } + val oneWeekAgo = remember { today.minusWeeks(1) } + val oneMonthAgo = remember { today.minusMonths(1) } + + val grouping by remember(links) { + derivedStateOf { + links.groupBy { link -> + val date = Instant + .ofEpochMilli(link.createAt) + .atZone(ZoneId.systemDefault()) + .toLocalDate() + + when { + date.isEqual(today) -> "오늘" + date.isEqual(yesterday) -> "어제" + date.isAfter(oneWeekAgo) -> "최근 일주일" + date.isAfter(oneMonthAgo) -> "최근 한 달" + else -> "오래된 데이터" + } + } + } + } + LazyColumn( modifier = modifier, state = state, contentPadding = PaddingValues(16.dp) ) { - items( - items = links, - key = { it.id ?: 0L }, - contentType = { "TimeLineItems" } - ) { link -> - Card( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 10.dp) - .clickableRipple(enableRipple = false) { onClick.invoke(link) }, - shape = RoundedCornerShape(12.dp), - backgroundColor = ColorFamilyWhiteAndGray999 - ) { - Column( + grouping.forEach { (groupName, groupItems) -> + item { + LinkyText( + modifier = Modifier.padding(5.dp), + text = groupName, + color = ColorFamilyGray800AndGray400, + fontWeight = FontWeight.Bold, + fontSize = 18.dp + ) + } + items( + items = groupItems, + key = { it.id ?: 0L }, + contentType = { "TimeLineItems" } + ) { link -> + Card( modifier = Modifier .fillMaxWidth() - .padding(top = 10.dp, bottom = 12.dp, start = 12.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally + .padding(bottom = 10.dp) + .clickableRipple(enableRipple = false) { onClick.invoke(link) }, + shape = RoundedCornerShape(12.dp), + backgroundColor = ColorFamilyWhiteAndGray999 ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - LinkyText( - text = link.createAtFormat, - color = ColorFamilyGray800AndGray300, - fontSize = 11.dp, - fontWeight = FontWeight.Medium - ) - Spacer( - modifier = Modifier - .padding(horizontal = 4.dp) - .width(1.dp) - .height(8.dp) - .background(ColorFamilyGray600AndGray400) - ) - LinkyText( - text = link.readCountFormat, - color = ColorFamilyGray800AndGray300, - fontSize = 11.dp, - fontWeight = FontWeight.Medium - ) - } - MenuButton( - onEdit = { onEdit.invoke(link) }, - onRemove = { onRemove.invoke(link.id!!) } - ) - } - Row( + Column( modifier = Modifier .fillMaxWidth() - .padding(top = 10.dp), - verticalAlignment = Alignment.CenterVertically + .padding(top = 10.dp, bottom = 12.dp, start = 12.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally ) { - SubcomposeAsyncImage( - modifier = Modifier - .size(98.dp) - .clip(RoundedCornerShape(4.dp)), - imageLoader = imageLoader, - model = link.openGraphData.image, - contentScale = ContentScale.Crop, - contentDescription = "thumbnail" - ) - Column( - modifier = Modifier - .height(98.dp) - .padding(horizontal = 10.dp), + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { - if (link.memo.isNotEmpty()) { - LinkyText( - text = link.memo, - color = ColorFamilyGray900AndGray100, - fontSize = 15.dp, - fontWeight = FontWeight.SemiBold, - lineHeight = 20.sp - ) - Spacer(modifier = Modifier.padding(bottom = 7.dp)) - } - LazyRow( - horizontalArrangement = Arrangement.Start, + Row( verticalAlignment = Alignment.CenterVertically, ) { - items(link.tags) { tag -> - TimeLineTagChip( - modifier = Modifier.padding(end = 3.dp), - tagName = tag.name, - ) - } + LinkyText( + text = link.createAtFormat, + color = ColorFamilyGray800AndGray300, + fontSize = 11.dp, + fontWeight = FontWeight.Medium + ) + Spacer( + modifier = Modifier + .padding(horizontal = 4.dp) + .width(1.dp) + .height(8.dp) + .background(ColorFamilyGray600AndGray400) + ) + LinkyText( + text = link.readCountFormat, + color = ColorFamilyGray800AndGray300, + fontSize = 11.dp, + fontWeight = FontWeight.Medium + ) } - Spacer( - modifier = Modifier.weight(1f) + MenuButton( + onEdit = { onEdit.invoke(link) }, + onRemove = { onRemove.invoke(link.id!!) } ) - // 태그 - Spacer( + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + SubcomposeAsyncImage( modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(Gray400) + .size(98.dp) + .clip(RoundedCornerShape(4.dp)), + imageLoader = imageLoader, + model = link.openGraphData.image, + contentScale = ContentScale.Crop, + contentDescription = "thumbnail" ) - Row( + Column( modifier = Modifier - .fillMaxWidth() - .padding(top = 6.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween + .height(98.dp) + .padding(horizontal = 10.dp), ) { - LinkyText( - text = link.openGraphData.title ?: "", - color = ColorFamilyGray800AndGray300, - fontSize = 12.dp, - fontWeight = FontWeight.SemiBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(0.7f), + if (link.memo.isNotEmpty()) { + LinkyText( + text = link.memo, + color = ColorFamilyGray900AndGray100, + fontSize = 15.dp, + fontWeight = FontWeight.SemiBold, + lineHeight = 20.sp + ) + Spacer(modifier = Modifier.padding(bottom = 7.dp)) + } + LazyRow( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + ) { + items(link.tags) { tag -> + TimeLineTagChip( + modifier = Modifier.padding(end = 3.dp), + tagName = tag.name, + ) + } + } + Spacer( + modifier = Modifier.weight(1f) ) - Spacer(modifier = Modifier.weight(0.2f)) - - Image( - painter = painterResource(R.drawable.ico_tag_copy), - contentDescription = "copy", + // 태그 + Spacer( modifier = Modifier - .weight(0.1f) - .clickableRipple(radius = 10.dp) { onCopyLink.invoke(link) }, + .fillMaxWidth() + .height(1.dp) + .background(Gray400) ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 6.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + LinkyText( + text = link.openGraphData.title ?: "", + color = ColorFamilyGray800AndGray300, + fontSize = 12.dp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(0.7f), + ) + Spacer(modifier = Modifier.weight(0.2f)) + + Image( + painter = painterResource(R.drawable.ico_tag_copy), + contentDescription = "copy", + modifier = Modifier + .weight(0.1f) + .clickableRipple(radius = 10.dp) { onCopyLink.invoke(link) }, + ) + } } } }