Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0cab93d
feat: PrezelAsyncImage 내 SVG 지원 추가
moondev03 Jul 5, 2026
b85ea1f
feat: 프로필 이미지 삭제 기능 추가 및 상태 업데이트 로직 개선
moondev03 Jul 5, 2026
56b236d
feat: 프로필 수정 성공 안내 스낵바 추가 및 관련 로직 개선
moondev03 Jul 5, 2026
75f7d80
refactor: ProfileUiState 내 프로필 수정 버튼 활성화 로직 개선
moondev03 Jul 5, 2026
6dbb052
refactor: 마이페이지 UI 구조 개선 및 뱃지 목록 그리드 전환
moondev03 Jul 6, 2026
03ba9f6
refactor: PrezelTextField 키보드 숨김 감지 및 포커스 해제 로직 추가
moondev03 Jul 6, 2026
24aa9b4
feat: 마이페이지 배지 클릭 이벤트 및 이동 로직 추가
moondev03 Jul 6, 2026
26b9146
refactor: 회원 탈퇴 화면 약관 동의 UI 구조 및 위치 개선
moondev03 Jul 6, 2026
9f6de65
feat: 프로필 화면 진입 시 캐시된 닉네임 우선 표시 로직 추가
moondev03 Jul 6, 2026
03763a8
refactor: 약관 관리 시스템 동적화 및 뱃지 정렬 기능 추가
moondev03 Jul 8, 2026
539122a
style: SpeedGraph 기본 하한값 상수 조정
moondev03 Jul 8, 2026
8f5f9c6
refactor: StickGraph UI 디자인 및 높이 계산 로직 개선
moondev03 Jul 8, 2026
89cf069
feat: 큐레이션 API 연동 및 홈 화면 구조/FAB 로직 개선
moondev03 Jul 10, 2026
71156db
fix: ScriptAnalysisSection 내 컴포넌트 클릭 활성화 상태 명시
moondev03 Jul 10, 2026
5d849ff
feat: 분석 완료 시 안내 스낵바 추가
moondev03 Jul 10, 2026
caaea97
fix: 분석 리포트 뒤로가기 안보이는 문제 수정
moondev03 Jul 12, 2026
0844ffa
fix: 대본 다시쓰기 탑바 케이스 반영
moondev03 Jul 12, 2026
4278554
feat: 분석 리포트 화면 내 대본 미입력 알림 및 작성 유도 로직 추가
moondev03 Jul 12, 2026
05b95e0
refactor: 회원 탈퇴 안내 단계의 동의 체크 로직 제거
moondev03 Jul 12, 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
28 changes: 22 additions & 6 deletions Prezel/app/src/main/java/com/team/prezel/ui/PrezelApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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.BoxScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -25,6 +26,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
Expand Down Expand Up @@ -53,6 +55,7 @@ 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.LocalSnackbarCoroutineScope
import com.team.prezel.core.ui.state.LocalSnackbarHostState
import com.team.prezel.core.ui.state.rememberAppDimmerState
import com.team.prezel.core.ui.util.noRippleClickable
Expand All @@ -70,11 +73,13 @@ fun PrezelApp(
) {
val navigator = remember(appState.navigationState) { Navigator(appState.navigationState) }
val snackbarHostState = remember { SnackbarHostState() }
val snackbarCoroutineScope = rememberCoroutineScope()
val appDimmerState = rememberAppDimmerState()

CompositionLocalProvider(
LocalNavigator provides navigator,
LocalAppDimmerState provides appDimmerState,
LocalSnackbarCoroutineScope provides snackbarCoroutineScope,
LocalSnackbarHostState provides snackbarHostState,
) {
DoubleBackToExitHandler(navigationState = appState.navigationState)
Expand Down Expand Up @@ -124,7 +129,11 @@ private fun PrezelAppContent(
}

EdgeToEdgeStatusBarBackground(style = statusBarStyle)
AppDimmerOverlay(isVisible = appDimmerState.isVisible, onDismiss = appDimmerState::dismiss)
AppDimmerOverlay(
isVisible = appDimmerState.isVisible,
foregroundContent = appDimmerState.foregroundContent,
onDismiss = appDimmerState::dismiss,
)
}
}

Expand Down Expand Up @@ -159,16 +168,23 @@ private fun AppNavigationContent(
@Composable
private fun AppDimmerOverlay(
isVisible: Boolean,
foregroundContent: (@Composable BoxScope.() -> Unit)?,
onDismiss: () -> Unit,
) {
if (!isVisible) return

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

foregroundContent?.invoke(this)
}
}

