Skip to content

Commit

Permalink
Merge pull request #473 from BCSDLab/feature/timetable_api_v3
Browse files Browse the repository at this point in the history
[Feature] 시간표 3차 구현
  • Loading branch information
Jokwanhee authored Nov 14, 2024
2 parents 8b63b22 + f55322a commit 179295d
Showing 58 changed files with 3,124 additions and 581 deletions.
Original file line number Diff line number Diff line change
@@ -5,7 +5,18 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

/**
* 리플 효과 없는 Clickable
@@ -24,4 +35,44 @@ fun Modifier.noRippleClickable(
role = role,
onClick = onClick
)
}

/**
* 점선(-) 테두리
*/
fun Modifier.dashedBorder(
color: Color,
shape: Shape,
strokeWidth: Dp = 1.dp,
dashLength: Dp = 3.dp,
gapLength: Dp = 3.dp,
cap: StrokeCap = StrokeCap.Round
) = dashedBorder(brush = SolidColor(color), shape, strokeWidth, dashLength, gapLength, cap)

private fun Modifier.dashedBorder(
brush: Brush,
shape: Shape,
strokeWidth: Dp,
dashLength: Dp,
gapLength: Dp,
cap: StrokeCap = StrokeCap.Round
) = this.drawWithContent {

val outline = shape.createOutline(size, layoutDirection, density = this)

val dashedStroke = Stroke(
cap = cap,
width = strokeWidth.toPx(),
pathEffect = PathEffect.dashPathEffect(
intervals = floatArrayOf(dashLength.toPx(), gapLength.toPx())
)
)

drawContent()

drawOutline(
outline = outline,
style = dashedStroke,
brush = brush
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package `in`.koreatech.koin.core.designsystem.component.picker

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
@@ -18,7 +17,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Brush.Companion.verticalGradient
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
@@ -34,21 +39,22 @@ import kotlinx.coroutines.flow.map
* @param visibleItemsCount 보여질 아이템 수
* @param modifier Modifier
* @param infiniteScroll 무한 스크롤 여부
* @param brushVerticalGradient vertical Brush 그라디언트 설정
* @param startIndex 시작 인덱스
* @param contentPadding 아이템 내부 Padding
* @param selectedTextStyle 선택 아이템 텍스트 스타일
* @param unselectedTextStyle 선택되지 않은 아이템 텍스트 스타일
* @param selectedItemColor 선택 아이템 색상
* @param unselectedItemColor 선택되지 않은 아이템 색상
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun KoinPicker(
items: List<String>,
pickerState: PickerState,
visibleItemsCount: Int,
modifier: Modifier = Modifier,
infiniteScroll: Boolean = true,
brushVerticalGradient: Brush = verticalGradient(),
startIndex: Int = 0,
contentPadding: PaddingValues = PaddingValues(vertical = 2.dp),
selectedTextStyle: TextStyle = KoinTheme.typography.medium16,
@@ -74,10 +80,14 @@ fun KoinPicker(
var itemHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current

val fadingEdgeGradient = remember { brushVerticalGradient }

LazyColumn(
state = listState,
flingBehavior = flingBehavior,
modifier = modifier.height(itemHeight * visibleItemsCount)
modifier = modifier
.height(itemHeight * visibleItemsCount)
.fadingEdge(fadingEdgeGradient),
) {
items(listScrollCount) { index ->
Text(
@@ -119,4 +129,12 @@ class PickerState internal constructor() {
var selectedItemIndex by mutableIntStateOf(0)
}

private fun Modifier.fadingEdge(brush: Brush) = this
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
.drawWithContent {
drawContent()
drawRect(brush = brush, blendMode = BlendMode.DstIn)
}


private fun List<String>.getItem(index: Int) = this[index % this.size]
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package `in`.koreatech.koin.core.designsystem.component.snackbar

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import `in`.koreatech.koin.core.designsystem.noRippleClickable
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme

// Surface 사용하지 않을 경우, SnackBar 커스텀
@Composable
fun CustomSnackBarHost(
hotState: SnackbarHostState,
radius: Dp = 0.dp,
messageTextStyle: TextStyle = KoinTheme.typography.regular12.copy(
color = Color.White
),
actionLabelTextStyle: TextStyle = KoinTheme.typography.regular12.copy(
color = Color.White
),
background: Color = Color.Black,
alignment: Alignment = Alignment.BottomCenter,
paddingValues: PaddingValues = PaddingValues(bottom = 20.dp, start = 10.dp, end = 10.dp),
innerPaddingValues: PaddingValues = PaddingValues(horizontal = 10.dp, vertical = 16.dp)
) {
SnackbarHost(
hostState = hotState,
) { snackbarData ->
SnackBarContent(
messageText = snackbarData.visuals.message,
actionLabelText = snackbarData.visuals.actionLabel ?: "",
radius = radius,
background = background,
messageTextStyle = messageTextStyle,
actionLabelTextStyle = actionLabelTextStyle,
alignment = alignment,
paddingValues = paddingValues,
innerPaddingValues = innerPaddingValues,
onAction = { snackbarData.dismiss() }
)
}

}

@Composable
private fun SnackBarContent(
messageText: String,
actionLabelText: String,
modifier: Modifier = Modifier,
radius: Dp = 0.dp,
background: Color = Color.Black,
messageTextStyle: TextStyle = KoinTheme.typography.regular12.copy(
color = Color.White
),
actionLabelTextStyle: TextStyle = KoinTheme.typography.regular12.copy(
color = Color.White
),
alignment: Alignment = Alignment.BottomCenter,
paddingValues: PaddingValues = PaddingValues(bottom = 20.dp, start = 10.dp, end = 10.dp),
innerPaddingValues: PaddingValues = PaddingValues(horizontal = 10.dp, vertical = 16.dp),
onAction: () -> Unit = {}
) {
Box(
modifier = modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = alignment
) {
Box(
modifier = modifier
.fillMaxWidth()
.clip(RoundedCornerShape(radius))
.background(background)
.padding(innerPaddingValues)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Spacer(modifier = Modifier.width(4.dp))
Text(
text = messageText,
style = messageTextStyle,
modifier = Modifier.weight(1f),
)
Spacer(modifier = Modifier.weight(0.05f))
if (actionLabelText.isNotEmpty()) {
Text(
text = actionLabelText,
style = actionLabelTextStyle,
modifier = Modifier.weight(0.1f).noRippleClickable { onAction() }
)
}

}
}
}
}

fun SnackbarHostState.dismissIfShown() {
if (currentSnackbarData != null) {
currentSnackbarData?.dismiss()
}
}

suspend fun SnackbarHostState.showSnackBarWithDismiss(
message: String,
actionLabel: String = "",
duration: SnackbarDuration = SnackbarDuration.Short
) {
dismissIfShown()
showSnackbar(
message = message,
actionLabel = actionLabel,
duration = duration
)
}

@Preview(showBackground = true, showSystemUi = true)
@Composable
private fun SnackBarContentPreview() {
SnackBarContent(
messageText = "스낵바 메시지",
actionLabelText = "닫기",
)
}
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ interface TimetableAuthApi {
@DELETE("/v2/timetables/lecture/{id}")
suspend fun deleteTimetableLecture(
@Path("id") id: Int
)
): Response<Unit>

@DELETE("/v2/timetables/frame/{frameId}/lecture/{lectureId}")
suspend fun deleteTimetableFrameLecture(
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package `in`.koreatech.koin.data.repository
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import `in`.koreatech.koin.data.request.timetable.LecturesQueryRequest
import `in`.koreatech.koin.data.request.timetable.toCustomLectureQueryRequest
import `in`.koreatech.koin.data.request.timetable.toLectureQueryRequest
import `in`.koreatech.koin.data.request.timetable.toTimetableLecturesQueryRequest
import `in`.koreatech.koin.data.source.datastore.TimetableDataStore
@@ -78,6 +79,13 @@ class TimetableRepositoryImpl @Inject constructor(
)).toTimetableLectures()
}

override suspend fun postTimetableCustomLectures(frameId: Int, lectures: List<Lecture>): Result<TimetableLectures> = runCatching {
timetableRemoteDataSource.postTimetableLectures(LecturesQueryRequest(
timetableFrameId = frameId,
timetableLecture = lectures.map { it.toCustomLectureQueryRequest() }
)).toTimetableLectures()
}


override suspend fun postTimetableFrame(frame: TimetableFrameCreateQuery): TimetableFrame {
TODO("Not yet implemented")
Original file line number Diff line number Diff line change
@@ -22,11 +22,22 @@ data class LectureQueryRequest(
@SerializedName("professor")
val professor: String,
@SerializedName("grades")
val grades: String,
val grades: String?,
@SerializedName("memo")
val memo: String,
val memo: String?,
)

fun Lecture.toCustomLectureQueryRequest() = LectureQueryRequest(
classTitle = name,
classTime = classTime,
classPlace = place ?: "",
professor = professor,
lectureId = null,
grades = null,
memo = null
)


fun Lecture.toLectureQueryRequest() = LectureQueryRequest(
lectureId = id,
classTitle = name,
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ data class TimetableLectureResponse(
@SerializedName("id")
val id: Int,
@SerializedName("lecture_id")
val lectureId: Int,
val lectureId: Int?,
@SerializedName("regular_number") // "38"
val regularNumber: String?,
@SerializedName("code") // "ARB244"
@@ -35,7 +35,7 @@ data class TimetableLectureResponse(
) {
fun toTimetableLecture() = TimetableLecture(
id = id,
lectureId = lectureId,
lectureId = lectureId ?: 0,
regularNumber = regularNumber.orEmpty(),
code = code.orEmpty(),
designScore = designScore.orEmpty(),
Loading

0 comments on commit 179295d

Please sign in to comment.