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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ captures
!*.xcworkspace/contents.xcworkspacedata
**/xcshareddata/WorkspaceSettings.xcsettings
node_modules/
Gemfile.lock
Gemfile.lock
.claude
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 5 additions & 2 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.kotlinMultiplatform)
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 {
Expand Down Expand Up @@ -77,4 +78,6 @@ android {

dependencies {
debugImplementation(libs.compose.uiTooling)
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.crashlytics)
}
29 changes: 29 additions & 0 deletions composeApp/google-services.json
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
)
}
Expand All @@ -160,6 +161,7 @@ private fun ProfileContent(
isLoggingOut: Boolean,
onEditClick: () -> Unit,
onLogoutClick: () -> Unit,
onTestCrash: () -> Unit,
) {

LazyColumn(
Expand All @@ -174,7 +176,8 @@ private fun ProfileContent(
profile = profile,
isLoggingOut = isLoggingOut,
onEditClick = onEditClick,
onLogoutClick = onLogoutClick
onLogoutClick = onLogoutClick,
onTestCrash = onTestCrash
)
}

Expand Down Expand Up @@ -232,6 +235,7 @@ private fun ProfileHeader(
isLoggingOut: Boolean,
onEditClick: () -> Unit,
onLogoutClick: () -> Unit,
onTestCrash: () -> Unit,
) {

Column(
Expand Down Expand Up @@ -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)
)
}
)
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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" }
Expand All @@ -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" }
11 changes: 8 additions & 3 deletions iosApp/iosApp/Application/iOSApp.swift
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
27 changes: 27 additions & 0 deletions iosApp/iosApp/Helper/FirebaseAnalyticsServiceImpl.swift
Original file line number Diff line number Diff line change
@@ -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 подключим отдельно
}
}
6 changes: 6 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,19 @@ 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)
}
}
}

dependencies {
"androidMainImplementation"(platform(libs.firebase.bom))
}

android {
namespace = "friends.mobile.shared"
compileSdk = libs.versions.android.compileSdk.get().toInt()
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, String>) {
val bundle = Bundle()
params.forEach { (key, value) -> bundle.putString(key, value) }
analytics.logEvent(name, bundle)
}

override fun logError(throwable: Throwable) {
Firebase.crashlytics.recordException(throwable)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,4 +20,5 @@ actual val platformModule: Module = module {
)
)
}
single<AnalyticsService> { FirebaseAnalyticsServiceImpl() }
}
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package friends.mobile.core.analytics

interface AnalyticsService {
fun logScreenView(screenName: String)
fun logEvent(name: String, params: Map<String, String> = emptyMap())
fun logError(throwable: Throwable)
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
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<State : Any, Action, Event>(
initState: State,
) : CommonViewModel() {
screenName: String? = null,
) : CommonViewModel(), KoinComponent {

private val analyticsService: AnalyticsService by inject()

private val mutableState = MutableStateFlow(initState)
private val mutableAction = MutableSharedFlow<Action>(
replay = 0,
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArchiveViewState, Unit, ArchiveViewAction>(
initState = ArchiveViewState.Loading,
),
KoinComponent {
screenName = AnalyticsEvent.LAUNCH_ARCHIVE,
) {

private val getArchivedEventsUseCase: GetArchivedEventsUseCase by inject()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<LoginViewState, LoginAction, LoginEvent>(
initState = LoginViewState.Content(),
), KoinComponent {
screenName = AnalyticsEvent.LAUNCH_LOGIN,
) {

private val loginUseCase: LoginUseCase by inject()

Expand Down
Loading
Loading