Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
eeb2e37
[feat/#131] 앰플리튜드 sdk 추가
vahkjsdf Oct 2, 2026
d4b893c
[feat/#131] 오프라인 상태 권한 추가
vahkjsdf Oct 2, 2026
020a562
[feat/#131] 앰플리튜드 인스턴스 생성 및 초기화
vahkjsdf Oct 2, 2026
882ecb4
[feat/#131] 카카오 로그인, 회원가입 - userId 추가
vahkjsdf Oct 7, 2026
2b6926a
[chore/#131] AmplitudeModule 패키지 이동(amplitude)
vahkjsdf Oct 7, 2026
004e776
[feat/#131] event 트래킹 인터페이스 및 앰플리튜드 구현체 추가
vahkjsdf Oct 7, 2026
e470d06
[feat/#131] 화면 조회 이벤트 트래킹
vahkjsdf Oct 7, 2026
6021307
[feat/#131] 로그인, 회원가입 시 앰플리튜드 userId 설정
vahkjsdf Oct 7, 2026
1f98074
[feat/#131] 로그아웃/탈퇴/세션 만료 시 앰플리튜드 초기화
vahkjsdf Oct 7, 2026
9115676
[feat/#131] 회원가입 완료 이벤트 트래킹
vahkjsdf Oct 7, 2026
0f93a2c
[feat/#131] 모임 생성 단계 조회 이벤트 트래킹
vahkjsdf Oct 7, 2026
1deabf6
[feat/#131] 모임 생성 완료 이벤트 트래킹
vahkjsdf Oct 7, 2026
7438e9e
[feat/#131] 초대 링크 공유 이벤트 트래킹
vahkjsdf Oct 7, 2026
eda4f87
[feat/#131] 모임 참여 이벤트 트래킹
vahkjsdf Oct 7, 2026
9659980
[feat/#131] 조건 입력 단계 조회 이벤트 트래킹
vahkjsdf Oct 7, 2026
68901df
[feat/#131] 조건 제출 이벤트 트래킹
vahkjsdf Oct 7, 2026
65be841
[feat/#131] 추천 지역 선택 이벤트 트래킹
vahkjsdf Oct 7, 2026
4b9727c
[feat/#131] 장소 상세 조회·길찾기 이벤트 트래킹
vahkjsdf Oct 7, 2026
745c3ba
[feat/#131] 장소 투표 제출 이벤트 트래킹
vahkjsdf Oct 7, 2026
1182e09
[feat/#131] 푸시 알림 오픈 이벤트 트래킹
vahkjsdf Oct 7, 2026
ad9c985
[fix/#131] 프로세스 복원 시 화면 조회 이벤트 누락 수정
vahkjsdf Oct 7, 2026
3719005
[refactor/#131] 초대 링크 공유 이벤트명을 Share Clicked로 변경
vahkjsdf Oct 7, 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
5 changes: 5 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ android {
buildTypes {
debug {
manifestPlaceholders["crashlyticsCollectionEnabled"] = false
buildConfigField("String", "AMPLITUDE_API_KEY", properties.getProperty("amplitude.api.key.dev"))
}
release {
manifestPlaceholders["crashlyticsCollectionEnabled"] = true
buildConfigField("String", "AMPLITUDE_API_KEY", properties.getProperty("amplitude.api.key.prod"))
Comment thread
vahkjsdf marked this conversation as resolved.
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
Expand Down Expand Up @@ -117,4 +119,7 @@ dependencies {
implementation(libs.firebase.analytics)
implementation(libs.firebase.messaging)
implementation(libs.firebase.crashlytics)

// Amplitude
implementation(libs.amplitude.analytics)
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />
<uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" tools:node="remove" />
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/moive/app/MoiveApplication.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.moive.app

import android.app.Application
import com.amplitude.android.Amplitude
import com.kakao.sdk.common.KakaoSdk
import com.kakao.vectormap.KakaoMapSdk
import com.moive.app.core.fcm.NotificationChannels
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject

@HiltAndroidApp
class MoiveApplication : Application() {

@Inject
lateinit var amplitude: Amplitude

override fun onCreate() {
super.onCreate()

Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/com/moive/app/core/analytics/AnalyticsModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.moive.app.core.analytics

import com.moive.app.core.analytics.amplitude.AmplitudeTracker
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

@Binds
@Singleton
abstract fun bindAnalyticsTracker(
analyticsTracker: AmplitudeTracker
): AnalyticsTracker
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/moive/app/core/analytics/AnalyticsTracker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.moive.app.core.analytics

import com.moive.app.core.analytics.event.AnalyticsEvent

interface AnalyticsTracker {
fun track(event: AnalyticsEvent)

fun setUserId(userId: Long)

fun reset()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.moive.app.core.analytics.amplitude

import android.content.Context
import com.amplitude.android.Amplitude
import com.amplitude.android.autocaptureOptions
import com.moive.app.BuildConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AmplitudeModule {

@Provides
@Singleton
fun provideAmplitude(
@ApplicationContext context: Context,
): Amplitude = Amplitude(
apiKey = BuildConfig.AMPLITUDE_API_KEY,
context = context
) {
minIdLength = 1
autocapture = autocaptureOptions {
+sessions
+appLifecycles
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.moive.app.core.analytics.amplitude

import com.amplitude.android.Amplitude
import com.moive.app.core.analytics.AnalyticsTracker
import com.moive.app.core.analytics.event.AnalyticsEvent
import javax.inject.Inject

class AmplitudeTracker @Inject constructor(
private val amplitude: Amplitude,
) : AnalyticsTracker {

override fun track(event: AnalyticsEvent) {
amplitude.track(event.name, event.properties)
}

override fun setUserId(userId: Long) {
amplitude.setUserId(userId.toString())
}

override fun reset() {
amplitude.reset()
}
}
153 changes: 153 additions & 0 deletions app/src/main/java/com/moive/app/core/analytics/event/AnalyticsEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.moive.app.core.analytics.event

sealed interface AnalyticsEvent {
val name: String
val properties: Map<String, Any?>
get() = emptyMap()

data class ScreenViewed(
val screenName: String,
val meetingId: Long? = null,
) : AnalyticsEvent {
override val name = "Screen Viewed"
override val properties: Map<String, Any?>
get() = buildMap {
put(AnalyticsPropertyKey.SCREEN_NAME, screenName)
meetingId?.let { put(AnalyticsPropertyKey.MEETING_ID, it) }
}
}

// 가입
data object SignUpCompleted : AnalyticsEvent {
override val name = "Sign Up Completed"
}

// 모임 생성
data class MeetingCreationStepViewed(
val step: String,
) : AnalyticsEvent {
override val name = "Meeting Creation Step Viewed"
override val properties: Map<String, Any?>
get() = mapOf(AnalyticsPropertyKey.STEP to step)
}

data class MeetingCreated(
val meetingId: Long,
val purpose: String,
val hasSchedule: Boolean,
) : AnalyticsEvent {
override val name = "Meeting Created"
override val properties: Map<String, Any?>
get() = mapOf(
AnalyticsPropertyKey.MEETING_ID to meetingId,
AnalyticsPropertyKey.PURPOSE to purpose,
AnalyticsPropertyKey.HAS_SCHEDULE to hasSchedule,
)
}

// 초대
// 공유 선택창 실행 시점 (실제 공유 완료 여부는 알 수 없음)
data class InviteLinkShareClicked(
val meetingId: Long,
val source: Source,
) : AnalyticsEvent {
override val name = "Invite Link Share Clicked"
override val properties: Map<String, Any?>
get() = mapOf(
AnalyticsPropertyKey.MEETING_ID to meetingId,
AnalyticsPropertyKey.SOURCE to source.value,
)

enum class Source(val value: String) {
DETAIL("detail"),
CONFIRMED("confirmed"),
}
}

data class MeetingJoined(
val meetingId: Long,
) : AnalyticsEvent {
override val name = "Meeting Joined"
override val properties: Map<String, Any?>
get() = mapOf(AnalyticsPropertyKey.MEETING_ID to meetingId)
}

// 조건 입력
data class ConditionStepViewed(
val meetingId: Long,
val step: String,
) : AnalyticsEvent {
override val name = "Condition Step Viewed"
override val properties: Map<String, Any?>
get() = mapOf(
AnalyticsPropertyKey.MEETING_ID to meetingId,
AnalyticsPropertyKey.STEP to step,
)
}

data class ConditionSubmitted(
val meetingId: Long,
) : AnalyticsEvent {
override val name = "Condition Submitted"
override val properties: Map<String, Any?>
get() = mapOf(AnalyticsPropertyKey.MEETING_ID to meetingId)
}

// 장소 투표
data class RecommendedAreaSelected(
val meetingId: Long,
) : AnalyticsEvent {
override val name = "Recommended Area Selected"
override val properties: Map<String, Any?>
get() = mapOf(AnalyticsPropertyKey.MEETING_ID to meetingId)
}

data class PlaceDetailViewed(
val meetingId: Long,
val placeId: Long,
) : AnalyticsEvent {
override val name = "Place Detail Viewed"
override val properties: Map<String, Any?>
get() = mapOf(
AnalyticsPropertyKey.MEETING_ID to meetingId,
AnalyticsPropertyKey.PLACE_ID to placeId,
)
}

data class PlaceRouteOpened(
val meetingId: Long,
val placeId: Long,
) : AnalyticsEvent {
override val name = "Place Route Opened"
override val properties: Map<String, Any?>
get() = mapOf(
AnalyticsPropertyKey.MEETING_ID to meetingId,
AnalyticsPropertyKey.PLACE_ID to placeId,
)
}

// 알림
data class PushNotificationOpened(
val notificationId: Long?,
val meetingId: Long?,
) : AnalyticsEvent {
override val name = "Push Notification Opened"
override val properties: Map<String, Any?>
get() = buildMap {
notificationId?.let { put(AnalyticsPropertyKey.NOTIFICATION_ID, it) }
meetingId?.let { put(AnalyticsPropertyKey.MEETING_ID, it) }
}
}

data class PlaceVoteSubmitted(
val meetingId: Long,
val selectedCount: Int,
) : AnalyticsEvent {
override val name = "Place Vote Submitted"
override val properties: Map<String, Any?>
get() = mapOf(
AnalyticsPropertyKey.MEETING_ID to meetingId,
AnalyticsPropertyKey.SELECTED_COUNT to selectedCount,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.moive.app.core.analytics.event

object AnalyticsPropertyKey {
// 공통
const val SCREEN_NAME = "screen_name"
const val MEETING_ID = "meeting_id"

// 모임 생성
const val PURPOSE = "purpose"
const val HAS_SCHEDULE = "has_schedule"

// 초대
const val SOURCE = "source"

// 단계
const val STEP = "step"

// 장소 / 투표
const val PLACE_ID = "place_id"
const val SELECTED_COUNT = "selected_count"

// 알림
const val NOTIFICATION_ID = "notification_id"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.moive.app.data.auth.remote.dto.KakaoLoginResponse
fun KakaoLoginResponse.toModel(): KakaoLoginModel =
KakaoLoginModel(
registered = registered,
userId = userId,
nickname = nickname,
profileImageUrl = profileImageUrl,
email = email,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.runtime.Immutable
@Immutable
data class KakaoLoginModel(
val registered: Boolean,
val userId: Long?,
val nickname: String,
val profileImageUrl: String,
val email: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable
data class KakaoLoginResponse(
@SerialName("registered")
val registered: Boolean,
@SerialName("userId")
val userId: Long?,
@SerialName("nickname")
val nickname: String,
@SerialName("profileImageUrl")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import kotlinx.serialization.Serializable

@Serializable
data class SignUpResponse(
@SerialName("userId")
val userId: Long,
@SerialName("accessToken")
val accessToken: String,
@SerialName("refreshToken")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface AuthRepository {
isServiceAgreed: Boolean,
isPrivacyAgreed: Boolean,
isMarketingAgreed: Boolean,
): Result<Unit>
): Result<Long>

suspend fun postLogout(): Result<Unit>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@ class AuthRepositoryImpl @Inject constructor(
isServiceAgreed: Boolean,
isPrivacyAgreed: Boolean,
isMarketingAgreed: Boolean,
): Result<Unit> =
): Result<Long> =
suspendRunCatching {
val token = authRemoteDataSource.postSignUp(
val data = authRemoteDataSource.postSignUp(
accessToken = accessToken,
isServiceAgreed = isServiceAgreed,
isPrivacyAgreed = isPrivacyAgreed,
isMarketingAgreed = isMarketingAgreed,
).checkData()

localTokenDatasource.setAccessToken(token.accessToken)
localTokenDatasource.setRefreshToken(token.refreshToken)
localTokenDatasource.setAccessToken(data.accessToken)
localTokenDatasource.setRefreshToken(data.refreshToken)

data.userId
}

override suspend fun postLogout(): Result<Unit> =
Expand Down
Loading
Loading