From 43e7ee3584f70e1cfa035245feb0dee527010a4e Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:59:02 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[feat]=20#107=20Firebase=20Crashlytics=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 ++ build.gradle.kts | 1 + 2 files changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d1ab406f..4e76bc14 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { id("org.jetbrains.kotlin.kapt") alias(libs.plugins.navigationSafeArgs) id("com.google.gms.google-services") + id("com.google.firebase.crashlytics") } val properties = Properties().apply { @@ -166,4 +167,5 @@ dependencies { implementation(platform("com.google.firebase:firebase-bom:33.4.0")) implementation("com.google.firebase:firebase-analytics-ktx") implementation("com.google.firebase:firebase-config-ktx") + implementation("com.google.firebase:firebase-crashlytics-ndk") } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 309816ff..11f54c0d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,4 +5,5 @@ plugins { alias(libs.plugins.dagger.hilt) apply false alias(libs.plugins.navigationSafeArgs) apply false id("com.google.gms.google-services") version "4.4.2" apply false + id("com.google.firebase.crashlytics") version "3.0.6" apply false } \ No newline at end of file From 910976f3979b1d0aec1ad0e32bd60b24c0d60801 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:07:17 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[feat]=20#107=20Firebase=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EB=B2=84=EC=A0=84=20libs.version.toml=20?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=B6=9C=20=EB=B0=8F=20kotlin=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=97=85=EA=B7=B8=EB=A0=88=EC=9D=B4=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 14 +++++++------- build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 30 +++++++++++++----------------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4e76bc14..76f37af5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,11 +4,12 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.compose) alias(libs.plugins.dagger.hilt) id("org.jetbrains.kotlin.kapt") alias(libs.plugins.navigationSafeArgs) - id("com.google.gms.google-services") - id("com.google.firebase.crashlytics") + alias(libs.plugins.googleServices) + alias(libs.plugins.firebaseCrashlytics) } val properties = Properties().apply { @@ -78,7 +79,6 @@ android { kotlinOptions { jvmTarget = "11" } - composeOptions { kotlinCompilerExtensionVersion = "1.5.14" } } dependencies { @@ -164,8 +164,8 @@ dependencies { implementation(libs.accompanist.permissions) // Firebase - implementation(platform("com.google.firebase:firebase-bom:33.4.0")) - implementation("com.google.firebase:firebase-analytics-ktx") - implementation("com.google.firebase:firebase-config-ktx") - implementation("com.google.firebase:firebase-crashlytics-ndk") + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.analytics.ktx) + implementation(libs.firebase.config.ktx) + implementation(libs.firebase.crashlytics) } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 11f54c0d..ebcb5dfd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,6 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.dagger.hilt) apply false alias(libs.plugins.navigationSafeArgs) apply false - id("com.google.gms.google-services") version "4.4.2" apply false - id("com.google.firebase.crashlytics") version "3.0.6" apply false + alias(libs.plugins.googleServices) apply false + alias(libs.plugins.firebaseCrashlytics) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bd319424..0e29bbf2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,11 +3,10 @@ accompanistPermissions = "0.37.3" agp = "8.7.3" coilCompose = "2.5.0" composeBom = "2024.04.01" -firebaseBom = "34.4.0" -firebaseBomVersion = "33.4.0" -firebaseConfig = "23.0.1" -googleFirebaseBom = "33.3.0" -kotlin = "1.9.24" +firebaseBom = "34.6.0" +firebaseCrashlytics = "3.0.6" +googleServices = "4.4.2" +kotlin = "2.0.21" coreKtx = "1.15.0" junit = "4.13.2" junitVersion = "1.2.1" @@ -37,8 +36,6 @@ playServicesLocation = "21.3.0" cameraCore = "1.4.1" kakao = "2.20.3" material3Android = "1.3.2" -firebaseCommonKtx = "22.0.1" - [libraries] accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" } @@ -51,16 +48,13 @@ androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" } -firebase-analytics = { module = "com.google.firebase:firebase-analytics" } -firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics-ktx" } + +# Firebase firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } -firebase-bom-v3330 = { module = "com.google.firebase:firebase-bom", version.ref = "googleFirebaseBom" } -firebase-bom-v3340 = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBomVersion" } -firebase-config = { module = "com.google.firebase:firebase-config" } -firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx" } -google-firebase-analytics = { module = "com.google.firebase:firebase-analytics" } -google-firebase-config = { module = "com.google.firebase:firebase-config", version.ref = "firebaseConfig" } -google-firebase-config-ktx = { module = "com.google.firebase:firebase-config-ktx" } +firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics" } +firebase-config-ktx = { module = "com.google.firebase:firebase-config" } +firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics"} + junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -101,14 +95,16 @@ kakao-friend = { group = "com.kakao.sdk", name = "v2-friend", version.ref = "kak kakao-navi = { group = "com.kakao.sdk", name = "v2-navi", version.ref = "kakao" } kakao-cert = { group = "com.kakao.sdk", name = "v2-cert", version.ref = "kakao" } androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } -firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtx" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "daggerHilt" } navigationSafeArgs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "navigationFragmentKtx" } +googleServices = { id = "com.google.gms.google-services", version.ref = "googleServices" } +firebaseCrashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlytics" } [bundles] hilt = [ From c015ad251ecb6e47737e78ee287d2a2b28ae4a4c Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:30:36 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[feat]=20#107=20Analytics=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kuit/findu/analytics/AnalyticsEvent.kt | 16 ++++++++++++ .../kuit/findu/analytics/AnalyticsHelper.kt | 5 ++++ .../findu/analytics/AnalyticsHelperImpl.kt | 21 ++++++++++++++++ .../kuit/findu/analytics/AnalyticsModule.kt | 25 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 app/src/main/java/com/kuit/findu/analytics/AnalyticsEvent.kt create mode 100644 app/src/main/java/com/kuit/findu/analytics/AnalyticsHelper.kt create mode 100644 app/src/main/java/com/kuit/findu/analytics/AnalyticsHelperImpl.kt create mode 100644 app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt diff --git a/app/src/main/java/com/kuit/findu/analytics/AnalyticsEvent.kt b/app/src/main/java/com/kuit/findu/analytics/AnalyticsEvent.kt new file mode 100644 index 00000000..bd63666a --- /dev/null +++ b/app/src/main/java/com/kuit/findu/analytics/AnalyticsEvent.kt @@ -0,0 +1,16 @@ +package com.kuit.findu.analytics + +data class AnalyticsEvent( + val type: String, + val extras: List = emptyList(), +) { + data class Param( + val key: String, + val value: String, + ) + + companion object { + const val SCREEN_VIEW = "screen_view" // TYPE + const val SCREEN_NAME = "screen_name" // EXTRA_KEY + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/findu/analytics/AnalyticsHelper.kt b/app/src/main/java/com/kuit/findu/analytics/AnalyticsHelper.kt new file mode 100644 index 00000000..69716ff2 --- /dev/null +++ b/app/src/main/java/com/kuit/findu/analytics/AnalyticsHelper.kt @@ -0,0 +1,5 @@ +package com.kuit.findu.analytics + +interface AnalyticsHelper { + fun logEvent(event: AnalyticsEvent) +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/findu/analytics/AnalyticsHelperImpl.kt b/app/src/main/java/com/kuit/findu/analytics/AnalyticsHelperImpl.kt new file mode 100644 index 00000000..f79af806 --- /dev/null +++ b/app/src/main/java/com/kuit/findu/analytics/AnalyticsHelperImpl.kt @@ -0,0 +1,21 @@ +package com.kuit.findu.analytics + +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.logEvent +import javax.inject.Inject + +class AnalyticsHelperImpl @Inject constructor( + private val firebaseAnalytics: FirebaseAnalytics, +) : AnalyticsHelper { + override fun logEvent(event: AnalyticsEvent) { + firebaseAnalytics.logEvent(event.type) { + for (extra in event.extras) { + // Key, Value Max Length에 따른 slicing + param( + key = extra.key.take(40), + value = extra.value.take(100), + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt b/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt new file mode 100644 index 00000000..4eae2d30 --- /dev/null +++ b/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt @@ -0,0 +1,25 @@ +package com.kuit.findu.analytics + +import com.google.firebase.Firebase +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.analytics +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import jakarta.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class AnalyticsModule { + @Binds + @Singleton + abstract fun bindsAnalyticsHelper(analyticsHelperImpl: AnalyticsHelperImpl): AnalyticsHelper + + @Provides + @Singleton + fun provideFirebaseAnalytics(): FirebaseAnalytics { + return Firebase.analytics + } +} \ No newline at end of file From 85eecfcc64dbb6ac39a104c5e7f269d1e404a3a3 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:30:54 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[feat]=20#107=20ScreenView=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/kuit/findu/analytics/AnalyticsExt.kt | 13 +++++++++++++ .../findu/presentation/ui/main/MainActivity.kt | 18 +++++++++++++----- .../ui/report/WitnessReportFragment.kt | 2 +- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt diff --git a/app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt b/app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt new file mode 100644 index 00000000..89f71728 --- /dev/null +++ b/app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt @@ -0,0 +1,13 @@ +package com.kuit.findu.analytics + +fun AnalyticsHelper.logScreenView(screenName: String) { + logEvent( + AnalyticsEvent( + type = AnalyticsEvent.SCREEN_VIEW, + extras = listOf( + AnalyticsEvent.Param(AnalyticsEvent.SCREEN_NAME, screenName), + ), + ), + ) +} + diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/main/MainActivity.kt b/app/src/main/java/com/kuit/findu/presentation/ui/main/MainActivity.kt index 4581d6f3..f1cc619f 100644 --- a/app/src/main/java/com/kuit/findu/presentation/ui/main/MainActivity.kt +++ b/app/src/main/java/com/kuit/findu/presentation/ui/main/MainActivity.kt @@ -7,22 +7,22 @@ import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import com.kuit.findu.R +import com.kuit.findu.analytics.AnalyticsHelper +import com.kuit.findu.analytics.logScreenView import com.kuit.findu.databinding.ActivityMainBinding import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding - + @Inject + lateinit var analyticsHelper: AnalyticsHelper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - - - binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) @@ -39,6 +39,14 @@ class MainActivity : AppCompatActivity() { private fun setBottomNaviVisible(navController: NavController) { navController.addOnDestinationChangedListener { _, destination, _ -> + val screenName = try { + resources.getResourceEntryName(destination.id) + } catch (_: Exception) { + "unknown_screen" + } + + analyticsHelper.logScreenView(screenName) + binding.bnvMain.visibility = when (destination.id) { R.id.fragment_home, R.id.fragment_search, R.id.fragment_info, R.id.fragment_my -> View.VISIBLE R.id.fragment_search_detail_witness, R.id.fragment_search_detail_disappear, R.id.fragment_search_detail_protecting -> View.VISIBLE diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/report/WitnessReportFragment.kt b/app/src/main/java/com/kuit/findu/presentation/ui/report/WitnessReportFragment.kt index 629549e5..45a17d99 100644 --- a/app/src/main/java/com/kuit/findu/presentation/ui/report/WitnessReportFragment.kt +++ b/app/src/main/java/com/kuit/findu/presentation/ui/report/WitnessReportFragment.kt @@ -59,7 +59,7 @@ class WitnessReportFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == AppCompatActivity.RESULT_OK) { val data = result.data?.getStringExtra(ReportLocationDialog.Companion.POST_TAG) From 7da7aff5bbd2cf6c24a5dd88c9af8a53c7c3a189 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:53:22 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[refactor]=20#107=20Provides=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20companion=20object=20=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kuit/findu/analytics/AnalyticsModule.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt b/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt index 4eae2d30..bc16563c 100644 --- a/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt +++ b/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt @@ -8,7 +8,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import jakarta.inject.Singleton +import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) @@ -17,9 +17,11 @@ abstract class AnalyticsModule { @Singleton abstract fun bindsAnalyticsHelper(analyticsHelperImpl: AnalyticsHelperImpl): AnalyticsHelper - @Provides - @Singleton - fun provideFirebaseAnalytics(): FirebaseAnalytics { - return Firebase.analytics + companion object { + @Provides + @Singleton + fun provideFirebaseAnalytics(): FirebaseAnalytics { + return Firebase.analytics + } } } \ No newline at end of file From 959a602692a1b359729c3f4b4bf2c214320b48c6 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:58:40 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[feat]=20#107=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=A0=9C=EB=B3=B4=ED=95=98?= =?UTF-8?q?=EA=B8=B0=EB=A1=9C=20=EC=9D=B4=EB=8F=99=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/ui/home/HomeFragment.kt | 50 +++++++++--- .../ui/home/viewmodel/HomeViewModel.kt | 81 ++++++++++++------- 2 files changed, 95 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/HomeFragment.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/HomeFragment.kt index 93661b62..a5f2d68a 100644 --- a/app/src/main/java/com/kuit/findu/presentation/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/HomeFragment.kt @@ -36,7 +36,7 @@ class HomeFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { _binding = FragmentHomeBinding.inflate(inflater, container, false) return binding.root @@ -76,11 +76,22 @@ class HomeFragment : Fragment() { } is HomeUiEffect.ShowToast -> { - Toast.makeText(requireContext(), sideEffect.message, Toast.LENGTH_SHORT).show() + Toast.makeText( + requireContext(), + sideEffect.message, + Toast.LENGTH_SHORT + ).show() } - is HomeUiEffect.NavigateToProtectList -> TODO() - is HomeUiEffect.NavigateToReportList -> TODO() + is HomeUiEffect.NavigateToProtectList -> {} + is HomeUiEffect.NavigateToReportList -> {} + + is HomeUiEffect.NavigateToFindReport -> { + navigateToFindReport() + } + is HomeUiEffect.NavigateToLostReport -> { + navigateToLostReport() + } is HomeUiEffect.Dial -> call120() } @@ -110,7 +121,11 @@ class HomeFragment : Fragment() { homeViewModel.handleEvent(HomeUiEvent.OnAlarmButtonClick) }, onIndicatorSelected = { reportDurationType -> - homeViewModel.handleEvent(HomeUiEvent.OnHomeReportDurationClick(reportDurationType)) + homeViewModel.handleEvent( + HomeUiEvent.OnHomeReportDurationClick( + reportDurationType + ) + ) }, userNickname = uiState.nickname, navigateToProtectDetail = { protectAnimal -> @@ -128,13 +143,18 @@ class HomeFragment : Fragment() { onReportDialogDismiss = { homeViewModel.handleEvent(HomeUiEvent.OnReportDialogDismiss) }, - onLostReportClick = {}, - onFindReportClick = {}, + onLostReportClick = { + homeViewModel.navigateToLostReport() + }, + onFindReportClick = { + homeViewModel.navigateToFindReport() + }, onPhoneClicked = { homeViewModel.dial() }, - navigateToHomeExtra = { homeExtraButtonType-> - navigateToHomeExtra(homeExtraButtonType) }, + navigateToHomeExtra = { homeExtraButtonType -> + navigateToHomeExtra(homeExtraButtonType) + }, ) } @@ -192,6 +212,18 @@ class HomeFragment : Fragment() { } } + private fun navigateToLostReport() { + findNavController().navigate( + HomeFragmentDirections.actionFragmentHomeToFragmentMissingReport() + ) + } + + private fun navigateToFindReport() { + findNavController().navigate( + HomeFragmentDirections.actionFragmentHomeToFragmentWitnessReport() + ) + } + private fun openWebLink(url: String) { val intent = Intent(Intent.ACTION_VIEW, url.toUri()) requireActivity().startActivity(intent) diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt index 2b74cffe..47c5f3c9 100644 --- a/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/kuit/findu/presentation/ui/home/viewmodel/HomeViewModel.kt @@ -32,12 +32,16 @@ data class HomeUiState( val bannerCurrentPage: Int = 0, val isScrollToTopVisible: Boolean = false, val isReportDialogVisible: Boolean = false, - val locationPermission: Boolean = false + val locationPermission: Boolean = false, ) { val userHomeUserStatusType: HomeUserStatusType get() = when { !locationPermission -> HomeUserStatusType.LOCATION_DENIED - nickname.isEmpty() || nickname.equals(GUEST_NAME, ignoreCase = false) -> HomeUserStatusType.GUEST + nickname.isEmpty() || nickname.equals( + GUEST_NAME, + ignoreCase = false + ) -> HomeUserStatusType.GUEST + else -> HomeUserStatusType.MEMBER } } @@ -68,6 +72,8 @@ sealed class HomeUiEffect { data object NavigateToReportList : HomeUiEffect() data class NavigateToProtectDetail(val animal: ProtectAnimal) : HomeUiEffect() data class NavigateToReportDetail(val animal: ReportAnimal) : HomeUiEffect() + data object NavigateToLostReport : HomeUiEffect() + data object NavigateToFindReport : HomeUiEffect() data class OpenWebLink(val url: String) : HomeUiEffect() data class ShowToast(val message: String) : HomeUiEffect() @@ -77,7 +83,7 @@ sealed class HomeUiEffect { @HiltViewModel class HomeViewModel @Inject constructor( private val homeUseCase: GetHomeUseCase, - private val getNicknameUseCase: GetNicknameUseCase + private val getNicknameUseCase: GetNicknameUseCase, ) : ViewModel() { private val _uiState = MutableStateFlow(HomeUiState()) @@ -125,17 +131,21 @@ class HomeViewModel @Inject constructor( _uiState.update { it.copy(loadState = LoadState.Loading) } homeUseCase().fold( onSuccess = { data -> - _uiState.update { it.copy( - loadState = LoadState.Success, - homeData = data, - errorMessage = null - ) } + _uiState.update { + it.copy( + loadState = LoadState.Success, + homeData = data, + errorMessage = null + ) + } }, onFailure = { error -> - _uiState.update { it.copy( - loadState = LoadState.Error, - errorMessage = error.message ?: "데이터를 불러오는 중 오류가 발생했습니다." - ) } + _uiState.update { + it.copy( + loadState = LoadState.Error, + errorMessage = error.message ?: "데이터를 불러오는 중 오류가 발생했습니다." + ) + } } ) } @@ -154,29 +164,35 @@ class HomeViewModel @Inject constructor( homeUseCase().fold( onSuccess = { data -> - _uiState.update { it.copy( - loadState = LoadState.Success, - homeData = data, - errorMessage = null, - isRefreshing = false - ) } + _uiState.update { + it.copy( + loadState = LoadState.Success, + homeData = data, + errorMessage = null, + isRefreshing = false + ) + } }, onFailure = { error -> - _uiState.update { it.copy( - loadState = LoadState.Error, - errorMessage = error.message ?: "데이터를 새로고침하는 중 오류가 발생했습니다.", - isRefreshing = false - ) } + _uiState.update { + it.copy( + loadState = LoadState.Error, + errorMessage = error.message ?: "데이터를 새로고침하는 중 오류가 발생했습니다.", + isRefreshing = false + ) + } } ) } } private fun clearError() { - _uiState.update { it.copy( - errorMessage = null, - loadState = if (_uiState.value.homeData != null) LoadState.Success else LoadState.Idle - ) } + _uiState.update { + it.copy( + errorMessage = null, + loadState = if (_uiState.value.homeData != null) LoadState.Success else LoadState.Idle + ) + } } @@ -215,6 +231,17 @@ class HomeViewModel @Inject constructor( } } + fun navigateToLostReport() { + viewModelScope.launch { + _uiEffect.send(HomeUiEffect.NavigateToLostReport) + } + } + + fun navigateToFindReport() { + viewModelScope.launch { + _uiEffect.send(HomeUiEffect.NavigateToFindReport) + } + } fun dial() { viewModelScope.launch { From c87f1bb0cf296a223b705fb2c2e87126e671c329 Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 21:04:38 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[feat]=20#107=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=A1=9C=EA=B9=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/kuit/findu/analytics/AnalyticsExt.kt | 23 +++++++++++++++++++ .../ui/login/viewmodel/LoginViewModel.kt | 11 ++++++--- .../viewmodel/OnboardingViewModel.kt | 4 ++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt b/app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt index 89f71728..5c03728f 100644 --- a/app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt +++ b/app/src/main/java/com/kuit/findu/analytics/AnalyticsExt.kt @@ -11,3 +11,26 @@ fun AnalyticsHelper.logScreenView(screenName: String) { ) } +fun AnalyticsHelper.logUserSignIn(userName: String, type: String) { + logEvent( + AnalyticsEvent( + type = "signed_in", + extras = listOf( + AnalyticsEvent.Param("user_name", userName), + AnalyticsEvent.Param("login_type", type), + ), + ), + ) +} + +fun AnalyticsHelper.logUserSignUp(userName: String) { + logEvent( + AnalyticsEvent( + type = "signed_in", + extras = listOf( + AnalyticsEvent.Param("user_name", userName), + AnalyticsEvent.Param("login_type", "Kakao"), + ), + ), + ) +} diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/login/viewmodel/LoginViewModel.kt b/app/src/main/java/com/kuit/findu/presentation/ui/login/viewmodel/LoginViewModel.kt index b7f70f43..b66b9233 100644 --- a/app/src/main/java/com/kuit/findu/presentation/ui/login/viewmodel/LoginViewModel.kt +++ b/app/src/main/java/com/kuit/findu/presentation/ui/login/viewmodel/LoginViewModel.kt @@ -3,6 +3,8 @@ package com.kuit.findu.presentation.ui.login.viewmodel import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.kuit.findu.analytics.AnalyticsHelper +import com.kuit.findu.analytics.logUserSignIn import com.kuit.findu.domain.usecase.SetNicknameUseCase import com.kuit.findu.domain.usecase.auth.PostGuestLoginUseCase import com.kuit.findu.domain.usecase.auth.PostLoginUseCase @@ -19,7 +21,8 @@ class LoginViewModel @Inject constructor( private val loginUseCase: PostLoginUseCase, private val guestLoginUseCase: PostGuestLoginUseCase, private val setAccessTokenUseCase: SetAccessTokenUseCase, - private val setNicknameUseCase: SetNicknameUseCase + private val setNicknameUseCase: SetNicknameUseCase, + private val analyticsHelper: AnalyticsHelper, ) : ViewModel() { private val _startMainActivity = MutableSharedFlow() val startMainActivity: SharedFlow = _startMainActivity @@ -34,6 +37,9 @@ class LoginViewModel @Inject constructor( if (loginData.isFirstLogin) { startOnboardingActivity(kakaoId = kakaoId) } else { + analyticsHelper.logUserSignIn( + userName = loginData.userInfo?.nickname ?: "Null Nickname", type = "kakao" + ) setAccessTokenUseCase(accessToken = loginData.userInfo!!.accessToken) setNicknameUseCase(nickname = loginData.userInfo.nickname) startMainActivity() @@ -44,11 +50,11 @@ class LoginViewModel @Inject constructor( } } - fun postGuestLogin(onSuccess: () -> Unit) { viewModelScope.launch { guestLoginUseCase.postGuestLogin() .onSuccess { loginData -> + analyticsHelper.logUserSignIn(userName = GUEST_NAME, type = "Kakao") setAccessTokenUseCase(accessToken = loginData.accessToken) setNicknameUseCase(nickname = GUEST_NAME) onSuccess() @@ -61,7 +67,6 @@ class LoginViewModel @Inject constructor( } - private fun startMainActivity() { viewModelScope.launch { _startMainActivity.emit(Unit) diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/onboarding/viewmodel/OnboardingViewModel.kt b/app/src/main/java/com/kuit/findu/presentation/ui/onboarding/viewmodel/OnboardingViewModel.kt index b3a0e52a..779f3b49 100644 --- a/app/src/main/java/com/kuit/findu/presentation/ui/onboarding/viewmodel/OnboardingViewModel.kt +++ b/app/src/main/java/com/kuit/findu/presentation/ui/onboarding/viewmodel/OnboardingViewModel.kt @@ -5,6 +5,8 @@ import android.net.Uri import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.kuit.findu.analytics.AnalyticsHelper +import com.kuit.findu.analytics.logUserSignUp import com.kuit.findu.domain.usecase.auth.PostCheckNicknameUseCase import com.kuit.findu.domain.usecase.auth.PostSignupUseCase import com.kuit.findu.presentation.type.DefaultProfileType @@ -35,6 +37,7 @@ class OnboardingViewModel @Inject constructor( @ApplicationContext private val context: Context, private val postCheckNicknameUseCase: PostCheckNicknameUseCase, private val postSignupUseCase: PostSignupUseCase, + private val analyticsHelper: AnalyticsHelper, ) : ViewModel() { private val _uiState = MutableStateFlow(OnboardingUiState()) val uiState: StateFlow = _uiState.asStateFlow() @@ -120,6 +123,7 @@ class OnboardingViewModel @Inject constructor( nickname = uiState.value.nickname, kakaoId = uiState.value.kakaoId ).onSuccess { + analyticsHelper.logUserSignUp(userName = uiState.value.nickname) startMainActivity() }.onFailure { e -> Log.d("http", "Error Message: : $e") From 8560e538f8c0c72fc5cba03d139f1bf1f95e9a6b Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 22:07:11 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[feat]=20#107=20=ED=8C=8C=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=EB=B2=A0=EC=9D=B4=EC=8A=A4=20DI=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{AnalyticsModule.kt => di/FirebaseModule.kt} | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) rename app/src/main/java/com/kuit/findu/analytics/{AnalyticsModule.kt => di/FirebaseModule.kt} (60%) diff --git a/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt b/app/src/main/java/com/kuit/findu/analytics/di/FirebaseModule.kt similarity index 60% rename from app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt rename to app/src/main/java/com/kuit/findu/analytics/di/FirebaseModule.kt index bc16563c..30fa006d 100644 --- a/app/src/main/java/com/kuit/findu/analytics/AnalyticsModule.kt +++ b/app/src/main/java/com/kuit/findu/analytics/di/FirebaseModule.kt @@ -1,8 +1,12 @@ -package com.kuit.findu.analytics +package com.kuit.findu.analytics.di import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.analytics +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.google.firebase.crashlytics.crashlytics +import com.kuit.findu.analytics.AnalyticsHelper +import com.kuit.findu.analytics.AnalyticsHelperImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -12,7 +16,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -abstract class AnalyticsModule { +abstract class FirebaseModule { @Binds @Singleton abstract fun bindsAnalyticsHelper(analyticsHelperImpl: AnalyticsHelperImpl): AnalyticsHelper @@ -23,5 +27,11 @@ abstract class AnalyticsModule { fun provideFirebaseAnalytics(): FirebaseAnalytics { return Firebase.analytics } + + @Provides + @Singleton + fun provideFirebaseCrashlytics(): FirebaseCrashlytics { + return Firebase.crashlytics + } } } \ No newline at end of file From 90c229e336a770ca994fe9cb54d84f46f09c768d Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 22:07:21 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[feat]=20#107=20Api=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EB=A1=9C=EA=B9=85=EC=9A=A9=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/ErrorTrackingInterceptor.kt | 45 +++++++++++++++++++ .../java/com/kuit/findu/di/NetworkModule.kt | 20 +++++++-- 2 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/kuit/findu/data/dataremote/util/ErrorTrackingInterceptor.kt diff --git a/app/src/main/java/com/kuit/findu/data/dataremote/util/ErrorTrackingInterceptor.kt b/app/src/main/java/com/kuit/findu/data/dataremote/util/ErrorTrackingInterceptor.kt new file mode 100644 index 00000000..8550122f --- /dev/null +++ b/app/src/main/java/com/kuit/findu/data/dataremote/util/ErrorTrackingInterceptor.kt @@ -0,0 +1,45 @@ +package com.kuit.findu.data.dataremote.util + +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.google.firebase.crashlytics.recordException +import okhttp3.Interceptor +import okhttp3.Response +import javax.inject.Inject + +class ErrorTrackingInterceptor @Inject constructor( + private val firebaseCrashlytics: FirebaseCrashlytics, +) : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val response: Response + + try { + response = chain.proceed(request) + } catch (e: Exception) { + // 1. 네트워크 자체가 끊긴 경우 등 (IOException) + firebaseCrashlytics.recordException(e) + throw e + } + + // 2. 서버에서 응답은 왔으나 4xx, 5xx 에러인 경우 + if (!response.isSuccessful) { + val code = response.code + + val url = request.url.toString() + + // Crashlytics에 상세 정보 기록 + val exceptionMessage = when (response.code) { + in (400..499) -> "Client $code Error" + in (500..599) -> "Server $code Error" + else -> "Unknown $code Error" + } + firebaseCrashlytics.recordException(Exception(exceptionMessage)) { + key("api_method", request.method) + key("api_url", url) + key("api_status", code) + } + } + + return response + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kuit/findu/di/NetworkModule.kt b/app/src/main/java/com/kuit/findu/di/NetworkModule.kt index 76021961..034b54f6 100644 --- a/app/src/main/java/com/kuit/findu/di/NetworkModule.kt +++ b/app/src/main/java/com/kuit/findu/di/NetworkModule.kt @@ -1,11 +1,13 @@ package com.kuit.findu.di import android.content.Context +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.kuit.findu.BuildConfig import com.kuit.findu.BuildConfig.DEBUG import com.kuit.findu.data.datalocal.datasource.TokenLocalDataSource import com.kuit.findu.data.dataremote.util.AuthInterceptor -import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.kuit.findu.data.dataremote.util.ErrorTrackingInterceptor import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -39,13 +41,15 @@ object NetworkModule { @Singleton fun providesOkHttpClient( loggingInterceptor: HttpLoggingInterceptor, - authInterceptor: AuthInterceptor + authInterceptor: AuthInterceptor, + errorTrackingInterceptor: ErrorTrackingInterceptor, ): OkHttpClient = OkHttpClient.Builder().apply { connectTimeout(10, TimeUnit.SECONDS) writeTimeout(10, TimeUnit.SECONDS) readTimeout(10, TimeUnit.SECONDS) if (DEBUG) addInterceptor(loggingInterceptor) + else addInterceptor(errorTrackingInterceptor) addInterceptor(authInterceptor) }.build() @@ -60,17 +64,25 @@ object NetworkModule { @Singleton fun provideAuthInterceptor( tokenLocalDataSource: TokenLocalDataSource, - @ApplicationContext context: Context + @ApplicationContext context: Context, ): AuthInterceptor { return AuthInterceptor(tokenLocalDataSource, context) } + @Provides + @Singleton + fun provideErrorTrackingInterceptor( + firebaseCrashlytics: FirebaseCrashlytics, + ): ErrorTrackingInterceptor { + return ErrorTrackingInterceptor(firebaseCrashlytics) + } + @ExperimentalSerializationApi @Provides @Singleton fun providesRetrofit( okHttpClient: OkHttpClient, - json: Json + json: Json, ): Retrofit = Retrofit.Builder() .baseUrl(BuildConfig.BASE_URL) From 21eaa2c02250c597e912069a1de0731f53ac69fc Mon Sep 17 00:00:00 2001 From: ikseong00 <127182222+ikseong00@users.noreply.github.com> Date: Wed, 26 Nov 2025 22:16:08 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[fix]=20#107=20=EA=B2=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 게스트 로그인 시 로깅 타입을 "Kakao"에서 "Guest"로 수정 --- .../findu/presentation/ui/login/viewmodel/LoginViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/kuit/findu/presentation/ui/login/viewmodel/LoginViewModel.kt b/app/src/main/java/com/kuit/findu/presentation/ui/login/viewmodel/LoginViewModel.kt index b66b9233..8480dd4d 100644 --- a/app/src/main/java/com/kuit/findu/presentation/ui/login/viewmodel/LoginViewModel.kt +++ b/app/src/main/java/com/kuit/findu/presentation/ui/login/viewmodel/LoginViewModel.kt @@ -54,7 +54,7 @@ class LoginViewModel @Inject constructor( viewModelScope.launch { guestLoginUseCase.postGuestLogin() .onSuccess { loginData -> - analyticsHelper.logUserSignIn(userName = GUEST_NAME, type = "Kakao") + analyticsHelper.logUserSignIn(userName = GUEST_NAME, type = "Guest") setAccessTokenUseCase(accessToken = loginData.accessToken) setNicknameUseCase(nickname = GUEST_NAME) onSuccess()