Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 67 additions & 29 deletions Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.team.prezel.ui

import androidx.compose.animation.ContentTransform
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
Expand All @@ -21,11 +25,15 @@ import com.team.prezel.core.common.event.GlobalEvent
import com.team.prezel.core.common.event.GlobalEventBus
import com.team.prezel.core.designsystem.component.PrezelNavigationScaffold
import com.team.prezel.core.designsystem.component.PrezelNavigationScope
import com.team.prezel.core.designsystem.theme.PrezelTheme
import com.team.prezel.core.navigation.LocalNavigator
import com.team.prezel.core.navigation.Navigator
import com.team.prezel.core.navigation.ProvideSharedTransitionScope
import com.team.prezel.core.navigation.toEntries
import com.team.prezel.core.ui.state.LocalAppDimmerState
import com.team.prezel.core.ui.state.LocalSnackbarHostState
import com.team.prezel.core.ui.state.rememberAppDimmerState
import com.team.prezel.core.ui.util.noRippleClickable
import com.team.prezel.feature.splash.api.SplashNavKey
import com.team.prezel.navigation.MAIN_NAV_ITEMS
import kotlinx.collections.immutable.ImmutableSet
Expand All @@ -38,9 +46,11 @@ fun PrezelApp(
) {
val navigator = remember(appState.navigationState) { Navigator(appState.navigationState) }
val snackbarHostState = remember { SnackbarHostState() }
val appDimmerState = rememberAppDimmerState()

CompositionLocalProvider(
LocalNavigator provides navigator,
LocalAppDimmerState provides appDimmerState,
LocalSnackbarHostState provides snackbarHostState,
) {
DoubleBackToExitHandler(navigationState = appState.navigationState)
Expand All @@ -60,47 +70,75 @@ private fun PrezelAppContent(
entryBuilders: ImmutableSet<EntryProviderScope<NavKey>.() -> Unit>,
) {
val navigator = LocalNavigator.current
val appDimmerState = LocalAppDimmerState.current

ObserveGlobalEvents(
globalEventBus = globalEventBus,
navigateToSplash = { navigator.replaceRoot(SplashNavKey) },
)

SharedTransitionLayout {
ProvideSharedTransitionScope(this@SharedTransitionLayout) {
val provider = remember(entryBuilders, navigator) {
entryProvider {
entryBuilders.forEach { builder -> this.builder() }
}
}

PrezelNavigationScaffold(
showNavigationBar = appState.shouldShowNavigationBar,
snackbarHostState = LocalSnackbarHostState.current,
navigationItems = { AppNavigationItems(appState = appState, navigateToKey = { key -> navigator.navigate(key) }) },
) { padding ->
NavDisplay(
entries = appState.navigationState.toEntries(provider),
onBack = navigator::goBack,
modifier = Modifier.padding(padding),
transitionSpec = {
fadeIn(animationSpec = tween(durationMillis = 100)) togetherWith
fadeOut(animationSpec = tween(durationMillis = 100))
},
popTransitionSpec = {
fadeIn(animationSpec = tween(durationMillis = 100)) togetherWith
fadeOut(animationSpec = tween(durationMillis = 100))
},
predictivePopTransitionSpec = {
fadeIn(animationSpec = tween(durationMillis = 100)) togetherWith
fadeOut(animationSpec = tween(durationMillis = 100))
},
Box(modifier = Modifier.fillMaxSize()) {
SharedTransitionLayout {
ProvideSharedTransitionScope(this@SharedTransitionLayout) {
AppNavigationContent(
appState = appState,
entryBuilders = entryBuilders,
navigator = navigator,
)
}
}

AppDimmerOverlay(isVisible = appDimmerState.isVisible, onDismiss = appDimmerState::dismiss)
}
}

@Composable
private fun AppNavigationContent(
appState: PrezelAppState,
entryBuilders: ImmutableSet<EntryProviderScope<NavKey>.() -> Unit>,
navigator: Navigator,
) {
val provider = remember(entryBuilders, navigator) {
entryProvider {
entryBuilders.forEach { builder -> this.builder() }
}
}

PrezelNavigationScaffold(
showNavigationBar = appState.shouldShowNavigationBar,
snackbarHostState = LocalSnackbarHostState.current,
navigationItems = { AppNavigationItems(appState = appState, navigateToKey = navigator::navigate) },
) { padding ->
NavDisplay(
entries = appState.navigationState.toEntries(provider),
onBack = navigator::goBack,
modifier = Modifier.padding(padding),
transitionSpec = { defaultPrezelNavTransition() },
popTransitionSpec = { defaultPrezelNavTransition() },
predictivePopTransitionSpec = { _: Int -> defaultPrezelNavTransition() },
)
}
}

@Composable
private fun AppDimmerOverlay(
isVisible: Boolean,
onDismiss: () -> Unit,
) {
if (!isVisible) return

Box(
modifier = Modifier
.fillMaxSize()
.background(PrezelTheme.colors.scrimContainer)
.noRippleClickable(onClick = onDismiss),
)
}

private fun defaultPrezelNavTransition(): ContentTransform =
fadeIn(animationSpec = tween(durationMillis = 100)) togetherWith
fadeOut(animationSpec = tween(durationMillis = 100))

@Composable
private fun ObserveGlobalEvents(
globalEventBus: GlobalEventBus,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.team.prezel.core.model.practice.RecordingSpeed
import com.team.prezel.core.model.presentation.Audience
import com.team.prezel.core.model.presentation.Category
import com.team.prezel.core.model.presentation.ExpectedQuestion
import com.team.prezel.core.model.presentation.MainData
import com.team.prezel.core.model.presentation.PracticeRecords
import com.team.prezel.core.model.presentation.PresentationAnalysisSummary
import com.team.prezel.core.model.presentation.PresentationGrowthPoint
import com.team.prezel.core.model.presentation.PresentationInfo
Expand All @@ -13,6 +15,8 @@ import com.team.prezel.core.model.presentation.Purpose
import com.team.prezel.core.model.presentation.ScriptCorrection
import com.team.prezel.core.model.presentation.Style
import com.team.prezel.core.model.presentation.WordAnalysisDetail
import com.team.prezel.core.network.model.presentation.GetMainDataResponse
import com.team.prezel.core.network.model.presentation.GetPracticeRecordsResponse
import com.team.prezel.core.network.model.presentation.GetPresentationsResponse
import com.team.prezel.core.network.model.presentation.PresentationExpectedQuestionResponse
import com.team.prezel.core.network.model.presentation.PresentationGrowthResponse
Expand Down Expand Up @@ -106,3 +110,28 @@ internal fun GetPresentationsResponse.toDomain(): PresentationInfo =
audience = Audience.from(value = audience),
dDay = dday,
)

internal fun GetPracticeRecordsResponse.toDomain(): PracticeRecords =
PracticeRecords(
dates = dates.map(LocalDate::parse),
startDate = LocalDate.parse(startDate),
endDate = LocalDate.parse(endDate),
)

internal fun GetMainDataResponse.toDomain(): MainData =
MainData(
presentationId = presentationId.toLong(),
title = title,
type = type,
presentationDate = LocalDate.parse(presentationDate),
isPast = isPast,
dDay = dDay,
growthGraph = growthGraph?.map { item -> item.toDomain() }.orEmpty(),
)

private fun GetMainDataResponse.GrowthGraph.toDomain(): PresentationGrowthPoint =
PresentationGrowthPoint(
attempt = attempt,
accuracyScore = accuracyScore,
scriptMatchRate = scriptMatchRate,
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ internal class PracticeRepositoryImpl @Inject constructor(
}.mapDomainFailure()

override suspend fun analyzePracticeRecording(
presentationId: Long,
recordingFilePath: String,
referenceText: String,
): Result<PracticeRecordingAnalysisResult> =
runCatching {
practiceRemoteDataSource.analyzePracticeRecording(
presentationId = presentationId,
recordingFilePath = recordingFilePath,
referenceText = referenceText,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import com.team.prezel.core.data.mapper.toDomain
import com.team.prezel.core.domain.repository.presentation.PresentationRepository
import com.team.prezel.core.model.presentation.Audience
import com.team.prezel.core.model.presentation.Category
import com.team.prezel.core.model.presentation.MainData
import com.team.prezel.core.model.presentation.PracticeRecords
import com.team.prezel.core.model.presentation.PresentationAnalysisSummary
import com.team.prezel.core.model.presentation.PresentationInfo
import com.team.prezel.core.model.presentation.PresentationScriptDetail
import com.team.prezel.core.model.presentation.PresentationWordDetail
import com.team.prezel.core.model.presentation.Purpose
import com.team.prezel.core.model.presentation.Style
import com.team.prezel.core.network.datasource.PresentationRemoteDataSource
import com.team.prezel.core.network.model.presentation.GetMainDataResponse
import com.team.prezel.core.network.model.presentation.GetPresentationsResponse
import javax.inject.Inject

Expand Down Expand Up @@ -104,4 +107,18 @@ internal class PresentationRepositoryImpl @Inject constructor(
}.mapCatching { response ->
response.toDomain()
}.mapDomainFailure()

override suspend fun getPracticeRecords(presentationId: Long): Result<PracticeRecords> =
runCatching {
presentationRemoteDataSource.getPracticeRecords(presentationId = presentationId)
}.mapCatching { response ->
response.toDomain()
}.mapDomainFailure()

override suspend fun getMainData(): Result<List<MainData>> =
runCatching {
presentationRemoteDataSource.getMainData()
}.mapCatching { response ->
response.map(GetMainDataResponse::toDomain)
}.mapDomainFailure()
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@ import javax.inject.Inject
internal class UserRepositoryImpl @Inject constructor(
private val userRemoteDataSource: UserRemoteDataSource,
) : UserRepository {
@Volatile
private var cachedNickname: String = ""

override suspend fun fetchUserInfo(): Result<User> =
runCatching {
userRemoteDataSource.getUser()
}.mapCatching { response ->
response.toDomain()
response.toDomain().also { user -> cachedNickname = user.nickname }
}.mapDomainFailure()

override suspend fun getUserNickname(): Result<String> {
if (cachedNickname.isNotBlank()) return Result.success(cachedNickname)

return runCatching { fetchUserInfo().getOrThrow().nickname }
}

override suspend fun patchProfile(
nickname: String,
profileImageFile: File?,
Expand All @@ -28,6 +37,7 @@ internal class UserRepositoryImpl @Inject constructor(
nickname = nickname,
profileImageFile = profileImageFile,
)
cachedNickname = nickname
}.mapDomainFailure()

override suspend fun checkNicknameDuplication(nickname: Nickname): Result<Boolean> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ object PrezelButtonDefaults {
ButtonType.GHOST,
-> Color.Transparent

ButtonType.FILLED -> PrezelTheme.colors.bgLarge
ButtonType.FILLED -> PrezelTheme.colors.bgDisabled
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ fun PrezelFloatingButton(
modifier: Modifier = Modifier,
size: ButtonSize = ButtonSize.REGULAR,
hierarchy: ButtonHierarchy = ButtonHierarchy.PRIMARY,
enabled: Boolean = true,
@DrawableRes openIconResId: Int = PrezelIcons.Cancel,
) {
PrezelIconButton(
iconResId = if (isExpanded) openIconResId else iconResId,
size = size,
hierarchy = hierarchy,
enabled = enabled,
isRounded = true,
modifier = modifier.prezelDropShadow(
style = PrezelDropShadowDefaults.Regular(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ fun PrezelFloatingMenu(
@DrawableRes openIconResId: Int = PrezelIcons.Cancel,
size: ButtonSize = ButtonSize.REGULAR,
hierarchy: ButtonHierarchy = ButtonHierarchy.PRIMARY,
enabled: Boolean = true,
items: @Composable PrezelMenuScope.() -> Unit,
) {
Column(
Expand All @@ -64,6 +65,7 @@ fun PrezelFloatingMenu(
openIconResId = openIconResId,
size = size,
hierarchy = hierarchy,
enabled = enabled,
)
}
}
Expand Down
1 change: 1 addition & 0 deletions Prezel/core/domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ dependencies {
implementation(projects.coreCommon)
implementation(libs.javax.inject)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface PracticeRepository {
suspend fun fetchPracticeScript(): Result<PracticeScript>

suspend fun analyzePracticeRecording(
presentationId: Long,
recordingFilePath: String,
referenceText: String,
): Result<PracticeRecordingAnalysisResult>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.team.prezel.core.domain.repository.presentation

import com.team.prezel.core.model.presentation.Audience
import com.team.prezel.core.model.presentation.Category
import com.team.prezel.core.model.presentation.MainData
import com.team.prezel.core.model.presentation.PracticeRecords
import com.team.prezel.core.model.presentation.PresentationAnalysisSummary
import com.team.prezel.core.model.presentation.PresentationInfo
import com.team.prezel.core.model.presentation.PresentationScriptDetail
Expand Down Expand Up @@ -40,4 +42,8 @@ interface PresentationRepository {
suspend fun getUpcomingPresentationDetail(presentationId: Long): Result<PresentationAnalysisSummary>

suspend fun getPastPresentationDetail(presentationId: Long): Result<PresentationAnalysisSummary>

suspend fun getPracticeRecords(presentationId: Long): Result<PracticeRecords>

suspend fun getMainData(): Result<List<MainData>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import java.io.File
interface UserRepository {
suspend fun fetchUserInfo(): Result<User>

suspend fun getUserNickname(): Result<String>

suspend fun patchProfile(
nickname: String,
profileImageFile: File?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ class AnalyzePracticeRecordingUseCase @Inject constructor(
private val practiceRepository: PracticeRepository,
) {
suspend operator fun invoke(
presentationId: Long,
recordingFilePath: String,
referenceText: String,
): Result<PracticeRecordingAnalysisResult> =
practiceRepository.analyzePracticeRecording(
presentationId = presentationId,
recordingFilePath = recordingFilePath,
referenceText = referenceText,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ class AnalyzePresentationUseCase @Inject constructor(
scriptFilePath: String?,
audioFilePath: String,
): Result<Long> =
presentationRepository.analyzePresentation(
name = name,
date = date,
category = category,
purpose = purpose,
style = style,
audience = audience,
script = script,
scriptFilePath = scriptFilePath,
audioFilePath = audioFilePath,
)
presentationRepository
.analyzePresentation(
name = name,
date = date,
category = category,
purpose = purpose,
style = style,
audience = audience,
script = script,
scriptFilePath = scriptFilePath,
audioFilePath = audioFilePath,
)
}
Loading