Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5d6da25
feat: 발표 분석(Analysis) 기능 초기 구현 및 홈/로그인 흐름 연동
HamBeomJoon May 3, 2026
f446bc4
feat: 분석(Analysis) 흐름 내 대본 추가 기능 개선 및 디자인 시스템 보정
HamBeomJoon May 5, 2026
ac9e92b
Merge remote-tracking branch 'origin/develop' into feat/#115-analytic…
HamBeomJoon May 10, 2026
d8b4fa2
Merge remote-tracking branch 'origin/develop' into feat/#115-analytic…
HamBeomJoon May 14, 2026
35e5218
feat: 분석(Analysis) 플로우 개선 및 파일 업로드 실패 화면 구현
HamBeomJoon May 14, 2026
d867e8c
Merge remote-tracking branch 'origin/develop' into feat/#115-analytic…
HamBeomJoon May 19, 2026
63d9684
Merge remote-tracking branch 'origin/develop' into feat/#115-analytic…
HamBeomJoon May 21, 2026
7298c6c
feat: 분석 대기 및 리포트 화면 구현과 오디오 재생 로직 추가
HamBeomJoon May 21, 2026
a325dff
fix: 스플래시 화면 로그인 상태 확인 로직 복구
HamBeomJoon May 23, 2026
5ce18c7
feat: 분석 기능 화면 리팩터링 및 UI 컴포넌트 구조 개선
HamBeomJoon May 23, 2026
5b63a7c
feat: 발표 녹음 분석 기능 구현 및 API 연동
HamBeomJoon May 23, 2026
c6730a4
feat: 발표 분석 기능의 오류 처리 로직 강화 및 UI 피드백 추가
HamBeomJoon May 23, 2026
1fd8c75
feat: 분석용 파일 캐시 관리 로직 추출 및 추상화
HamBeomJoon May 23, 2026
b5d8602
fix: 스크립트 파일 인식 오류 처리 및 파일 캐시 로직 개선
HamBeomJoon May 23, 2026
3e9feb9
refactor: 파일 업로드 방식 개선 및 분석 플로우 상태 전이 로직 수정
HamBeomJoon May 23, 2026
b8fc011
fix: 분석 단계 유효성 검증 개선 및 에러 매핑 테스트 추가
HamBeomJoon May 24, 2026
cceee72
fix: 분석 단계 유효성 검증 개선 및 에러 매핑 테스트 추가
HamBeomJoon May 24, 2026
396c225
refactor: 발표 분석 요청 파라미터 타입 변경 및 관련 로직 개선
HamBeomJoon May 24, 2026
aa70294
refactor: 리소스 ID 명명 규칙 통일 및 분석 로직 개선
HamBeomJoon May 24, 2026
89ef4c4
refactor: 발표 상황 선택 옵션 아이템 레이아웃 개선
HamBeomJoon May 24, 2026
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
1 change: 1 addition & 0 deletions Prezel/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ dependencies {

implementation(projects.featureTermsImpl)
implementation(projects.featurePracticeImpl)
implementation(projects.featureAnalysisImpl)
implementation(projects.featureSettingImpl)
implementation(projects.featureProfileImpl)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enum class AppError {
NOT_FOUND,
DUPLICATE,
VOICE_RECOGNITION_FAILED,
SCRIPT_FILE_RECOGNITION_FAILED,
NETWORK,
UNKNOWN,
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ private fun ServerErrorCode.toDomainError(): AppError =
when (this) {
ServerErrorCode.INVALID_REQUEST,
ServerErrorCode.REQUIRED_TERMS_DISAGREED,
ServerErrorCode.FILE_IS_EMPTY,
ServerErrorCode.FILE_UPLOAD_FAILED,
-> AppError.INVALID_REQUEST

ServerErrorCode.FILE_IS_EMPTY -> AppError.SCRIPT_FILE_RECOGNITION_FAILED

ServerErrorCode.SERVER_ERROR,
ServerErrorCode.FILE_UPLOAD_FAILED,
ServerErrorCode.SENTENCE_NOT_FOUND,
ServerErrorCode.VOICE_ANALYSIS_FAILED,
-> AppError.SERVER_ERROR

ServerErrorCode.TERMS_NOT_FOUND,
ServerErrorCode.SENTENCE_NOT_FOUND,
-> AppError.NOT_FOUND
ServerErrorCode.TERMS_NOT_FOUND -> AppError.NOT_FOUND

ServerErrorCode.DUPLICATE_NICKNAME -> AppError.DUPLICATE

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.team.prezel.core.data.mapper

import com.team.prezel.core.model.practice.PracticeRecordingAnalysisResult
import com.team.prezel.core.model.practice.PracticeRecordingOverallEvaluation
import com.team.prezel.core.model.practice.PracticeRecordingSpeed
import com.team.prezel.core.model.practice.PracticeScript
import com.team.prezel.core.model.presentation.Audience
import com.team.prezel.core.model.presentation.Category
import com.team.prezel.core.model.presentation.PresentationExpectedQuestion
import com.team.prezel.core.model.presentation.PresentationGrowthGraph
import com.team.prezel.core.model.presentation.PresentationRecordingAnalysisResult
import com.team.prezel.core.model.presentation.Purpose
import com.team.prezel.core.model.presentation.Style
import com.team.prezel.core.network.model.practice.AnalyzePracticeRecordingResponse
import com.team.prezel.core.network.model.practice.PracticeSentenceResponse
import com.team.prezel.core.network.model.practice.PresentationRecordingAnalysisResponse
import kotlin.math.roundToInt

internal fun PracticeSentenceResponse.toDomain(): PracticeScript =
PracticeScript(
id = PRACTICE_SCRIPT_ID,
content = sentence,
)

internal fun AnalyzePracticeRecordingResponse.toDomain(): PracticeRecordingAnalysisResult =
PracticeRecordingAnalysisResult(
pronunciationScore = accuracyScore.roundToInt(),
speed = speedEvaluation.toPracticeRecordingSpeed(),
overallEvaluation = overallEvaluation.toPracticeRecordingOverallEvaluation(),
)

internal fun PresentationRecordingAnalysisResponse.toDomain(): PresentationRecordingAnalysisResult =
PresentationRecordingAnalysisResult(
presentationId = presentationId,
analysisResultId = analysisResultId,
name = name,
type = type,
purpose = purpose,
style = style,
audience = audience,
analysisDate = analysisDate,
durationSeconds = durationSeconds,
formattedDuration = formattedDuration,
spm = spm,
speedEval = speedEval,
summaryFeedback = summaryFeedback,
accuracyScore = accuracyScore,
scriptMatchRate = scriptMatchRate,
spellErrorCount = spellErrorCount,
grammarErrorCount = grammarErrorCount,
totalErrorCount = totalErrorCount,
growthGraph = growthGraph.map { it.toDomain() },
expectedQuestions = expectedQuestions.map { it.toDomain() },
)

internal fun Category.toRequestType(): String = name

internal fun Purpose.toRequestPurpose(): String = name

internal fun Style.toRequestStyle(): String = name

internal fun Audience.toRequestAudience(): String = name

internal fun PresentationRecordingAnalysisResponse.GrowthGraph.toDomain(): PresentationGrowthGraph =
PresentationGrowthGraph(
attempt = attempt,
accuracyScore = accuracyScore,
scriptMatchRate = scriptMatchRate,
)

internal fun PresentationRecordingAnalysisResponse.ExpectedQuestion.toDomain(): PresentationExpectedQuestion =
PresentationExpectedQuestion(
question = question,
answer = answer,
)

internal fun String.toPracticeRecordingSpeed(): PracticeRecordingSpeed =
when {
contains("느려요") -> PracticeRecordingSpeed.SLOW
contains("빨라요") -> PracticeRecordingSpeed.FAST
else -> PracticeRecordingSpeed.ADEQUATE
}

internal fun String.toPracticeRecordingOverallEvaluation(): PracticeRecordingOverallEvaluation =
when (this) {
"Perfect" -> PracticeRecordingOverallEvaluation.PERFECT
"Good" -> PracticeRecordingOverallEvaluation.GOOD
"Try" -> PracticeRecordingOverallEvaluation.TRY
else -> PracticeRecordingOverallEvaluation.TRY
}

private const val PRACTICE_SCRIPT_ID = 0L
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package com.team.prezel.core.data.repository

import com.team.prezel.core.data.error.mapDomainFailure
import com.team.prezel.core.data.mapper.toDomain
import com.team.prezel.core.data.mapper.toRequestAudience
import com.team.prezel.core.data.mapper.toRequestPurpose
import com.team.prezel.core.data.mapper.toRequestStyle
import com.team.prezel.core.data.mapper.toRequestType
import com.team.prezel.core.domain.repository.practice.PracticeRepository
import com.team.prezel.core.model.practice.PracticeRecordingAnalysisResult
import com.team.prezel.core.model.practice.PracticeRecordingOverallEvaluation
import com.team.prezel.core.model.practice.PracticeRecordingSpeed
import com.team.prezel.core.model.practice.PracticeScript
import com.team.prezel.core.model.presentation.Audience
import com.team.prezel.core.model.presentation.Category
import com.team.prezel.core.model.presentation.PresentationRecordingAnalysisResult
import com.team.prezel.core.model.presentation.Purpose
import com.team.prezel.core.model.presentation.Style
import com.team.prezel.core.network.datasource.PracticeRemoteDataSource
import com.team.prezel.core.network.model.practice.AnalyzePracticeRecordingResponse
import com.team.prezel.core.network.model.practice.PracticeSentenceResponse
import javax.inject.Inject
import kotlin.math.roundToInt

internal class PracticeRepositoryImpl @Inject constructor(
private val practiceRemoteDataSource: PracticeRemoteDataSource,
Expand All @@ -22,6 +27,33 @@ internal class PracticeRepositoryImpl @Inject constructor(
response.toDomain()
}.mapDomainFailure()

override suspend fun analyzePresentationRecording(
name: String,
date: String,
category: Category,
purpose: Purpose,
style: Style,
audience: Audience,
script: String?,
scriptFilePath: String?,
audioFilePath: String,
): Result<PresentationRecordingAnalysisResult> =
runCatching {
practiceRemoteDataSource.analyzePresentationRecording(
name = name,
date = date,
type = category.toRequestType(),
purpose = purpose.toRequestPurpose(),
style = style.toRequestStyle(),
audience = audience.toRequestAudience(),
script = script,
scriptFilePath = scriptFilePath,
audioFilePath = audioFilePath,
)
}.mapCatching { response ->
response.toDomain()
}.mapDomainFailure()

override suspend fun analyzePracticeRecording(
recordingFilePath: String,
referenceText: String,
Expand All @@ -34,36 +66,4 @@ internal class PracticeRepositoryImpl @Inject constructor(
}.mapCatching { response ->
response.toDomain()
}.mapDomainFailure()

private fun PracticeSentenceResponse.toDomain(): PracticeScript =
PracticeScript(
id = PRACTICE_SCRIPT_ID,
content = sentence,
)

private fun AnalyzePracticeRecordingResponse.toDomain(): PracticeRecordingAnalysisResult =
PracticeRecordingAnalysisResult(
pronunciationScore = accuracyScore.roundToInt(),
speed = speedEvaluation.toPracticeRecordingSpeed(),
overallEvaluation = overallEvaluation.toPracticeRecordingOverallEvaluation(),
)

private fun String.toPracticeRecordingSpeed(): PracticeRecordingSpeed =
when {
contains("느려요") -> PracticeRecordingSpeed.SLOW
contains("빨라요") -> PracticeRecordingSpeed.FAST
else -> PracticeRecordingSpeed.ADEQUATE
}

private fun String.toPracticeRecordingOverallEvaluation(): PracticeRecordingOverallEvaluation =
when (this) {
"Perfect" -> PracticeRecordingOverallEvaluation.PERFECT
"Good" -> PracticeRecordingOverallEvaluation.GOOD
"Try" -> PracticeRecordingOverallEvaluation.TRY
else -> PracticeRecordingOverallEvaluation.TRY
}

private companion object {
const val PRACTICE_SCRIPT_ID = 0L
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ fun PrezelAccordion(
)
}

if (showDivider) {
if (showDivider && !expanded) {
PrezelHorizontalDivider(
type = PrezelDividerType.THICK,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal fun DayCell(
.padding(PrezelTheme.spacing.V4)
.clip(PrezelTheme.shapes.V1000)
.background(color = config.dayContainerColor(dayCell = dayCell)),
isUseRipple = false,
onClick = onClick,
) {
if (dayCell == null) return@PrezelTouchArea
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.team.prezel.core.designsystem.foundation.typography.PrezelTextStyles
import com.team.prezel.core.designsystem.theme.PrezelTheme
import com.team.prezel.core.designsystem.util.NoRippleInteractionSource
import kotlinx.collections.immutable.ImmutableList

@Composable
Expand Down Expand Up @@ -76,5 +77,6 @@ private fun PrezelTab(
},
selectedContentColor = PrezelTheme.colors.solidBlack,
unselectedContentColor = PrezelTheme.colors.textDisabled,
interactionSource = NoRippleInteractionSource,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ package com.team.prezel.core.domain.repository.practice

import com.team.prezel.core.model.practice.PracticeRecordingAnalysisResult
import com.team.prezel.core.model.practice.PracticeScript
import com.team.prezel.core.model.presentation.Audience
import com.team.prezel.core.model.presentation.Category
import com.team.prezel.core.model.presentation.PresentationRecordingAnalysisResult
import com.team.prezel.core.model.presentation.Purpose
import com.team.prezel.core.model.presentation.Style

interface PracticeRepository {
suspend fun fetchPracticeScript(): Result<PracticeScript>
Expand All @@ -10,4 +15,16 @@ interface PracticeRepository {
recordingFilePath: String,
referenceText: String,
): Result<PracticeRecordingAnalysisResult>

suspend fun analyzePresentationRecording(
name: String,
date: String,
category: Category,
purpose: Purpose,
style: Style,
audience: Audience,
script: String?,
scriptFilePath: String?,
audioFilePath: String,
): Result<PresentationRecordingAnalysisResult>
}
Loading