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
6 changes: 6 additions & 0 deletions .claude/docs/clock-convention.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
- UseCase, Repository, DataSource 등 시간을 다루는 모든 레이어에 적용된다.
- 타임스탬프가 필요한 경우: `clock.now().toEpochMilliseconds()`.

### 예외: Compose 레이어

- Composable, `remember*` 함수, ScaffoldState 등 **Compose 레이어**에서는 `Clock.System` 직접 사용을 허용한다.
- 이유: Composable/`remember*` 시그니처에 `Clock`을 강제 주입하면 호출부 부담이 커지고, UI 상태 초기값에 사용되는 현재 시각은 테스트 결정성보다 호출 시점의 자연스러운 사용성이 더 중요하다.
- 대신 Compose 레이어가 호출하는 ViewModel/UseCase/Repository는 컨벤션을 지킨다.

### 이유

- 테스트에서 시간을 고정할 수 있어야 TC가 결정론적으로 동작한다.
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ xcuserdata/

# Google
google-services.json
GoogleService-Info.plist
GoogleService-Info.plist

# SPM
exportedKMP*/
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ Kotlin Multiplatform 일기/메모 앱 (Android, iOS, JVM, WASM).

ProjectGuard로 강제한다. domain은 data/feature를 알 수 없고, feature는 data를 직접 참조할 수 없다.

## 의존성 추가 규칙

- **코드 최소화**: 상위 의존성으로 transitively 포함되는 의존성은 명시적으로 추가하지 않는다.
- 예: `compose-material3` 추가 시 `compose-foundation`, `compose-ui`는 transitive로 함께 적용되므로 별도로 추가하지 않는다.
- **범위 최소화**: 실제 사용하는 최소 범위의 의존성만 추가한다.
- 예: `compose-runtime`만 필요하면 `compose-runtime`만 추가한다. `compose-ui`, `compose-foundation` 등 상위 의존성은 추가하지 않는다.

## 공통 컨벤션