private fun defaultPrezelNavTransition(): ContentTransform =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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.Curation
import com.team.prezel.core.model.presentation.ExpectedQuestion
import com.team.prezel.core.model.presentation.MainData
import com.team.prezel.core.model.presentation.PracticeRecords
Expand All @@ -18,6 +19,7 @@ import com.team.prezel.core.model.presentation.SentenceAnalysisDetail
import com.team.prezel.core.model.presentation.Style
import com.team.prezel.core.model.presentation.WordAnalysisDetail
import com.team.prezel.core.model.presentation.WordAnalysisStatus
import com.team.prezel.core.network.model.presentation.GetCurationResponse
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
Expand Down Expand Up @@ -151,3 +153,13 @@ private fun GetMainDataResponse.GrowthGraph.toDomain(): PresentationGrowthPoint
accuracyScore = accuracyScore,
scriptMatchRate = scriptMatchRate,
)

internal fun GetCurationResponse.toDomain(): Curation =
Curation(
guideMessage = guideMessage,
materialType = materialType,
title = title,
sourceChannel = sourceChannel,
linkUrl = linkUrl,
imageUrl = imageUrl,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.team.prezel.core.domain.repository.badge.BadgeRepository
import com.team.prezel.core.model.badge.Badge
import com.team.prezel.core.model.badge.BadgeDetail
import com.team.prezel.core.model.badge.BadgeEvent
import com.team.prezel.core.model.badge.BadgeSortType
import com.team.prezel.core.network.datasource.BadgeRemoteDataSource
import com.team.prezel.core.network.model.badge.BadgeEventResponse
import com.team.prezel.core.network.model.badge.GetBadgeDetailResponse
Expand All @@ -16,9 +17,9 @@ import javax.inject.Inject
internal class BadgeRepositoryImpl @Inject constructor(
private val badgeRemoteDataSource: BadgeRemoteDataSource,
) : BadgeRepository {
override suspend fun getBadges(): Result<List<Badge>> =
override suspend fun getBadges(sort: BadgeSortType): Result<List<Badge>> =
runCatching {
badgeRemoteDataSource.getBadges()
badgeRemoteDataSource.getBadges(sort.value.lowercase())
}.mapCatching { response ->
response.map(GetBadgeResponse::toDomain)
}.mapDomainFailure()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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.Curation
import com.team.prezel.core.model.presentation.MainData
import com.team.prezel.core.model.presentation.PracticeRecords
import com.team.prezel.core.model.presentation.PresentationAnalysisSummary
Expand All @@ -14,6 +15,7 @@ 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.GetCurationResponse
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 @@ -136,4 +138,11 @@ internal class PresentationRepositoryImpl @Inject constructor(
content = content,
)
}.mapDomainFailure()

override suspend fun getCuration(presentationId: Long): Result<List<Curation>> =
runCatching {
presentationRemoteDataSource.getCuration(presentationId = presentationId)
}.mapCatching { response ->
response.map(GetCurationResponse::toDomain)
}.mapDomainFailure()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ package com.team.prezel.core.data.repository

import com.team.prezel.core.data.error.mapDomainFailure
import com.team.prezel.core.domain.repository.terms.TermsRepository
import com.team.prezel.core.model.terms.Term
import com.team.prezel.core.model.terms.TermsAgreement
import com.team.prezel.core.network.datasource.TermsRemoteDataSource
import com.team.prezel.core.network.model.terms.AgreeTermsRequest
import com.team.prezel.core.network.model.terms.FetchTermsResponse
import javax.inject.Inject

internal class TermsRepositoryImpl @Inject constructor(
private val termsRemoteDataSource: TermsRemoteDataSource,
) : TermsRepository {
override suspend fun fetchTerms(): Result<List<Term>> =
runCatching {
termsRemoteDataSource.fetchTerms()
}.mapCatching { response ->
response.map { term -> term.toDomain() }
}.mapDomainFailure()

override suspend fun agreeTerms(terms: List<TermsAgreement>): Result<Unit> =
runCatching {
termsRemoteDataSource.agreeTerms(
Expand All @@ -18,4 +27,14 @@ internal class TermsRepositoryImpl @Inject constructor(
},
)
}.mapDomainFailure()

private fun FetchTermsResponse.toDomain(): Term =
Term(
termsId = termsId.toLong(),
title = title,
summary = summary,
content = content,
isRequired = isRequired,
version = version,
)
}
1 change: 1 addition & 0 deletions Prezel/core/designsystem/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ android {
dependencies {
implementation(libs.kotlinx.collections.immutable)
implementation(libs.coil.kt.compose)
implementation(libs.coil.kt.svg)
implementation(libs.kotlinx.datetime)
implementation(libs.timber)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.team.prezel.core.designsystem.component

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.decode.SvgDecoder
import timber.log.Timber

@Composable
Expand All @@ -17,8 +21,18 @@ fun PrezelAsyncImage(
onSuccess: () -> Unit = {},
onError: (Throwable?) -> Unit = {},
) {
val context = LocalContext.current
val imageLoader = remember(context) {
ImageLoader
.Builder(context)
.components {
add(SvgDecoder.Factory())
}.build()
}

AsyncImage(
model = url,
imageLoader = imageLoader,
contentDescription = contentDescription,
modifier = modifier,
placeholder = ColorPainter(Color.Transparent),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package com.team.prezel.core.designsystem.component.textfield
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.isImeVisible
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
Expand All @@ -29,7 +30,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
Expand All @@ -43,6 +44,7 @@ import com.team.prezel.core.designsystem.preview.PreviewColumn
import com.team.prezel.core.designsystem.preview.PreviewSurface
import com.team.prezel.core.designsystem.theme.PrezelTheme

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun PrezelTextField(
value: String,
Expand All @@ -57,22 +59,29 @@ fun PrezelTextField(
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
) {
val focusManager = LocalFocusManager.current
val isImeVisible = WindowInsets.isImeVisible
var focused by remember { mutableStateOf(false) }
var wasImeVisible by remember { mutableStateOf(isImeVisible) }
var textFieldValue by remember { mutableStateOf(TextFieldValue(text = value, selection = TextRange(value.length))) }
val density = LocalDensity.current
val imeVisible = WindowInsets.ime.getBottom(density) > 0
val typing = focused && imeVisible

LaunchedEffect(value) {
if (value != textFieldValue.text) {
textFieldValue = TextFieldValue(text = value, selection = TextRange(value.length))
}
}

LaunchedEffect(isImeVisible, focused, enabled) {
if (enabled && focused && wasImeVisible && !isImeVisible) {
focusManager.clearFocus()
}
wasImeVisible = isImeVisible
}

val state = rememberPrezelTextFieldState(
value = textFieldValue.text,
enabled = enabled,
focused = typing,
focused = focused,
).let { state -> PrezelTextFieldStyle(state = state, status = status) }

PrezelTextField(
Expand All @@ -86,7 +95,7 @@ fun PrezelTextField(
},
placeholder = placeholder,
style = state,
focused = typing,
focused = focused,
onFocusChange = { isFocused -> focused = isFocused },
modifier = modifier,
label = label,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fun PrezelTheme(
isDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit,
) {
val colorScheme = if (isDarkTheme) PrezelColorScheme.Dark else PrezelColorScheme.Light
val colorScheme = if (isDarkTheme) PrezelColorScheme.Light else PrezelColorScheme.Light
val typographyScheme = PrezelTypographyScheme.Default()

CompositionLocalProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package com.team.prezel.core.domain.repository.badge
import com.team.prezel.core.model.badge.Badge
import com.team.prezel.core.model.badge.BadgeDetail
import com.team.prezel.core.model.badge.BadgeEvent
import com.team.prezel.core.model.badge.BadgeSortType
import kotlinx.coroutines.flow.Flow

interface BadgeRepository {
suspend fun getBadges(): Result<List<Badge>>
suspend fun getBadges(sort: BadgeSortType): Result<List<Badge>>

suspend fun getBadgeDetail(badgeCode: String): Result<BadgeDetail>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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.Curation
import com.team.prezel.core.model.presentation.MainData
import com.team.prezel.core.model.presentation.PracticeRecords
import com.team.prezel.core.model.presentation.PresentationAnalysisSummary
Expand Down Expand Up @@ -53,4 +54,6 @@ interface PresentationRepository {
presentationId: Long,
content: String,
): Result<Unit>

suspend fun getCuration(presentationId: Long): Result<List<Curation>>
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.team.prezel.core.domain.repository.terms

import com.team.prezel.core.model.terms.Term
import com.team.prezel.core.model.terms.TermsAgreement

interface TermsRepository {
suspend fun fetchTerms(): Result<List<Term>>

suspend fun agreeTerms(terms: List<TermsAgreement>): Result<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package com.team.prezel.core.domain.usecase.badge

import com.team.prezel.core.domain.repository.badge.BadgeRepository
import com.team.prezel.core.model.badge.Badge
import com.team.prezel.core.model.badge.BadgeSortType
import javax.inject.Inject

class FetchBadgesUseCase @Inject constructor(
private val badgeRepository: BadgeRepository,
) {
suspend operator fun invoke(): Result<List<Badge>> = badgeRepository.getBadges()
suspend operator fun invoke(sort: BadgeSortType = BadgeSortType.DEFAULT): Result<List<Badge>> = badgeRepository.getBadges(sort)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.team.prezel.core.domain.usecase.presentation

import com.team.prezel.core.domain.repository.presentation.PresentationRepository
import com.team.prezel.core.model.presentation.Curation
import javax.inject.Inject

class FetchCurationUseCase @Inject constructor(
private val presentationRepository: PresentationRepository,
) {
suspend operator fun invoke(presentationId: Long): Result<List<Curation>> = presentationRepository.getCuration(presentationId = presentationId)
}
Loading