Skip to content

Commit

Permalink
Merge pull request #67 from YAPP-Github/feature/TNT-180
Browse files Browse the repository at this point in the history
[TNT-180] 트레이니 주간 캘린더 연동
  • Loading branch information
SeonJeongk authored Feb 8, 2025
2 parents 1fcce19 + a468017 commit c3ca0ff
Show file tree
Hide file tree
Showing 15 changed files with 544 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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일 중 과반 이상 보여지는 '월' 을 반환합니다.
*/
@Composable
fun rememberMostVisibleYearMonth(
state: WeekCalendarState,
): YearMonth {
val visibleYearMonth = remember { mutableStateOf(YearMonth.now()) }

LaunchedEffect(state) {
snapshotFlow { state.firstVisibleWeek.days.map { it.date } }
.collect { visibleDays ->
val mostVisibleYearMonth = getMostFrequentYearMonth(visibleDays)
visibleYearMonth.value = mostVisibleYearMonth
}
}

return visibleYearMonth.value
}

private fun getMostFrequentYearMonth(visibleDays: List<LocalDate>): YearMonth {
return visibleDays
.groupingBy { YearMonth.from(it) }
.eachCount()
.maxBy { it.value }
.key
}
18 changes: 18 additions & 0 deletions core/designsystem/src/main/res/drawable/ic_alarm.xml
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 core/ui/src/main/java/co/kr/tnt/ui/component/TnTHomeTopBar.kt
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()
}
}
22 changes: 22 additions & 0 deletions core/ui/src/main/java/co/kr/tnt/ui/utils/FileUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package co.kr.tnt.ui.utils

import android.content.Context
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import java.io.File
import java.io.FileOutputStream

fun Uri.toFile(context: Context): File? {
return getRealPathFromUri(this, context)?.let { filePath ->
Expand All @@ -27,3 +30,22 @@ fun getRealPathFromUri(uri: Uri, context: Context): String? {
}
return null
}

fun Uri.convertToAllowedImageFormat(context: Context): File {
val inputStream = context.contentResolver.openInputStream(this)
val bitmap = BitmapFactory.decodeStream(inputStream)

val convertedFile = File(context.cacheDir, "image.png")
val outputStream = FileOutputStream(convertedFile)

bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
outputStream.flush()
outputStream.close()

return convertedFile
}

fun isAllowedImageFormat(file: File): Boolean {
val allowedExtensions = listOf("jpg", "jpeg", "png", "svg")
return file.extension.lowercase() in allowedExtensions
}
12 changes: 12 additions & 0 deletions domain/src/main/java/co/kr/tnt/domain/model/RecordList.kt
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,
)
1 change: 1 addition & 0 deletions feature/trainee/home/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ android {

dependencies {
implementation(libs.kotlinx.immutable)
implementation(libs.calendar.compose)
}
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
}
}
Loading

0 comments on commit c3ca0ff

Please sign in to comment.