- Clock 주입: @.claude/docs/clock-convention.md
Expand Down
6 changes: 3 additions & 3 deletions Diary/Diary.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.8.1;
MARKETING_VERSION = 1.8.2;
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.github.taetae98coding.diary.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -413,7 +413,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.8.1;
MARKETING_VERSION = 1.8.2;
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.github.taetae98coding.diary;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -518,7 +518,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.8.1;
MARKETING_VERSION = 1.8.2;
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = io.github.taetae98coding.diary;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
232 changes: 116 additions & 116 deletions app/android/dependencies/realReleaseRuntimeClasspath.txt

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions app/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ kotlin {
implementation(projects.feature.routine)
implementation(projects.feature.tag)
implementation(projects.library.navigation3Runtime)
implementation(projects.logger.analytics.api)
implementation(libs.jetbrains.compose.material3.navigation.suite)
implementation(libs.jetbrains.navigation3.ui)
implementation(libs.coil.compose)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.navigation3.scene.Scene
import androidx.navigation3.scene.SinglePaneSceneStrategy
import androidx.navigation3.ui.NavDisplay
import androidx.navigationevent.NavigationEvent
import io.github.taetae98coding.diary.app.shared.navigation3.rememberAnalyticsNavEntryDecorator
import io.github.taetae98coding.diary.app.shared.navigation3.rememberRetainedValuesStoreNavEntryDecorator
import io.github.taetae98coding.diary.feature.calendar.calendarEntry
import io.github.taetae98coding.diary.feature.login.loginEntry
Expand All @@ -32,6 +33,7 @@ internal fun AppNavigation(
rememberSaveableStateHolderNavEntryDecorator(),
rememberRetainedValuesStoreNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator(),
rememberAnalyticsNavEntryDecorator(),
),
sceneStrategies = listOf(
DialogSceneStrategy(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.github.taetae98coding.diary.app.shared.navigation3

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.navigation3.runtime.NavEntryDecorator
import io.github.taetae98coding.diary.logger.analytics.api.AnalyticsLogEntry
import io.github.taetae98coding.diary.logger.core.DiaryLogger

internal class AnalyticsNavEntryDecorator<T : Any> :
NavEntryDecorator<T>(
decorate = { entry ->
val screenName = entry.contentKey::class.simpleName.orEmpty()

LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
DiaryLogger.log(
AnalyticsLogEntry(
name = "screen_view",
params = mapOf("screen_name" to screenName),
),
)
}

entry.Content()
},
)

@Composable
internal fun <T : Any> rememberAnalyticsNavEntryDecorator(): AnalyticsNavEntryDecorator<T> {
return remember { AnalyticsNavEntryDecorator() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import androidx.navigation3.ui.defaultPredictivePopTransitionSpec
import androidx.navigation3.ui.defaultTransitionSpec
import androidx.navigationevent.NavigationEvent

internal actual fun <T : Any> platformTransitionSpec(state: io.github.taetae98coding.diary.app.shared.AppState): androidx.compose.animation.AnimatedContentTransitionScope<androidx.navigation3.scene.Scene<T>>.() -> androidx.compose.animation.ContentTransform {
internal actual fun <T : Any> platformTransitionSpec(state: AppState): AnimatedContentTransitionScope<Scene<T>>.() -> ContentTransform {
return defaultTransitionSpec()
}

Expand Down
1 change: 0 additions & 1 deletion app/wasm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ kotlin {
dependencies {
implementation(projects.app.shared)
implementation(libs.jetbrains.compose.components.resources)
implementation(libs.jetbrains.compose.ui)
implementation(libs.jetbrains.compose.material3)
}
}
Expand Down
4 changes: 2 additions & 2 deletions build-logic/src/main/kotlin/BuildConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ public data object BuildConfig {
internal const val ANDROID_TARGET_SDK = 36

public const val NAMESPACE: String = "io.github.taetae98coding.diary"
public const val VERSION_NAME: String = "1.8.1"
public const val VERSION_CODE: Int = 10
public const val VERSION_NAME: String = "1.8.2"
public const val VERSION_CODE: Int = 11
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import io.github.taetae98coding.diary.gradle.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.findByType
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

internal class ComposePrimitivePlugin : Plugin<Project> {
override fun apply(target: Project) {
Expand All @@ -15,21 +13,6 @@ internal class ComposePrimitivePlugin : Plugin<Project> {
apply("org.jetbrains.kotlin.plugin.compose")
}

target.extensions
.findByType<KotlinMultiplatformExtension>()
?.apply {
compilerOptions {
optIn.addAll(
"androidx.compose.ui.ExperimentalComposeUiApi",
"androidx.compose.foundation.ExperimentalFoundationApi",
"androidx.compose.foundation.layout.ExperimentalFlexBoxApi",
"androidx.compose.material3.ExperimentalMaterial3Api",
"androidx.compose.material3.ExperimentalMaterial3ExpressiveApi",
"androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi",
)
}
}

target.configure<ComposeCompilerGradlePluginExtension> {
metricsDestination.set(target.projectDir.resolve("build/compose/metrics"))
reportsDestination.set(target.projectDir.resolve("build/compose/reports"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import io.github.taetae98coding.diary.gradle.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

internal class WasmPrimitivePlugin : Plugin<Project> {
@OptIn(ExperimentalWasmDsl::class)
override fun apply(target: Project) {
target.plugins {
apply("org.jetbrains.kotlin.multiplatform")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)

package io.github.taetae98coding.diary.compose.core.button

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularWavyProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)

package io.github.taetae98coding.diary.compose.core.button

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularWavyProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FloatingActionButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package io.github.taetae98coding.diary.compose.core.date

import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TimePicker
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package io.github.taetae98coding.diary.compose.core.date

import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TimePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package io.github.taetae98coding.diary.compose.core.dialog

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package io.github.taetae98coding.diary.compose.core.dialog

import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)

package io.github.taetae98coding.diary.compose.core.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.taetae98coding.diary.core.database.impl.dao

import androidx.room3.Dao
import androidx.room3.Query
import androidx.room3.RewriteQueriesToDropUnusedColumns
import io.github.taetae98coding.diary.core.database.api.entity.CalendarMemoLocalEntity
import kotlin.uuid.Uuid
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -38,6 +39,7 @@ internal interface AccountCalendarMemoDao {
ORDER BY Memo.isAllDay DESC, Memo.start, Memo.endInclusive DESC, Memo.title
""",
)
@RewriteQueriesToDropUnusedColumns
fun get(
accountId: Uuid,
year: Int,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:OptIn(ExperimentalCoroutinesApi::class)

package io.github.taetae98coding.diary.core.supabase.impl

import io.github.jan.supabase.SupabaseClient
Expand All @@ -8,6 +10,7 @@ import io.github.jan.supabase.auth.status.RefreshFailureCause
import io.github.jan.supabase.auth.status.SessionStatus
import io.github.taetae98coding.diary.core.supabase.api.SupabaseAuth
import io.github.taetae98coding.diary.core.supabase.api.SupabaseSessionStatus
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapLatest
Expand Down Expand Up @@ -45,8 +48,10 @@ internal class SupabaseAuthImpl(private val supabase: SupabaseClient) : Supabase
}

private suspend fun getSessionStatusFromStorage(): SupabaseSessionStatus? {
return supabase.auth.sessionManager.loadSession().user
?.let { SupabaseSessionStatus.Authenticated(userId = it.id, email = it.email) }
return runCatching {
supabase.auth.sessionManager.loadSession().user
?.let { SupabaseSessionStatus.Authenticated(userId = it.id, email = it.email) }
}.getOrNull()
}

override suspend fun signOut() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
@file:OptIn(ExperimentalCoroutinesApi::class)

package io.github.taetae98coding.diary.data.account.repository

import io.github.taetae98coding.diary.core.model.account.AccountInfo
import io.github.taetae98coding.diary.core.supabase.api.SupabaseAuth
import io.github.taetae98coding.diary.core.supabase.api.SupabaseSessionStatus
import io.github.taetae98coding.diary.domain.account.repository.AccountInfoRepository
import kotlin.uuid.Uuid
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.mapLatest
Expand Down
1 change: 1 addition & 0 deletions data/sync/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ kotlin {
implementation(projects.core.network.api)
implementation(projects.domain.account)
implementation(projects.domain.sync)
implementation(projects.logger.crashlytics.api)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import io.github.taetae98coding.diary.domain.account.usecase.GetAccountUseCase
import io.github.taetae98coding.diary.logger.core.DiaryLogger
import io.github.taetae98coding.diary.logger.crashlytics.api.CrashlyticsLogEntry
import kotlin.uuid.Uuid
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.first
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
Expand All @@ -28,7 +31,17 @@ internal class SyncWorker(
}

Result.success()
} catch (_: Throwable) {
} catch (throwable: Throwable) {
if (throwable is CancellationException) {
throw throwable
}

DiaryLogger.log(
CrashlyticsLogEntry(
message = "동기화 실패[$runAttemptCount]",
throwable = throwable,
),
)
Result.retry()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import io.github.taetae98coding.diary.core.model.sync.SyncStatus
import io.github.taetae98coding.diary.core.model.sync.SyncType
import io.github.taetae98coding.diary.data.sync.di.SyncCoroutineScope
import io.github.taetae98coding.diary.domain.sync.manager.SyncManager
import io.github.taetae98coding.diary.logger.core.DiaryLogger
import io.github.taetae98coding.diary.logger.crashlytics.api.CrashlyticsLogEntry
import kotlin.uuid.Uuid
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -42,7 +44,12 @@ internal class NonAndroidSyncManager(
if (throwable is CancellationException) {
throw throwable
} else {
throwable.printStackTrace()
DiaryLogger.log(
CrashlyticsLogEntry(
message = "동기화 실패[$type]",
throwable = throwable,
),
)
_syncStatus.value = SyncStatus.Failed
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
@file:OptIn(ExperimentalCoroutinesApi::class)

package io.github.taetae98coding.diary.domain.account.usecase

import io.github.taetae98coding.diary.core.model.account.Account
import io.github.taetae98coding.diary.domain.account.repository.AccountInfoRepository
import io.github.taetae98coding.diary.domain.account.repository.AccountMetaDataRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
Expand Down
Loading