Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a7d5f3a
feat: 발표 분석 요청 유즈케이스 구현
moondev03 May 21, 2026
0d50aa0
feat: 분석 리포트 화면 구현
moondev03 May 21, 2026
a65a357
chore: 발표 분석 관련 코드 포맷 정리
moondev03 May 21, 2026
2ed1d75
refactor: Presentation 데이터 매핑 로직 분리 및 Repository 리팩터링
moondev03 May 21, 2026
cf2b144
refactor: PrezelList 컴포넌트 내 타이틀 텍스트의 줄 제한 제거
moondev03 May 25, 2026
e0e4915
refactor: Navigator 내비게이션 로직 개선 및 하위 스택 초기화 기능 추가
moondev03 May 25, 2026
6340ca5
refactor: 네트워크 타임아웃 설정 추가 및 로깅 로직 개선
moondev03 May 25, 2026
6409fc0
refactor: 발표 및 연습 분석 도메인 모델 개선 및 Enum 구조 표준화
moondev03 May 25, 2026
fceb39d
refactor: 발표 분석 로직 구조 개선 및 데이터 레이어 통합
moondev03 May 25, 2026
82f4456
refactor: ReportAnalysisPayload 구조 단순화 및 매핑 로직 개선
moondev03 May 25, 2026
7348918
refactor: 분석 완료 후 리포트 화면 연동 및 데이터 모델 구조 개선
moondev03 May 25, 2026
975f6bd
refactor: 분석 결과 리포트 진입 로직 개선 및 ID 기반 데이터 조회 방식으로 변경
moondev03 May 25, 2026
2fa339c
feat: 발표 목록 조회 기능 추가 및 관련 모델 리팩터링
moondev03 May 25, 2026
7385ece
refactor: 리포트 기능 통합 및 히스토리 탐색 로직 개선
moondev03 May 25, 2026
4e55be6
feat: 분석 리포트 삭제 및 재녹음·재작성 다이얼로그 구현
moondev03 May 25, 2026
6941add
feat: 히스토리 화면에서 분석하기 이동 추가 및 네비게이션 로직 개선
moondev03 May 25, 2026
6e2866b
refactor: 발표 분석 요약 정보 필드 Null 허용 처리
moondev03 May 25, 2026
b748a8c
refactor: 분석 리포트 내 스크립트 작성 및 재녹음 기능 연동 및 UI 구조 개선
moondev03 May 25, 2026
8eb2e3f
feat: 분석 리포트 조회 및 삭제 예외 처리 및 알림 기능 구현
moondev03 May 25, 2026
026f7df
fix: 분석 리포트 화면 스낵바 노출 위치 조정
moondev03 May 25, 2026
e2d0975
refactor: 리포트 화면 UI 스타일 및 컴포넌트 로직 개선
HamBeomJoon May 26, 2026
761b616
refactor: HistoryUiModel 속성명 변경 및 D-Day 로직 이동
HamBeomJoon May 26, 2026
c1a15b0
Merge branch 'develop' into feat/#134-분석-리포트-화면-구현
moondev03 May 26, 2026
947ad1b
fix: 에러 변환 로직 내 `CancellationException` 처리 추가
moondev03 May 26, 2026
4a8db7b
refactor: 모델 클래스의 문자열 비교 로직 개선
moondev03 May 26, 2026
bf6fe2d
refactor: 네트워크 로깅 보안 강화 및 코드 포맷팅 수정
moondev03 May 26, 2026
919e9c3
refactor: 파일 업로드 로직 안정성 강화 및 명칭 개선
moondev03 May 26, 2026
3990740
fix: PresentationService 내 API 엔드포인트 경로 수정
moondev03 May 26, 2026
fe1a49e
feat: 분석 레포트 다이얼로그 확인 시 네비게이션 로직 구현
moondev03 May 26, 2026
a266937
feat: 분석 리포트 셀프 피드백 작성 기능 연동 및 네비게이션 추가
moondev03 May 26, 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 @@ -54,6 +54,7 @@ dependencies {
implementation(projects.featureAnalysisImpl)
implementation(projects.featureSettingImpl)
implementation(projects.featureProfileImpl)
implementation(projects.featureReportImpl)

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
Expand Down
2 changes: 1 addition & 1 deletion Prezel/core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ plugins {

android {
namespace = "com.team.prezel.core.data"
testOptions.unitTests.isIncludeAndroidResources = true
}

dependencies {
Expand All @@ -16,4 +15,5 @@ dependencies {
implementation(projects.coreNetwork)

implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package com.team.prezel.core.data.di

import com.team.prezel.core.data.repository.AuthRepositoryImpl
import com.team.prezel.core.data.repository.PracticeRepositoryImpl
import com.team.prezel.core.data.repository.PresentationRepositoryImpl
import com.team.prezel.core.data.repository.TermsRepositoryImpl
import com.team.prezel.core.data.repository.UserRepositoryImpl
import com.team.prezel.core.domain.repository.auth.AuthRepository
import com.team.prezel.core.domain.repository.practice.PracticeRepository
import com.team.prezel.core.domain.repository.presentation.PresentationRepository
import com.team.prezel.core.domain.repository.profile.UserRepository
import com.team.prezel.core.domain.repository.terms.TermsRepository
import dagger.Binds
Expand All @@ -29,6 +31,10 @@ internal abstract class RepositoryModule {
@Singleton
abstract fun bindPracticeRepository(impl: PracticeRepositoryImpl): PracticeRepository

@Binds
@Singleton
abstract fun bindPresentationRepository(impl: PresentationRepositoryImpl): PresentationRepository

@Binds
@Singleton
abstract fun bindTermsRepository(impl: TermsRepositoryImpl): TermsRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.team.prezel.core.common.error.AppError
import com.team.prezel.core.common.error.AppException
import com.team.prezel.core.network.model.ApiException
import com.team.prezel.core.network.model.ServerErrorCode
import kotlinx.coroutines.CancellationException
import java.io.IOException

internal fun <T> Result<T>.mapDomainFailure(): Result<T> =
Expand All @@ -14,6 +15,8 @@ internal fun <T> Result<T>.mapDomainFailure(): Result<T> =

private fun Throwable.toDomainThrowable(): Throwable =
when (this) {
is CancellationException -> this

is ApiException ->
AppException(
error = errorCode.toDomainError(),
Expand Down Expand Up @@ -50,7 +53,10 @@ private fun ServerErrorCode.toDomainError(): AppError =
ServerErrorCode.VOICE_ANALYSIS_FAILED,
-> AppError.SERVER_ERROR

ServerErrorCode.TERMS_NOT_FOUND -> AppError.NOT_FOUND
ServerErrorCode.TERMS_NOT_FOUND,
ServerErrorCode.PRESENTATION_NOT_FOUND,
ServerErrorCode.ANALYSIS_RESULT_NOT_FOUND,
-> AppError.NOT_FOUND

ServerErrorCode.DUPLICATE_NICKNAME -> AppError.DUPLICATE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,10 @@ 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.model.practice.RecordingSpeed
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 =
Expand All @@ -25,68 +17,8 @@ internal fun PracticeSentenceResponse.toDomain(): PracticeScript =
internal fun AnalyzePracticeRecordingResponse.toDomain(): PracticeRecordingAnalysisResult =
PracticeRecordingAnalysisResult(
pronunciationScore = accuracyScore.roundToInt(),
speed = speedEvaluation.toPracticeRecordingSpeed(),
overallEvaluation = overallEvaluation.toPracticeRecordingOverallEvaluation(),
speed = RecordingSpeed.from(value = speedEvaluation),
overallEvaluation = PracticeRecordingOverallEvaluation.from(overallEvaluation),
)

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
@@ -0,0 +1,108 @@
package com.team.prezel.core.data.mapper

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.PresentationAnalysisSummary
import com.team.prezel.core.model.presentation.PresentationGrowthPoint
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.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.GetPresentationsResponse
import com.team.prezel.core.network.model.presentation.PresentationExpectedQuestionResponse
import com.team.prezel.core.network.model.presentation.PresentationGrowthResponse
import com.team.prezel.core.network.model.presentation.PresentationScriptAnalysisResponse
import com.team.prezel.core.network.model.presentation.PresentationScriptDetailResponse
import com.team.prezel.core.network.model.presentation.PresentationSummaryResponse
import com.team.prezel.core.network.model.presentation.PresentationWordAnalysisResponse
import com.team.prezel.core.network.model.presentation.PresentationWordDetailResponse
import kotlinx.datetime.LocalDate

internal fun PresentationSummaryResponse.toDomain(): PresentationAnalysisSummary =
PresentationAnalysisSummary(
presentationId = presentationId,
analysisResultId = analysisResultId,
title = name,
category = Category.from(value = type),
purpose = Purpose.from(value = purpose),
style = Style.from(value = style),
audience = Audience.from(value = audience),
analyzedAt = analysisDate,
durationSeconds = durationSeconds,
formattedDuration = formattedDuration,
spm = spm,
speedEvaluation = RecordingSpeed.from(value = speedEval),
summaryFeedback = summaryFeedback,
accuracyScore = accuracyScore,
scriptMatchRate = scriptMatchRate,
spellErrorCount = spellErrorCount,
grammarErrorCount = grammarErrorCount,
totalErrorCount = totalErrorCount,
growth = growthGraph.map { item -> item.toDomain() },
expectedQuestions = expectedQuestions.map { item -> item.toDomain() },
selfFeedback = reviewContent,
)

private fun PresentationGrowthResponse.toDomain(): PresentationGrowthPoint =
PresentationGrowthPoint(
attempt = attempt,
accuracyScore = accuracyScore,
scriptMatchRate = scriptMatchRate,
)

private fun PresentationExpectedQuestionResponse.toDomain(): ExpectedQuestion =
ExpectedQuestion(
question = question,
answer = answer,
)

internal fun PresentationScriptDetailResponse.toDomain(): PresentationScriptDetail =
PresentationScriptDetail(
presentationId = presentationId,
audioUrl = audioUrl,
originalScript = originalScript,
scriptCorrections = scriptDetails.map { item -> item.toDomain() },
)

internal fun PresentationScriptAnalysisResponse.toDomain(): ScriptCorrection =
ScriptCorrection(
errorType = errorType,
sentence = sentence,
originalText = originalText,
correctedText = correctedText,
reason = reason,
)

internal fun PresentationWordDetailResponse.toDomain(): PresentationWordDetail =
PresentationWordDetail(
presentationId = presentationId,
audioUrl = audioUrl,
wordDetails = wordDetails.map { item -> item.toDomain() },
)

internal fun PresentationWordAnalysisResponse.toDomain(): WordAnalysisDetail =
WordAnalysisDetail(
word = word,
status = status,
description = description,
accuracy = accuracy,
startTimeMs = startTimeMs,
endTimeMs = endTimeMs,
)

internal fun GetPresentationsResponse.toDomain(): PresentationInfo =
PresentationInfo(
id = presentationId,
title = title,
presentationDate = LocalDate.parse(presentationDate),
category = Category.from(value = type),
purpose = Purpose.from(value = purpose),
style = Style.from(value = style),
audience = Audience.from(value = audience),
dDay = dday,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,9 @@ 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.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 javax.inject.Inject

Expand All @@ -27,33 +18,6 @@ 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 Down
Loading