-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TNT-180] 트레이니 주간 캘린더 연동 #67
Merged
Merged
Changes from 6 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
73366b9
[TNT-180] feat: 트레이니 주간 캘린더 연동
SeonJeongk ff636c5
[TNT-178] feat: 홈 화면에서 공통으로 사용 가능한 TopBar 구현
hoyahozz 7e60275
[TNT-180] refactor: TnTHomeTopBar 적용 및 네이밍 수정
SeonJeongk 63b34b4
[TNT-180] feat: 최소, 최대 년도 지정 및 홈 공통 TopBar 적용
SeonJeongk 1b8ba68
[TNT-178] feat: 홈 화면 내 캘린더 UI 연동
hoyahozz ed3afe6
[TNT-180] fix: 트레이니 주간 캘린더 월 표시 조건에 맞게 수정
SeonJeongk 56ee465
[TNT-180] fix: 주간 캘린더 가장 많이 보여지는 YearMonth 넘겨주도록 수정
SeonJeongk d47807d
[TNT-180] fix: 회원가입 사진 파일 형식 제한
SeonJeongk a468017
[TNT-180] fix: 불필요한 코드 삭제
SeonJeongk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
75 changes: 75 additions & 0 deletions
75
...signsystem/src/main/java/co/kr/tnt/designsystem/component/calendar/utils/CalendarUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package co.kr.tnt.designsystem.component.calendar.utils | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.snapshotFlow | ||
import com.kizitonwose.calendar.compose.CalendarLayoutInfo | ||
import com.kizitonwose.calendar.compose.CalendarState | ||
import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState | ||
import com.kizitonwose.calendar.core.CalendarMonth | ||
import kotlinx.coroutines.flow.filterNotNull | ||
import java.time.LocalDate | ||
import java.time.YearMonth | ||
|
||
/** | ||
* 주어진 [viewportPercent] 를 기준으로, 현재 화면상으로 가장 많이 보이는 '월' 을 반환합니다. | ||
*/ | ||
@Composable | ||
fun rememberMostVisibleMonth( | ||
state: CalendarState, | ||
viewportPercent: Float = 90f, | ||
): YearMonth { | ||
val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleMonth) } | ||
LaunchedEffect(state) { | ||
snapshotFlow { state.layoutInfo.firstMostVisibleMonth(viewportPercent) } | ||
.filterNotNull() | ||
.collect { month -> visibleMonth.value = month } | ||
} | ||
return visibleMonth.value.yearMonth | ||
} | ||
|
||
private fun CalendarLayoutInfo.firstMostVisibleMonth(viewportPercent: Float = 50f): CalendarMonth? { | ||
return if (visibleMonthsInfo.isEmpty()) { | ||
null | ||
} else { | ||
val viewportSize = (viewportEndOffset + viewportStartOffset) * viewportPercent / 100f | ||
visibleMonthsInfo.firstOrNull { itemInfo -> | ||
if (itemInfo.offset < 0) { | ||
itemInfo.offset + itemInfo.size >= viewportSize | ||
} else { | ||
itemInfo.size - itemInfo.offset >= viewportSize | ||
} | ||
}?.month | ||
} | ||
} | ||
|
||
/** | ||
* 화면에 보여지는 7일 중 과반 이상 (4일) 보여지는 '월' 을 반환합니다. | ||
*/ | ||
@Composable | ||
fun rememberMostVisibleYearMonth( | ||
state: WeekCalendarState, | ||
): YearMonth { | ||
val visibleYearMonth = remember { mutableStateOf(YearMonth.now()) } | ||
|
||
LaunchedEffect(state) { | ||
snapshotFlow { state.firstVisibleWeek.days.map { it.date } } | ||
.collect { visibleDays -> | ||
val dominantYearMonth = getDominantYearMonth(visibleDays) | ||
visibleYearMonth.value = dominantYearMonth | ||
} | ||
} | ||
|
||
return visibleYearMonth.value | ||
} | ||
|
||
private fun getDominantYearMonth(visibleDays: List<LocalDate>): YearMonth { | ||
val yearMonthCount = visibleDays.groupingBy { YearMonth.from(it) }.eachCount() | ||
|
||
return yearMonthCount.entries | ||
.sortedByDescending { it.value } | ||
.firstOrNull { it.value >= 4 }?.key | ||
?: YearMonth.from(visibleDays.first()) | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="24dp" | ||
android:height="24dp" | ||
android:viewportWidth="24" | ||
android:viewportHeight="24"> | ||
<path | ||
android:pathData="M5.202,17.5H18.798C19.844,17.5 20.551,16.434 20.146,15.471L19.701,14.415C19.238,13.316 19,12.136 19,10.944V9C19,5.134 15.866,2 12,2C8.134,2 5,5.134 5,9V10.944C5,12.136 4.762,13.316 4.299,14.415L3.854,15.471C3.449,16.434 4.156,17.5 5.202,17.5Z" | ||
android:strokeWidth="2" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#262626"/> | ||
<path | ||
android:pathData="M9,18C9,18 9,21 12,21C15,21 15,18 15,18" | ||
android:strokeLineJoin="round" | ||
android:strokeWidth="2" | ||
android:fillColor="#00000000" | ||
android:strokeColor="#262626" | ||
android:strokeLineCap="round"/> | ||
</vector> |
74 changes: 74 additions & 0 deletions
74
core/ui/src/main/java/co/kr/tnt/ui/component/TnTHomeTopBar.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package co.kr.tnt.ui.component | ||
|
||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.WindowInsets | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.size | ||
import androidx.compose.material3.CenterAlignedTopAppBar | ||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.IconButton | ||
import androidx.compose.material3.TopAppBarDefaults | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import co.kr.tnt.core.designsystem.R | ||
import co.kr.tnt.designsystem.component.calendar.TnTCalendarSelector | ||
import co.kr.tnt.designsystem.theme.TnTTheme | ||
import java.time.YearMonth | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Composable | ||
fun TnTHomeTopBar( | ||
modifier: Modifier = Modifier, | ||
yearMonth: YearMonth = YearMonth.now(), | ||
onClickSelectorPrevious: () -> Unit = { }, | ||
onClickSelectorNext: () -> Unit = { }, | ||
onClickNotification: () -> Unit = { }, | ||
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, | ||
) { | ||
Column { | ||
CenterAlignedTopAppBar( | ||
modifier = modifier | ||
.fillMaxWidth() | ||
.padding( | ||
vertical = 8.dp, | ||
horizontal = 16.dp, | ||
), | ||
title = { | ||
TnTCalendarSelector( | ||
yearMonth = yearMonth, | ||
onClickPrevious = onClickSelectorPrevious, | ||
onClickNext = onClickSelectorNext, | ||
) | ||
}, | ||
actions = { | ||
IconButton( | ||
onClick = onClickNotification, | ||
modifier = Modifier.size(32.dp), | ||
) { | ||
Icon( | ||
painter = painterResource(R.drawable.ic_alarm), | ||
contentDescription = "Go to notification screen", | ||
) | ||
} | ||
}, | ||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors( | ||
containerColor = TnTTheme.colors.commonColors.Common0, | ||
), | ||
windowInsets = windowInsets, | ||
expandedHeight = 48.dp, | ||
) | ||
} | ||
} | ||
|
||
@Preview(showBackground = true) | ||
@Composable | ||
private fun TnTHomeTopBarPreview() { | ||
TnTTheme { | ||
TnTHomeTopBar() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package co.kr.tnt.domain.model | ||
|
||
import java.time.LocalDate | ||
|
||
data class RecordList( | ||
val recordDate: LocalDate, | ||
val recordType: RecordType, | ||
val recordTime: String, | ||
val recordImage: String?, | ||
val recordContents: String, | ||
val hasFeedback: Boolean, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,5 @@ android { | |
|
||
dependencies { | ||
implementation(libs.kotlinx.immutable) | ||
implementation(libs.calendar.compose) | ||
} |
25 changes: 25 additions & 0 deletions
25
feature/trainee/home/src/main/java/co/kr/tnt/trainee/home/TraineeHomeContract.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package co.kr.tnt.trainee.home | ||
|
||
import co.kr.tnt.domain.model.RecordList | ||
import co.kr.tnt.ui.base.UiEvent | ||
import co.kr.tnt.ui.base.UiSideEffect | ||
import co.kr.tnt.ui.base.UiState | ||
import java.time.LocalDate | ||
|
||
class TraineeHomeContract { | ||
data class TraineeHomeUiState( | ||
val selectedDate: LocalDate = LocalDate.now(), | ||
val markedDates: List<LocalDate> = emptyList(), | ||
val recordList: List<RecordList> = emptyList(), | ||
) : UiState | ||
|
||
sealed interface TraineeHomeUiEvent : UiEvent { | ||
data object OnClickNextWeek : TraineeHomeUiEvent | ||
data object OnClickPreviousWeek : TraineeHomeUiEvent | ||
data class OnClickDay(val date: LocalDate) : TraineeHomeUiEvent | ||
} | ||
|
||
sealed interface TraineeHomeEffect : UiSideEffect { | ||
data class ShowToast(val message: String) : TraineeHomeEffect | ||
} | ||
} |
136 changes: 121 additions & 15 deletions
136
feature/trainee/home/src/main/java/co/kr/tnt/trainee/home/TraineeHomeScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,143 @@ | ||
package co.kr.tnt.trainee.home | ||
|
||
import androidx.compose.foundation.layout.Column | ||
import android.widget.Toast | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material3.Button | ||
import androidx.compose.foundation.lazy.LazyColumn | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.rememberCoroutineScope | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import androidx.compose.ui.unit.dp | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||
import co.kr.tnt.designsystem.component.calendar.TnTIndicatorWeekCalendar | ||
import co.kr.tnt.designsystem.component.calendar.model.DayIndicatorState | ||
import co.kr.tnt.designsystem.component.calendar.model.DayState | ||
import co.kr.tnt.designsystem.component.calendar.utils.rememberMostVisibleYearMonth | ||
import co.kr.tnt.designsystem.theme.TnTTheme | ||
import co.kr.tnt.trainee.home.TraineeHomeContract.TraineeHomeUiEvent | ||
import co.kr.tnt.trainee.home.TraineeHomeContract.TraineeHomeUiState | ||
import co.kr.tnt.ui.component.TnTHomeTopBar | ||
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState | ||
import kotlinx.coroutines.launch | ||
import java.time.DayOfWeek | ||
import java.time.LocalDate | ||
|
||
@Composable | ||
@Suppress("UnusedParameter") | ||
internal fun TraineeHomeRoute( | ||
viewModel: TraineeHomeViewModel = hiltViewModel(), | ||
navigateToNotification: () -> Unit, | ||
) { | ||
TraineeHomeScreen(navigateToNotification) | ||
val context = LocalContext.current | ||
val uiState by viewModel.uiState.collectAsStateWithLifecycle() | ||
|
||
TraineeHomeScreen( | ||
state = uiState, | ||
onClickNotification = navigateToNotification, | ||
onSelectDate = { date -> | ||
viewModel.setEvent(TraineeHomeUiEvent.OnClickDay(date)) | ||
}, | ||
onClickNextWeek = { viewModel.setEvent(TraineeHomeUiEvent.OnClickNextWeek) }, | ||
onClickPreviousWeek = { viewModel.setEvent(TraineeHomeUiEvent.OnClickPreviousWeek) }, | ||
) | ||
|
||
LaunchedEffect(viewModel.effect) { | ||
viewModel.effect.collect { effect -> | ||
when (effect) { | ||
is TraineeHomeContract.TraineeHomeEffect.ShowToast -> { | ||
Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
fun TraineeHomeScreen( | ||
navigateToNotification: () -> Unit, | ||
private fun TraineeHomeScreen( | ||
state: TraineeHomeUiState, | ||
onSelectDate: (LocalDate) -> Unit, | ||
onClickNextWeek: () -> Unit, | ||
onClickPreviousWeek: () -> Unit, | ||
onClickNotification: () -> Unit, | ||
) { | ||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> | ||
Column { | ||
Text( | ||
text = "trainee home", | ||
modifier = Modifier.padding(innerPadding), | ||
) | ||
Button(onClick = navigateToNotification) { | ||
Text("navigate to notification") | ||
val now = LocalDate.now() | ||
val coroutineScope = rememberCoroutineScope() | ||
|
||
val weekCalendarState = rememberWeekCalendarState( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 우리 앱의 첫번째 요일은 '일요일'이므로 별도로 |
||
firstDayOfWeek = DayOfWeek.SUNDAY, | ||
firstVisibleWeekDate = state.selectedDate, | ||
startDate = now.minusYears(10), | ||
endDate = now.plusYears(10), | ||
) | ||
|
||
val visibleYearMonth = rememberMostVisibleYearMonth(weekCalendarState) | ||
|
||
Scaffold( | ||
containerColor = TnTTheme.colors.commonColors.Common0, | ||
modifier = Modifier.fillMaxSize(), | ||
) { innerPadding -> | ||
LazyColumn(modifier = Modifier.padding(innerPadding)) { | ||
item { | ||
Spacer(modifier = Modifier.height(12.dp)) | ||
TnTHomeTopBar( | ||
yearMonth = visibleYearMonth, | ||
onClickSelectorPrevious = { | ||
coroutineScope.launch { | ||
onClickPreviousWeek() | ||
weekCalendarState.animateScrollToWeek(state.selectedDate) | ||
} | ||
}, | ||
onClickSelectorNext = { | ||
coroutineScope.launch { | ||
onClickNextWeek() | ||
weekCalendarState.animateScrollToWeek(state.selectedDate) | ||
} | ||
}, | ||
onClickNotification = onClickNotification, | ||
) | ||
Spacer(modifier = Modifier.height(16.dp)) | ||
TnTIndicatorWeekCalendar( | ||
state = weekCalendarState, | ||
dayState = { date -> | ||
DayState(isSelected = date == state.selectedDate) | ||
}, | ||
indicatorState = { date -> | ||
DayIndicatorState(showIcon = date in state.markedDates) | ||
}, | ||
onClickDay = { date -> | ||
onSelectDate(date) | ||
}, | ||
) | ||
Spacer(modifier = Modifier.height(12.dp)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Preview | ||
@Composable | ||
private fun TraineeHomeScreenPreview() { | ||
val now = LocalDate.now() | ||
|
||
val dummyUiState = TraineeHomeUiState( | ||
selectedDate = now, | ||
markedDates = List(5) { now.minusDays(it.toLong() * 2) }, | ||
) | ||
|
||
TnTTheme { | ||
TraineeHomeScreen( | ||
state = dummyUiState, | ||
onClickNotification = { }, | ||
onSelectDate = {}, | ||
onClickNextWeek = { }, | ||
onClickPreviousWeek = { }, | ||
) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요거 단순하게
Grouping
한 후에 값이 가장 많은YearMonth
로 넘겨주는건 어떨까요?캘린더 상에서 과반이 항상 4일 이상을 의미하는건 아닐 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옹 넵 수정하고 머지하겠습니다!! 🙇