Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d0de1a3
feat: saju-contents 기능 모듈 추가
oungsi2000 Aug 9, 2026
7c1d6d0
feat: 택일 운세 도메인 모델·UseCase 추가 (#107)
oungsi2000 Aug 10, 2026
b409582
feat: 일일 운세 결과 조회 ViewModel 및 UseCase 구현
oungsi2000 Aug 11, 2026
6ce97df
feat: 택일 운세 Repository 구현체 추가 (#107)
oungsi2000 Aug 10, 2026
96bfae2
feat: 단일 운세 조회 기능 추가
oungsi2000 Aug 11, 2026
4e0ea1d
refactor: 운세 공유 관련 문자열 리소스 제거
oungsi2000 Aug 12, 2026
87e7dc7
feat: 택일 운세 Remote DataSource 및 DTO 추가 (#107)
oungsi2000 Aug 10, 2026
dbf2fc3
feat: 일일 운세 상세 조회(ID) Remote DataSource 구현
oungsi2000 Aug 11, 2026
f6fa2fe
refactor: RemoteDataSourceModule import 순서 정리
oungsi2000 Aug 12, 2026
174c55e
feat: 일일 운세 결과 조회 ViewModel 및 UseCase 구현
oungsi2000 Aug 11, 2026
6c0289f
feat: 택일 운세 ViewModel 및 상태 정의 추가 (#107)
oungsi2000 Aug 10, 2026
0628fe1
feat: 택일(Date Fortune) UI State 및 Route 구현
oungsi2000 Aug 11, 2026
53386d9
feat: DateFortune SideEffect 구조 변경 및 ViewModel 리팩터링
oungsi2000 Aug 11, 2026
064bc81
feat: DateFortuneViewModel에서 결과 데이터 및 탭 전환 로직 제거
oungsi2000 Aug 11, 2026
cb9f196
feat: 일진 결과 점수순 정렬 및 상위 3건 제한
oungsi2000 Aug 11, 2026
737822b
feat: 일진 운세 결과 공유 UI 상태 및 ViewModel 로직 구현
oungsi2000 Aug 12, 2026
ddb8527
test: FakeDayFortuneRepository 기능 확장 및 SideEffect 테스트 수정
oungsi2000 Aug 12, 2026
927280c
feat: 일일 운세 공유 다이얼로그 및 카카오톡 공유 기능 구현
oungsi2000 Aug 12, 2026
1d542e0
feat: 일일 운세 공유를 위한 딥링크 및 앱 링크 설정
oungsi2000 Aug 12, 2026
96bc347
chore: local.properties 키 부재 시 빌드 실패 방지
oungsi2000 Aug 12, 2026
8d53583
style: build.gradle.kts 내 TODO 주석 공백 추가
oungsi2000 Aug 12, 2026
8e9a36a
refactor: 운세 공유 다이얼로그 범용화 및 이름 변경
oungsi2000 Aug 13, 2026
9743076
refactor: 운세 관련 스트링 리소스 키 이름 변경
oungsi2000 Aug 13, 2026
1a14681
feat: 앱 링크 지원 경로 확장 및 명세 변경
oungsi2000 Aug 14, 2026
f403f1f
feat: 택일(Date Fortune) UI State 및 Route 구현
oungsi2000 Aug 11, 2026
b42a2c3
feat: 택일 운세 입력/결과 화면 Compose UI 추가 (#107)
oungsi2000 Aug 10, 2026
fe560c9
refactor: 택일(DateFortune) UI 패키지 구조 개편
oungsi2000 Aug 11, 2026
0376f5b
feat: 택일 입력 화면 UI 개편 및 성별 선택 필드 제거
oungsi2000 Aug 11, 2026
15c7595
feat: 날짜 선택 달력 라이브러리 도입 및 UI 고도화
oungsi2000 Aug 11, 2026
4d7c965
refactor: 날짜별 운세 입력 로직 개선 및 Snackbar 전환
oungsi2000 Aug 11, 2026
eb04ec4
refactor: 택일 운세 UI 컴포넌트 구조 개선 및 리소스 이동
oungsi2000 Aug 11, 2026
5d874ee
style: R 클래스 임포트 순서 조정
oungsi2000 Aug 11, 2026
8fd3818
feat: 일진 결과 화면 UI 고도화 및 데이터 연동
oungsi2000 Aug 11, 2026
67476c3
feat: 일운 결과 공유 SideEffect 처리 및 UI 연결
oungsi2000 Aug 12, 2026
c15d076
feat: 일진 결과 캘린더 저장 기능 구현
oungsi2000 Aug 12, 2026
c892232
feat: 일운 결과 화면 공유 다이얼로그 연동
oungsi2000 Aug 12, 2026
3e8511a
feat: 연도별 운세 결과 조회 기능 구현 및 화면 분리
oungsi2000 Aug 13, 2026
3b21bec
refactor: 운세 도메인 모델 및 공유 컴포넌트 범용화
oungsi2000 Aug 13, 2026
2c285b0
feat: FortuneCategoryStar 도메인 모델 추가
oungsi2000 Aug 13, 2026
feb5981
feat: Haze 라이브러리를 이용한 운세 결과 카드 블러 효과 적용
oungsi2000 Aug 14, 2026
a68733d
fix: 일운 결과 공유 URL의 중복 스킴 제거
oungsi2000 Aug 14, 2026
c19a9db
feat: 연간 운세(YearFortune) 도메인/데이터/UI 레이어 구현
oungsi2000 Aug 12, 2026
b8e5a88
feat: 연도 선택 화면 에러 처리 SideEffect 전환 및 UI 개선
oungsi2000 Aug 12, 2026
875bb6b
feat: 연도별 운세 결과 조회 기능 구현 및 화면 분리
oungsi2000 Aug 13, 2026
bae6dad
feat: 연간 운세 결과 공유 기능 고도화 및 공용 UI 컴포넌트 적용
oungsi2000 Aug 13, 2026
85495c9
feat: Haze 라이브러리를 이용한 운세 결과 카드 블러 효과 적용
oungsi2000 Aug 14, 2026
ecdfb28
refactor: YearSelectionScreen 이름 변경 및 공유 URL 수정
oungsi2000 Aug 14, 2026
7595e97
style: YearFortuneResultScreen 배경색 수정
oungsi2000 Aug 15, 2026
d426817
feat: 상대방 궁합 조회 기능 및 결과 화면 구현
oungsi2000 Aug 19, 2026
0da9f8d
Merge pull request #147 from YAPP-Github/feat/106-saju-compatibility
oungsi2000 Aug 19, 2026
27b0d9b
Merge branch 'develop' into feat/107-date-fortune
oungsi2000 Aug 19, 2026
50ab0ff
test: FakeUserRepository 내 withdraw 메서드 스텁 추가
oungsi2000 Aug 19, 2026
74d782a
style: FakeUserRepository 코드 포맷팅 및 트레일링 콤마 적용
oungsi2000 Aug 19, 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
24 changes: 24 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import java.util.Properties


plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
Expand All @@ -7,6 +10,12 @@ plugins {
alias(libs.plugins.google.services)
}

val localProperty =
Properties().apply {
val f = rootProject.file("local.properties")
if (f.exists()) f.inputStream().use { load(it) }
}

android {
namespace = "com.kikidan.todakun"
compileSdk = 37
Expand All @@ -22,7 +31,20 @@ android {
}

buildTypes {
debug {
// TODO CI 통과를 위해 공백을 넣음. 추후 CD 설정 시 재설정
val kakaoKey = localProperty.getProperty("KAKAO_NATIVE_APP_KEY_DEV") ?: ""
val appLinkHost = localProperty.getProperty("APP_LINK_HOST_DEV") ?: ""
manifestPlaceholders["KAKAO_APP_KEY"] = kakaoKey
manifestPlaceholders["APP_LINK_HOST"] = appLinkHost
}
release {
// TODO CI 통과를 위해 공백을 넣음. 추후 CD 설정 시 재설정
val kakaoKey = localProperty.getProperty("KAKAO_NATIVE_APP_KEY") ?: ""
val appLinkHost = localProperty.getProperty("APP_LINK_HOST") ?: ""
manifestPlaceholders["KAKAO_APP_KEY"] = kakaoKey
manifestPlaceholders["APP_LINK_HOST"] = appLinkHost

isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
Expand Down Expand Up @@ -70,10 +92,12 @@ dependencies {
implementation(projects.feature.mypage)
implementation(projects.feature.onboarding)
implementation(projects.feature.auth)
implementation(projects.feature.sajuContents)
implementation(projects.feature.luckAction)
implementation(projects.feature.home)
implementation(projects.feature.chat)
implementation(projects.feature.notification)
implementation(projects.feature.sajuContents)

implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.navigation3.ui)
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<!-- 카카오 공유 딥링크 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="kakao${KAKAO_APP_KEY}" android:host="kakaolink" />
</intent-filter>

<!-- App-Link -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"/>
<data android:host="${APP_LINK_HOST}"/>
<data android:path="/day-fortune" />
<data android:path="/year-fortune" />
<data android:path="/compatibility" />
</intent-filter>
</activity>
</application>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.kikidan.data_remote.datasource

import com.kikidan.data.datasource.RemoteCompatibilityDataSource
import com.kikidan.data_remote.dto.compatibility.CompatibilityResponse
import com.kikidan.data_remote.dto.compatibility.toDomain
import com.kikidan.data_remote.util.bodyNotNull
import com.kikidan.domain.model.compatibility.Compatibility
import dagger.Lazy
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.request.post
import javax.inject.Inject

class RemoteCompatibilityDataSourceImpl
@Inject
constructor(
private val client: Lazy<HttpClient>,
) : RemoteCompatibilityDataSource {
override suspend fun postCompatibility(partnerLinkId: String): Compatibility =
client
.get()
.post("$COMPATIBILITIES_URL/$partnerLinkId")
.bodyNotNull<CompatibilityResponse>()
.toDomain()

override suspend fun getCompatibility(compatibilityId: String): Compatibility =
client
.get()
.get("$COMPATIBILITIES_URL/$compatibilityId")
.bodyNotNull<CompatibilityResponse>()
.toDomain()

companion object {
private const val COMPATIBILITIES_URL = "api/v1/compatibilities"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.kikidan.data_remote.datasource

import com.kikidan.data.datasource.RemoteDayFortuneDataSource
import com.kikidan.data_remote.dto.dayfortune.CreateDayFortuneRequest
import com.kikidan.data_remote.dto.dayfortune.DayFortuneResponse
import com.kikidan.data_remote.dto.dayfortune.toDomain
import com.kikidan.data_remote.util.bodyNotNull
import com.kikidan.domain.model.dayfortune.DayFortune
import com.kikidan.domain.model.dayfortune.DayFortunePurpose
import dagger.Lazy
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import javax.inject.Inject

class RemoteDayFortuneDataSourceImpl
@Inject
constructor(
private val client: Lazy<HttpClient>,
) : RemoteDayFortuneDataSource {
override suspend fun postDayFortunes(
purpose: DayFortunePurpose,
targetDates: List<LocalDate>,
): List<DayFortune> =
client
.get()
.post(DAY_FORTUNES_URL) {
setBody(
CreateDayFortuneRequest(
purpose = purpose.name,
targetDates = targetDates.map { it.format(DateTimeFormatter.ISO_LOCAL_DATE) },
),
)
}.bodyNotNull<List<DayFortuneResponse>>()
.map { it.toDomain() }

override suspend fun getDayFortune(id: String): DayFortune =
client
.get()
.get("$DAY_FORTUNES_URL/$id")
.bodyNotNull<DayFortuneResponse>()
.toDomain()

companion object {
private const val DAY_FORTUNES_URL = "api/v1/day-fortunes"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import com.kikidan.data_remote.dto.CommonResponse
import com.kikidan.data_remote.dto.saju.PartnerSajuDetailResponse
import com.kikidan.data_remote.dto.saju.PartnerSajuSummaryResponse
import com.kikidan.data_remote.dto.saju.RegisterPartnerSajuResponse
import com.kikidan.data_remote.dto.saju.SajuChartDetailResponse
import com.kikidan.data_remote.dto.saju.toChartDetail
import com.kikidan.data_remote.dto.saju.toDomain
import com.kikidan.data_remote.dto.saju.toRegisterRequest
import com.kikidan.data_remote.dto.saju.toUpdateRequest
import com.kikidan.data_remote.util.bodyNotNull
import com.kikidan.domain.model.saju.PartnerSaju
import com.kikidan.domain.model.saju.PartnerSajuInput
import com.kikidan.domain.model.saju.SajuChartDetail
import dagger.Lazy
import io.ktor.client.HttpClient
import io.ktor.client.call.body
Expand Down Expand Up @@ -40,6 +43,13 @@ class RemotePartnerSajuDataSourceImpl
.bodyNotNull<PartnerSajuDetailResponse>()
.toDomain()

override suspend fun getPartnerSajuChartDetail(linkId: String): SajuChartDetail =
client
.get()
.get(partnerUrl(linkId))
.bodyNotNull<SajuChartDetailResponse>()
.toChartDetail()

override suspend fun deletePartnerSaju(linkId: String) {
client.get().delete(partnerUrl(linkId)).body<CommonResponse<Unit>>()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.kikidan.data_remote.datasource

import com.kikidan.data.datasource.RemoteYearFortuneDataSource
import com.kikidan.data_remote.dto.fortune.YearFortuneResponse
import com.kikidan.data_remote.dto.fortune.toDomain
import com.kikidan.data_remote.util.bodyNotNull
import com.kikidan.domain.model.fortune.YearFortune
import dagger.Lazy
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.request.post
import javax.inject.Inject

class RemoteYearFortuneDataSourceImpl
@Inject
constructor(
private val client: Lazy<HttpClient>,
) : RemoteYearFortuneDataSource {
override suspend fun postYearFortune(year: Int): YearFortune =
client
.get()
.post("api/v1/year-fortunes/$year")
.bodyNotNull<YearFortuneResponse>()
.toDomain()

override suspend fun getYearFortune(id: String): YearFortune =
client
.get()
.get("api/v1/year-fortunes/$id")
.bodyNotNull<YearFortuneResponse>()
.toDomain()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@ package com.kikidan.data_remote.di
import com.kikidan.data.auth.AuthTokenCacheInvalidator
import com.kikidan.data.datasource.RemoteAuthDataSource
import com.kikidan.data.datasource.RemoteChatDataSource
import com.kikidan.data.datasource.RemoteCompatibilityDataSource
import com.kikidan.data.datasource.RemoteDayFortuneDataSource
import com.kikidan.data.datasource.RemoteDeviceTokenDataSource
import com.kikidan.data.datasource.RemoteFortuneDataSource
import com.kikidan.data.datasource.RemoteLuckActionDataSource
import com.kikidan.data.datasource.RemoteNotificationDataSource
import com.kikidan.data.datasource.RemotePartnerSajuDataSource
import com.kikidan.data.datasource.RemoteSajuDataSource
import com.kikidan.data.datasource.RemoteUserDataSource
import com.kikidan.data.datasource.RemoteYearFortuneDataSource
import com.kikidan.data_remote.auth.BearerTokenCacheInvalidator
import com.kikidan.data_remote.datasource.RemoteAuthDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteChatDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteCompatibilityDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteDayFortuneDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteDeviceTokenDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteFortuneDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteLuckActionDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteNotificationDataSourceImpl
import com.kikidan.data_remote.datasource.RemotePartnerSajuDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteSajuDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteUserDataSourceImpl
import com.kikidan.data_remote.datasource.RemoteYearFortuneDataSourceImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -37,6 +43,14 @@ abstract class RemoteDataSourceModule {
@Singleton
abstract fun bindAuthTokenCacheInvalidator(impl: BearerTokenCacheInvalidator): AuthTokenCacheInvalidator

@Binds
@Singleton
abstract fun bindYearFortuneRemoteDataSource(impl: RemoteYearFortuneDataSourceImpl): RemoteYearFortuneDataSource

@Binds
@Singleton
abstract fun bindDayFortuneRemoteDataSource(impl: RemoteDayFortuneDataSourceImpl): RemoteDayFortuneDataSource

@Binds
@Singleton
abstract fun bindFortuneRemoteDataSource(impl: RemoteFortuneDataSourceImpl): RemoteFortuneDataSource
Expand Down Expand Up @@ -68,4 +82,10 @@ abstract class RemoteDataSourceModule {
@Binds
@Singleton
abstract fun bindDeviceTokenDataSource(impl: RemoteDeviceTokenDataSourceImpl): RemoteDeviceTokenDataSource

@Binds
@Singleton
abstract fun bindCompatibilityRemoteDataSource(
impl: RemoteCompatibilityDataSourceImpl,
): RemoteCompatibilityDataSource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.kikidan.data_remote.dto.compatibility

import com.kikidan.data_remote.dto.saju.ElementResponse
import com.kikidan.data_remote.dto.saju.RelationshipTypeResponse
import com.kikidan.data_remote.dto.saju.toOhaeng
import com.kikidan.domain.model.compatibility.Compatibility
import com.kikidan.domain.model.compatibility.CompatibilityOhaeng
import com.kikidan.domain.model.saju.RelationshipType
import kotlinx.serialization.Serializable

@Serializable
data class CompatibilityResponse(
val id: String,
val partnerName: String,
val relationshipType: RelationshipTypeResponse,
val score: Int,
val headline: String,
val subheadline: String,
val summary: String,
val totalAnalysis: String,
val analysisBasis: String,
val ohaengs: List<CompatibilityOhaengResponse>,
)

@Serializable
data class CompatibilityOhaengResponse(
val element: ElementResponse,
val percentage: Int,
)

fun CompatibilityResponse.toDomain(): Compatibility =
Compatibility(
id = id,
partnerName = partnerName,
relationshipType = RelationshipType(code = relationshipType.code, label = relationshipType.label),
score = score,
headline = headline,
subheadline = subheadline,
summary = summary,
totalAnalysis = totalAnalysis,
analysisBasis = analysisBasis,
ohaengs = ohaengs.map { CompatibilityOhaeng(ohaeng = it.element.code.toOhaeng(), percentage = it.percentage) },
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.kikidan.data_remote.dto.dayfortune

import kotlinx.serialization.Serializable

@Serializable
data class CreateDayFortuneRequest(
val purpose: String,
val targetDates: List<String>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.kikidan.data_remote.dto.dayfortune

import com.kikidan.domain.model.dayfortune.DayFortune
import com.kikidan.domain.model.dayfortune.DayFortunePurpose
import com.kikidan.domain.model.fortune.FortuneCategory
import com.kikidan.domain.model.fortune.FortuneCategoryStar
import kotlinx.serialization.Serializable
import java.time.LocalDate

@Serializable
data class DayFortuneResponse(
val id: String,
val purpose: String,
val targetDate: String,
val score: Int,
val title: String,
val content: String,
val fortuneCategories: List<FortuneCategoryStarResponse>,
)

@Serializable
data class FortuneCategoryStarResponse(
val fortuneCategory: String,
val star: Int,
)

fun DayFortuneResponse.toDomain(): DayFortune =
DayFortune(
id = id,
purpose =
DayFortunePurpose.entries.find { it.name == purpose }
?: throw IllegalArgumentException("알 수 없는 purpose 입니다: $purpose"),
targetDate = LocalDate.parse(targetDate),
score = score,
title = title,
content = content,
categoryStars = fortuneCategories.mapNotNull { it.toDomainOrNull() },
)

private fun FortuneCategoryStarResponse.toDomainOrNull(): FortuneCategoryStar? {
val category = FortuneCategory.entries.find { it.name == fortuneCategory } ?: return null
return FortuneCategoryStar(category = category, star = star)
}
Loading
Loading