Skip to content

Commit

Permalink
Merge pull request #78 from uswLectureEvaluation/feature/#75-open-major
Browse files Browse the repository at this point in the history
Feature/#75 open major
  • Loading branch information
jinukeu authored Dec 31, 2023
2 parents 166ed4a + 24789d5 commit df0ae34
Show file tree
Hide file tree
Showing 36 changed files with 1,012 additions and 125 deletions.
2 changes: 2 additions & 0 deletions core/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ dependencies {

implementation(platform(libs.firebase.bom))
implementation(libs.firebase.crashlytics)

implementation(libs.timber)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package com.suwiki.core.android

import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import timber.log.Timber

fun recordException(
e: Throwable,
) {
Timber.e(e)
Firebase.crashlytics.recordException(e)
}
1 change: 1 addition & 0 deletions core/designsystem/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ dependencies {
implementation(projects.core.model)
implementation(projects.core.ui)

implementation(libs.kotlinx.immutable)
implementation(libs.compose.rating.bar)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package com.suwiki.core.designsystem.component.tabbar

import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.suwiki.core.designsystem.theme.Black
import com.suwiki.core.designsystem.theme.Gray95
import com.suwiki.core.designsystem.theme.SuwikiTheme
import com.suwiki.core.designsystem.theme.White
import com.suwiki.core.ui.extension.suwikiClickable

enum class SubComposeID {
PRE_CALCULATE_ITEM,
ITEM,
INDICATOR,
}

data class TabPosition(
val left: Dp,
val width: Dp,
)

// https://github.com/GautierLouis/ComposePlayground/blob/1c5ffb483291bb21d9820a2fe7a18aec049c7bd2/app/src/main/java/com/louis/composeplayground/ui/TabRow.kt
@Composable
fun SuwikiTabBar(
indicatorColor: Color = Black,
paddingValues: PaddingValues = PaddingValues(start = 12.dp, end = 12.dp),
animationSpec: AnimationSpec<Dp> = tween(durationMillis = 250, easing = FastOutSlowInEasing),
selectedTabPosition: Int = 0,
tabItem: @Composable () -> Unit,
) {
Surface(
color = White,
) {
SubcomposeLayout(
Modifier
.padding(paddingValues)
.selectableGroup(),
) { constraints ->
val tabMeasurable: List<Placeable> = subcompose(SubComposeID.PRE_CALCULATE_ITEM, tabItem)
.map { it.measure(constraints) }

val maxItemHeight = tabMeasurable.maxOf { it.height }

val tabPlacables = subcompose(SubComposeID.ITEM, tabItem).map {
it.measure(constraints)
}

val tabPositions = tabPlacables.mapIndexed { index, placeable ->
val itemWidth = placeable.width
val x = tabPlacables.take(index).sumOf { it.width }
TabPosition(x.toDp(), itemWidth.toDp())
}

val tabRowWidth = tabPlacables.sumOf { it.width }

layout(tabRowWidth, maxItemHeight) {
subcompose(SubComposeID.INDICATOR) {
Box(
Modifier
.tabIndicator(tabPositions[selectedTabPosition], animationSpec)
.fillMaxWidth()
.height(maxItemHeight.toDp())
.background(color = indicatorColor),
)
}.forEach {
it.measure(Constraints.fixed(tabRowWidth, maxItemHeight)).placeRelative(0, 0)
}

tabPlacables.forEachIndexed { index, placeable ->
val x = tabPlacables.take(index).sumOf { it.width }
placeable.placeRelative(x, 0)
}
}
}
}
}

private fun Modifier.tabIndicator(
tabPosition: TabPosition,
animationSpec: AnimationSpec<Dp>,
): Modifier = composed(
inspectorInfo = debugInspectorInfo {
name = "tabIndicatorOffset"
value = tabPosition
},
) {
val currentTabWidth by animateDpAsState(
targetValue = tabPosition.width,
animationSpec = animationSpec,
label = "",
)
val indicatorOffset by animateDpAsState(
targetValue = tabPosition.left,
animationSpec = animationSpec,
label = "",
)
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.offset(x = indicatorOffset + 12.dp)
.width(currentTabWidth - 24.dp)
.height(2.dp)
}

@Composable
fun TabTitle(
title: String,
position: Int,
selected: Boolean,
onClick: (Int) -> Unit,
) {
Text(
text = title,
style = SuwikiTheme.typography.header6,
modifier = Modifier
.wrapContentWidth(Alignment.CenterHorizontally)
.padding(12.dp)
.suwikiClickable(
rippleEnabled = false,
onClick = { onClick(position) },
),
color = if (selected) Black else Gray95,
)
}

@Preview()
@Composable
fun SuwikiTabBarPreview() {
SuwikiTheme {
var selectedTabPosition by remember { mutableIntStateOf(0) }

val items = listOf(
"메뉴(0)",
"메뉴(0)",
)

SuwikiTabBar(
selectedTabPosition = selectedTabPosition,
) {
items.forEachIndexed { index, s ->
TabTitle(title = s, position = index, selected = index == selectedTabPosition) { selectedTabPosition = index }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.suwiki.core.model.notice

import androidx.compose.runtime.Stable
import java.time.LocalDateTime

@Stable
data class NoticeDetail(
val id: Long = 0,
val title: String = "",
Expand Down
26 changes: 26 additions & 0 deletions core/ui/src/main/java/com/suwiki/core/ui/extension/Flow.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.suwiki.core.ui.extension

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch

@Composable
inline fun <reified T> Flow<T>.collectWithLifecycle(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
noinline action: suspend (T) -> Unit,
) {
LaunchedEffect(key1 = Unit) {
lifecycleOwner.lifecycleScope.launch {
flowWithLifecycle(lifecycleOwner.lifecycle, minActiveState).collect {
action(it)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.suwiki.core.ui.extension

import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow

// https://manavtamboli.medium.com/infinite-list-paged-list-in-jetpack-compose-b10fc7e74768
@Composable
fun LazyListState.OnBottomReached(
// tells how many items before we reach the bottom of the list
// to call onLoadMore function
buffer: Int = 0,
onLoadMore: () -> Unit,
) {
// Buffer must be positive.
// Or our list will never reach the bottom.
require(buffer >= 0) { "buffer cannot be negative, but was $buffer" }

val shouldLoadMore = remember {
derivedStateOf {
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
?: return@derivedStateOf true

// subtract buffer from the total items
lastVisibleItem.index >= layoutInfo.totalItemsCount - 1 - buffer
}
}

LaunchedEffect(shouldLoadMore) {
snapshotFlow { shouldLoadMore.value }
.collect { if (it) onLoadMore() }
}
}

fun LazyListState.isScrolledToEnd() = layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.suwiki.data.user.repository

import com.suwiki.core.model.exception.AuthorizationException
import com.suwiki.core.model.user.User
import com.suwiki.core.security.SecurityPreferences
import com.suwiki.data.user.datasource.LocalUserDataSource
Expand All @@ -8,7 +9,6 @@ import com.suwiki.domain.user.repository.UserRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.zip
import javax.inject.Inject

class UserRepositoryImpl @Inject constructor(
Expand Down Expand Up @@ -39,20 +39,16 @@ class UserRepositoryImpl @Inject constructor(
val localUserInfo = localUserDataSource.user.first()
emit(localUserInfo)

val remoteUserInfo = remoteUserDataSource.getUserInfo()

val isTokenExpired = with(securityPreferences) {
val (accessToken, refreshToken) = flowAccessToken().zip(flowRefreshToken()) { accessToken, refreshToken ->
(accessToken to refreshToken)
}.first()

accessToken.isEmpty() && refreshToken.isEmpty()
}

if (isTokenExpired) {
logout()
emit(User())
return@flow
val remoteUserInfo = runCatching {
remoteUserDataSource.getUserInfo()
}.getOrElse { exception ->
if (exception is AuthorizationException) {
logout()
emit(User())
return@flow
} else {
throw exception
}
}

emit(remoteUserInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.suwiki.domain.openmajor.usecase

import com.suwiki.domain.openmajor.repository.OpenMajorRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class GetOpenMajorListUseCase @Inject constructor(
Expand All @@ -16,6 +17,6 @@ class GetOpenMajorListUseCase @Inject constructor(
* 그 이후 LocalVersion과 LocalOpenMajorList를 최신화 합니다.
*/
suspend operator fun invoke(): Flow<List<String>> {
return openMajorRepository.getOpenMajorList()
return openMajorRepository.getOpenMajorList().map { listOf("전체") + it } // TODO v2 api 리팩토링
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.suwiki.feature.lectureevaluation.viewerreporter

data class LectureEvaluationState(
val showOnboardingBottomSheet: Boolean = false,
val selectedOpenMajor: String = "",
)

sealed interface LectureEvaluationSideEffect {
Expand Down
Loading

0 comments on commit df0ae34

Please sign in to comment.