diff --git a/.gitignore b/.gitignore index 5a3d072..f3151e5 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ captures !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings node_modules/ -Gemfile.lock \ No newline at end of file +Gemfile.lock +.claude \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index ef6b54e..3cf6cd3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,8 @@ plugins { alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.kotlinxSerialization) apply false alias(libs.plugins.detekt) apply false + alias(libs.plugins.google.services) apply false + alias(libs.plugins.firebase.crashlytics) apply false } val detektVersion: String? = libs.versions.detekt.get() diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 002bb34..74d5930 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { @@ -6,7 +5,9 @@ plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) - alias(libs.plugins.kotlinxSerialization) // Используем alias из libs.versions.toml + alias(libs.plugins.kotlinxSerialization) + alias(libs.plugins.google.services) + alias(libs.plugins.firebase.crashlytics) } kotlin { @@ -77,4 +78,6 @@ android { dependencies { debugImplementation(libs.compose.uiTooling) + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.crashlytics) } diff --git a/composeApp/google-services.json b/composeApp/google-services.json new file mode 100644 index 0000000..d8a7567 --- /dev/null +++ b/composeApp/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "220601164196", + "project_id": "friends-meet-plan", + "storage_bucket": "friends-meet-plan.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:220601164196:android:c3cbfdaa1f763ecf946131", + "android_client_info": { + "package_name": "friends.mobile" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCoJc66r9bptA8cze2U07ZsxVbaqt6FOsY" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/friends/mobile/profile/ProfileScreen.kt b/composeApp/src/androidMain/kotlin/friends/mobile/profile/ProfileScreen.kt index 8ebdfed..c45112d 100644 --- a/composeApp/src/androidMain/kotlin/friends/mobile/profile/ProfileScreen.kt +++ b/composeApp/src/androidMain/kotlin/friends/mobile/profile/ProfileScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator @@ -137,15 +138,15 @@ fun ProfileScreen( isLoggingOut = current.isLoggingOut, onEditClick = { - viewModel.obtainEvent( - ProfileEvent.OnEditClick - ) + viewModel.obtainEvent(ProfileEvent.OnEditClick) }, onLogoutClick = { - viewModel.obtainEvent( - ProfileEvent.OnLogoutClick - ) + viewModel.obtainEvent(ProfileEvent.OnLogoutClick) + }, + + onTestCrash = { + throw IllegalArgumentException("jj") } ) } @@ -160,6 +161,7 @@ private fun ProfileContent( isLoggingOut: Boolean, onEditClick: () -> Unit, onLogoutClick: () -> Unit, + onTestCrash: () -> Unit, ) { LazyColumn( @@ -174,7 +176,8 @@ private fun ProfileContent( profile = profile, isLoggingOut = isLoggingOut, onEditClick = onEditClick, - onLogoutClick = onLogoutClick + onLogoutClick = onLogoutClick, + onTestCrash = onTestCrash ) } @@ -232,6 +235,7 @@ private fun ProfileHeader( isLoggingOut: Boolean, onEditClick: () -> Unit, onLogoutClick: () -> Unit, + onTestCrash: () -> Unit, ) { Column( @@ -272,6 +276,19 @@ private fun ProfileHeader( isLoading = isLoggingOut, isEnabled = !isLoggingOut ) + + PrimaryButton( + onClick = onTestCrash, + text = "Test Crash", + modifier = Modifier.fillMaxWidth(), + icon = { + Icon( + imageVector = Icons.Default.BugReport, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + } + ) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0fad7e1..ee165c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,8 @@ [versions] agp = "8.11.2" +google-services = "4.4.2" +firebase-crashlytics-gradle = "3.0.3" +firebase-bom = "33.15.0" detekt = "1.23.7" ktor = "3.0.3" kotlinx-serialization = "1.7.3" @@ -60,6 +63,9 @@ compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-previ androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } compose-material-icons-extended = { module = "org.jetbrains.compose.material:material-icons-extended", version.ref = "composeIcons" } accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanist" } +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" } +firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" } +firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } @@ -69,3 +75,5 @@ composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "k kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +google-services = { id = "com.google.gms.google-services", version.ref = "google-services" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-gradle" } diff --git a/iosApp/iosApp/Application/iOSApp.swift b/iosApp/iosApp/Application/iOSApp.swift index f62978b..a30fe76 100644 --- a/iosApp/iosApp/Application/iOSApp.swift +++ b/iosApp/iosApp/Application/iOSApp.swift @@ -1,13 +1,18 @@ import SwiftUI import Shared +import FirebaseCore +import FirebaseCrashlytics @main struct iOSApp: App { - + init() { - IOSKoinInitializerKt.doInitKoinIOS() + FirebaseApp.configure() + IOSKoinInitializerKt.doInitKoinIOS( + analyticsService: FirebaseAnalyticsServiceImpl() + ) } - + var body: some Scene { WindowGroup { RootView() diff --git a/iosApp/iosApp/Helper/FirebaseAnalyticsServiceImpl.swift b/iosApp/iosApp/Helper/FirebaseAnalyticsServiceImpl.swift new file mode 100644 index 0000000..eb0224e --- /dev/null +++ b/iosApp/iosApp/Helper/FirebaseAnalyticsServiceImpl.swift @@ -0,0 +1,27 @@ +// +// FirebaseAnalyticsServiceImpl.swift +// iosApp +// + +import Foundation +import Shared +import FirebaseAnalytics + +final class FirebaseAnalyticsServiceImpl: AnalyticsService { + + func logScreenView(screenName: String) { + Analytics.logEvent(AnalyticsEventScreenView, parameters: [ + AnalyticsParameterScreenName: screenName, + ]) + Analytics.logEvent(screenName, parameters: nil) + } + + func logEvent(name: String, params: [String: String]) { + let converted = params as [String: Any] + Analytics.logEvent(name, parameters: converted.isEmpty ? nil : converted) + } + + func logError(throwable: KotlinThrowable) { + // iOS Crashlytics подключим отдельно + } +} diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 9b3387d..2c05a54 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -43,6 +43,8 @@ kotlin { androidMain.dependencies { implementation(libs.ktor.client.okhttp) implementation(libs.koin.android) + implementation(libs.firebase.analytics) + implementation(libs.firebase.crashlytics) } iosMain.dependencies { implementation(libs.ktor.client.darwin) @@ -50,6 +52,10 @@ kotlin { } } +dependencies { + "androidMainImplementation"(platform(libs.firebase.bom)) +} + android { namespace = "friends.mobile.shared" compileSdk = libs.versions.android.compileSdk.get().toInt() diff --git a/shared/src/androidMain/kotlin/friends/mobile/core/analytics/FirebaseAnalyticsServiceImpl.kt b/shared/src/androidMain/kotlin/friends/mobile/core/analytics/FirebaseAnalyticsServiceImpl.kt new file mode 100644 index 0000000..d88ce04 --- /dev/null +++ b/shared/src/androidMain/kotlin/friends/mobile/core/analytics/FirebaseAnalyticsServiceImpl.kt @@ -0,0 +1,32 @@ +package friends.mobile.core.analytics + +import android.os.Bundle +import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.analytics.ktx.analytics +import com.google.firebase.crashlytics.ktx.crashlytics +import com.google.firebase.ktx.Firebase + +class FirebaseAnalyticsServiceImpl : AnalyticsService { + + private val analytics: FirebaseAnalytics = Firebase.analytics + + override fun logScreenView(screenName: String) { + analytics.logEvent( + FirebaseAnalytics.Event.SCREEN_VIEW, + Bundle().apply { + putString(FirebaseAnalytics.Param.SCREEN_NAME, screenName) + }, + ) + analytics.logEvent(screenName, null) + } + + override fun logEvent(name: String, params: Map) { + val bundle = Bundle() + params.forEach { (key, value) -> bundle.putString(key, value) } + analytics.logEvent(name, bundle) + } + + override fun logError(throwable: Throwable) { + Firebase.crashlytics.recordException(throwable) + } +} diff --git a/shared/src/androidMain/kotlin/friends/mobile/core/di/PlatformModule.android.kt b/shared/src/androidMain/kotlin/friends/mobile/core/di/PlatformModule.android.kt index a1db3c9..8b7da73 100644 --- a/shared/src/androidMain/kotlin/friends/mobile/core/di/PlatformModule.android.kt +++ b/shared/src/androidMain/kotlin/friends/mobile/core/di/PlatformModule.android.kt @@ -3,6 +3,8 @@ package friends.mobile.core.di import android.content.Context import com.russhwolf.settings.Settings import com.russhwolf.settings.SharedPreferencesSettings +import friends.mobile.core.analytics.AnalyticsService +import friends.mobile.core.analytics.FirebaseAnalyticsServiceImpl import org.koin.android.ext.koin.androidContext import org.koin.core.module.Module import org.koin.core.qualifier.named @@ -18,4 +20,5 @@ actual val platformModule: Module = module { ) ) } + single { FirebaseAnalyticsServiceImpl() } } diff --git a/shared/src/commonMain/kotlin/friends/mobile/core/analytics/AnalyticsEvent.kt b/shared/src/commonMain/kotlin/friends/mobile/core/analytics/AnalyticsEvent.kt new file mode 100644 index 0000000..532de5f --- /dev/null +++ b/shared/src/commonMain/kotlin/friends/mobile/core/analytics/AnalyticsEvent.kt @@ -0,0 +1,15 @@ +package friends.mobile.core.analytics + +object AnalyticsEvent { + const val LAUNCH_HOME = "launch_home" + const val LAUNCH_FRIENDS = "launch_friends" + const val LAUNCH_FRIEND_PROFILE = "launch_friend_profile" + const val LAUNCH_PROFILE = "launch_profile" + const val LAUNCH_EDIT_PROFILE = "launch_edit_profile" + const val LAUNCH_ARCHIVE = "launch_archive" + const val LAUNCH_EVENT_DETAIL = "launch_event_detail" + const val LAUNCH_CREATE_EVENT = "launch_create_event" + const val LAUNCH_PENDING_EVENTS = "launch_pending_events" + const val LAUNCH_LOGIN = "launch_login" + const val LAUNCH_REGISTER = "launch_register" +} diff --git a/shared/src/commonMain/kotlin/friends/mobile/core/analytics/AnalyticsService.kt b/shared/src/commonMain/kotlin/friends/mobile/core/analytics/AnalyticsService.kt new file mode 100644 index 0000000..acbcbe7 --- /dev/null +++ b/shared/src/commonMain/kotlin/friends/mobile/core/analytics/AnalyticsService.kt @@ -0,0 +1,7 @@ +package friends.mobile.core.analytics + +interface AnalyticsService { + fun logScreenView(screenName: String) + fun logEvent(name: String, params: Map = emptyMap()) + fun logError(throwable: Throwable) +} diff --git a/shared/src/commonMain/kotlin/friends/mobile/core/viewmodel/BaseViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/core/viewmodel/BaseViewModel.kt index d1eb66c..9639083 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/core/viewmodel/BaseViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/core/viewmodel/BaseViewModel.kt @@ -1,15 +1,21 @@ package friends.mobile.core.viewmodel +import friends.mobile.core.analytics.AnalyticsService import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject abstract class BaseViewModel( initState: State, -) : CommonViewModel() { + screenName: String? = null, +) : CommonViewModel(), KoinComponent { + + private val analyticsService: AnalyticsService by inject() private val mutableState = MutableStateFlow(initState) private val mutableAction = MutableSharedFlow( @@ -17,6 +23,14 @@ abstract class BaseViewModel( extraBufferCapacity = 1, ) + init { + screenName?.let { analyticsService.logScreenView(it) } + } + + protected fun logError(throwable: Throwable) { + analyticsService.logError(throwable) + } + protected var viewState: State get() = mutableState.value set(value) { diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/archive/presentation/ArchiveEventsViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/archive/presentation/ArchiveEventsViewModel.kt index deb0c57..a6a8e5b 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/archive/presentation/ArchiveEventsViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/archive/presentation/ArchiveEventsViewModel.kt @@ -3,16 +3,16 @@ package friends.mobile.feature.archive.presentation import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.events.domain.usecase.GetArchivedEventsUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class ArchiveEventsViewModel : BaseViewModel( initState = ArchiveViewState.Loading, -), - KoinComponent { + screenName = AnalyticsEvent.LAUNCH_ARCHIVE, +) { private val getArchivedEventsUseCase: GetArchivedEventsUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/auth/presentation/login/LoginViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/auth/presentation/login/LoginViewModel.kt index 2d45094..52e7a84 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/auth/presentation/login/LoginViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/auth/presentation/login/LoginViewModel.kt @@ -3,15 +3,16 @@ package friends.mobile.feature.auth.presentation.login import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.auth.domain.usecase.LoginUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class LoginViewModel : BaseViewModel( initState = LoginViewState.Content(), -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_LOGIN, +) { private val loginUseCase: LoginUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/auth/presentation/register/RegisterViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/auth/presentation/register/RegisterViewModel.kt index ccc07e6..c10947f 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/auth/presentation/register/RegisterViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/auth/presentation/register/RegisterViewModel.kt @@ -3,15 +3,16 @@ package friends.mobile.feature.auth.presentation.register import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.auth.domain.usecase.RegisterUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class RegisterViewModel : BaseViewModel( initState = RegisterViewState.Content(), -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_REGISTER, +) { private val registerUseCase: RegisterUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/CreateEventViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/CreateEventViewModel.kt index 2281701..7281634 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/CreateEventViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/CreateEventViewModel.kt @@ -4,12 +4,12 @@ import friends.mobile.core.domain.model.ApiError import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.events.domain.usecase.CheckFriendsAvailabilityUseCase import friends.mobile.feature.events.domain.usecase.CheckUserAvailabilityUseCase import friends.mobile.feature.events.domain.usecase.CreateEventUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class CreateEventViewModel( @@ -20,7 +20,8 @@ class CreateEventViewModel( CreateEventEvent, >( initState = CreateEventViewState.Loading, -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_CREATE_EVENT, +) { private val checkFriendsAvailabilityUseCase: CheckFriendsAvailabilityUseCase by inject() private val checkUserAvailabilityUseCase: CheckUserAvailabilityUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/eventdetail/EventDetailViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/eventdetail/EventDetailViewModel.kt index 51052df..212e06e 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/eventdetail/EventDetailViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/eventdetail/EventDetailViewModel.kt @@ -3,18 +3,18 @@ package friends.mobile.feature.events.presentation.eventdetail import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.events.domain.usecase.GetEventDetailUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class EventDetailViewModel( private val eventId: String, ) : BaseViewModel( initState = EventDetailViewState.Loading, -), - KoinComponent { + screenName = AnalyticsEvent.LAUNCH_EVENT_DETAIL, +) { private val getEventDetailUseCase: GetEventDetailUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/pendingevents/PendingEventViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/pendingevents/PendingEventViewModel.kt index 0697bde..3c83634 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/pendingevents/PendingEventViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/events/presentation/pendingevents/PendingEventViewModel.kt @@ -3,18 +3,19 @@ package friends.mobile.feature.events.presentation.pendingevents import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.events.domain.usecase.AcceptEventUseCase import friends.mobile.feature.events.domain.usecase.DeclineEventUseCase import friends.mobile.feature.events.domain.usecase.GetEventDetailUseCase import friends.mobile.feature.events.domain.usecase.GetWaitingEventsUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject -class PendingEventViewModel : BaseViewModel( +class PendingEventViewModel : BaseViewModel( initState = PendingViewState.Loading, -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_PENDING_EVENTS, +) { private val getWaitingEventsUseCase: GetWaitingEventsUseCase by inject() private val getEventDetailUseCase: GetEventDetailUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/friends/presentation/friends/FriendsViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/friends/presentation/friends/FriendsViewModel.kt index 5f9bd4d..11652e4 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/friends/presentation/friends/FriendsViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/friends/presentation/friends/FriendsViewModel.kt @@ -3,19 +3,20 @@ package friends.mobile.feature.friends.presentation.friends import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.friends.domain.usecase.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) class FriendsViewModel : BaseViewModel( initState = FriendsViewState.Loading, -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_FRIENDS, +) { private val getFriendsUseCase: GetFriendsUseCase by inject() private val getIncomingFriendRequestsUseCase: GetIncomingFriendRequestsUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/friends/presentation/friendsProfile/FriendProfileViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/friends/presentation/friendsProfile/FriendProfileViewModel.kt index f94a4a1..91bfc21 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/friends/presentation/friendsProfile/FriendProfileViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/friends/presentation/friendsProfile/FriendProfileViewModel.kt @@ -3,6 +3,7 @@ package friends.mobile.feature.friends.presentation.friendsProfile import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.friends.domain.usecase.AcceptFriendRequestUseCase import friends.mobile.feature.friends.domain.usecase.GetFriendStatusUseCase @@ -10,12 +11,12 @@ import friends.mobile.feature.friends.domain.usecase.RejectFriendRequestUseCase import friends.mobile.feature.friends.domain.usecase.RemoveFriendUseCase import friends.mobile.feature.friends.domain.usecase.SendFriendRequestUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class FriendProfileViewModel : BaseViewModel( initState = FriendProfileViewState.Loading, -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_FRIEND_PROFILE, +) { private val getFriendStatusUseCase: GetFriendStatusUseCase by inject() private val sendFriendRequestUseCase: SendFriendRequestUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/main/presentation/MainViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/main/presentation/MainViewModel.kt index fa5b31f..0a9e361 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/main/presentation/MainViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/main/presentation/MainViewModel.kt @@ -4,12 +4,12 @@ import friends.mobile.core.domain.model.ApiError import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.events.domain.usecase.CheckUserAvailabilityUseCase import friends.mobile.feature.events.domain.usecase.GetAcceptedEventsUseCase import friends.mobile.feature.events.domain.usecase.GetPendingEventsUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class MainViewModel : BaseViewModel< @@ -18,7 +18,8 @@ class MainViewModel : BaseViewModel< MainViewAction, >( initState = MainViewState.Loading, -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_HOME, +) { private val getAcceptedEventsUseCase: GetAcceptedEventsUseCase by inject() @@ -68,33 +69,11 @@ class MainViewModel : BaseViewModel< ) } - is ResultWrapper.Error -> { - val currentState = viewState - val userError = mapApiErrorToUserFriendly(pendingResult.error) - - if (currentState is MainViewState.Content) { - viewState = currentState.copy(isRefreshing = false) - } else { - viewState = MainViewState.Error( - message = getErrorMessage(userError), - ) - } - } + is ResultWrapper.Error -> handleError(pendingResult.error) } } - is ResultWrapper.Error -> { - val currentState = viewState - val userError = mapApiErrorToUserFriendly(activeResult.error) - - if (currentState is MainViewState.Content) { - viewState = currentState.copy(isRefreshing = false) - } else { - viewState = MainViewState.Error( - message = getErrorMessage(userError), - ) - } - } + is ResultWrapper.Error -> handleError(activeResult.error) } } finally { isLoadingInProgress = false @@ -133,6 +112,19 @@ class MainViewModel : BaseViewModel< } } + private fun handleError(error: ApiError) { + logError(RuntimeException("MainViewModel: $error")) + + val userError = mapApiErrorToUserFriendly(error) + val currentState = viewState + + if (currentState is MainViewState.Content) { + viewState = currentState.copy(isRefreshing = false) + } else { + viewState = MainViewState.Error(message = getErrorMessage(userError)) + } + } + private inline fun updateContent( transform: MainViewState.Content.() -> MainViewState.Content, ) { diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/profile/presentation/edit/EditProfileViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/profile/presentation/edit/EditProfileViewModel.kt index 01497ef..0a2c041 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/profile/presentation/edit/EditProfileViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/profile/presentation/edit/EditProfileViewModel.kt @@ -3,15 +3,16 @@ package friends.mobile.feature.profile.presentation.edit import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.profile.domain.usecase.UpdateProfileUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class EditProfileViewModel : BaseViewModel( initState = EditProfileViewState.Loading, -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_EDIT_PROFILE, +) { private val updateProfileUseCase: UpdateProfileUseCase by inject() diff --git a/shared/src/commonMain/kotlin/friends/mobile/feature/profile/presentation/profile/ProfileViewModel.kt b/shared/src/commonMain/kotlin/friends/mobile/feature/profile/presentation/profile/ProfileViewModel.kt index 0293c2a..684df3f 100644 --- a/shared/src/commonMain/kotlin/friends/mobile/feature/profile/presentation/profile/ProfileViewModel.kt +++ b/shared/src/commonMain/kotlin/friends/mobile/feature/profile/presentation/profile/ProfileViewModel.kt @@ -3,15 +3,16 @@ package friends.mobile.feature.profile.presentation.profile import friends.mobile.core.domain.model.ResultWrapper import friends.mobile.core.domain.model.getErrorMessage import friends.mobile.core.domain.model.mapApiErrorToUserFriendly +import friends.mobile.core.analytics.AnalyticsEvent import friends.mobile.core.viewmodel.BaseViewModel import friends.mobile.feature.profile.domain.usecase.GetMeUseCase import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent import org.koin.core.component.inject class ProfileViewModel : BaseViewModel( initState = ProfileViewState.Loading, -), KoinComponent { + screenName = AnalyticsEvent.LAUNCH_PROFILE, +) { private val getMeUseCase: GetMeUseCase by inject() private var isLoadingInProgress = false diff --git a/shared/src/iosMain/kotlin/friends/mobile/di/IOSKoinInitializer.kt b/shared/src/iosMain/kotlin/friends/mobile/di/IOSKoinInitializer.kt index 5dba282..21f4a0e 100644 --- a/shared/src/iosMain/kotlin/friends/mobile/di/IOSKoinInitializer.kt +++ b/shared/src/iosMain/kotlin/friends/mobile/di/IOSKoinInitializer.kt @@ -1,8 +1,15 @@ package friends.mobile.di import friends.mobile.core.CommonKmp +import friends.mobile.core.analytics.AnalyticsService import friends.mobile.core.config.createConfiguration +import org.koin.dsl.module -fun initKoinIOS() = CommonKmp.initKoin( +fun initKoinIOS(analyticsService: AnalyticsService) = CommonKmp.initKoin( configuration = createConfiguration(isDebug = true), + appDeclaration = { + modules(module { + single { analyticsService } + }) + }, ) \ No newline at end of file