android: add core common module logging crashlytics and feature flags

This commit is contained in:
Codex
2026-03-09 16:04:53 +03:00
parent ef5f866bd0
commit 65e74cffdb
14 changed files with 153 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ plugins {
id("org.jetbrains.kotlin.plugin.serialization") id("org.jetbrains.kotlin.plugin.serialization")
id("com.google.dagger.hilt.android") id("com.google.dagger.hilt.android")
id("com.google.gms.google-services") id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
} }
android { android {
@@ -19,6 +20,10 @@ android {
versionCode = 1 versionCode = 1
versionName = "0.1.0" versionName = "0.1.0"
buildConfigField("String", "API_BASE_URL", "\"https://chat.daemonlord.ru/\"") buildConfigField("String", "API_BASE_URL", "\"https://chat.daemonlord.ru/\"")
buildConfigField("String", "API_VERSION_HEADER", "\"2026-03\"")
buildConfigField("boolean", "FEATURE_ACCOUNT_MANAGEMENT", "true")
buildConfigField("boolean", "FEATURE_TWO_FACTOR", "true")
buildConfigField("boolean", "FEATURE_MEDIA_GALLERY", "true")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@@ -62,6 +67,7 @@ android {
} }
dependencies { dependencies {
implementation(project(":core:common"))
implementation(platform("com.google.firebase:firebase-bom:34.10.0")) implementation(platform("com.google.firebase:firebase-bom:34.10.0"))
implementation("androidx.core:core-ktx:1.15.0") implementation("androidx.core:core-ktx:1.15.0")
@@ -98,6 +104,8 @@ dependencies {
kapt("com.google.dagger:hilt-compiler:2.52") kapt("com.google.dagger:hilt-compiler:2.52")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0") implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
implementation("com.google.firebase:firebase-messaging") implementation("com.google.firebase:firebase-messaging")
implementation("com.google.firebase:firebase-crashlytics")
implementation("com.jakewharton.timber:timber:5.0.1")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")

View File

@@ -5,14 +5,20 @@ import coil.ImageLoader
import coil.ImageLoaderFactory import coil.ImageLoaderFactory
import coil.disk.DiskCache import coil.disk.DiskCache
import coil.memory.MemoryCache import coil.memory.MemoryCache
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import ru.daemonlord.messenger.core.notifications.NotificationChannels import ru.daemonlord.messenger.core.notifications.NotificationChannels
import java.io.File import java.io.File
import timber.log.Timber
@HiltAndroidApp @HiltAndroidApp
class MessengerApplication : Application(), ImageLoaderFactory { class MessengerApplication : Application(), ImageLoaderFactory {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
NotificationChannels.ensureCreated(this) NotificationChannels.ensureCreated(this)
} }

View File

@@ -0,0 +1,38 @@
package ru.daemonlord.messenger.core.logging
import com.google.firebase.crashlytics.FirebaseCrashlytics
import javax.inject.Inject
import javax.inject.Singleton
import timber.log.Timber
@Singleton
class TimberAppLogger @Inject constructor(
private val crashlytics: FirebaseCrashlytics,
) : AppLogger {
override fun d(tag: String, message: String) {
Timber.tag(tag).d(message)
}
override fun i(tag: String, message: String) {
Timber.tag(tag).i(message)
}
override fun w(tag: String, message: String, throwable: Throwable?) {
if (throwable != null) {
Timber.tag(tag).w(throwable, message)
crashlytics.recordException(throwable)
} else {
Timber.tag(tag).w(message)
}
}
override fun e(tag: String, message: String, throwable: Throwable?) {
if (throwable != null) {
Timber.tag(tag).e(throwable, message)
crashlytics.recordException(throwable)
} else {
Timber.tag(tag).e(message)
}
}
}

View File

@@ -0,0 +1,17 @@
package ru.daemonlord.messenger.core.network
import okhttp3.Interceptor
import okhttp3.Response
import ru.daemonlord.messenger.BuildConfig
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ApiVersionInterceptor @Inject constructor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.header("X-Api-Version", BuildConfig.API_VERSION_HEADER)
.build()
return chain.proceed(request)
}
}

View File

@@ -0,0 +1,23 @@
package ru.daemonlord.messenger.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import ru.daemonlord.messenger.BuildConfig
import ru.daemonlord.messenger.core.feature.FeatureFlags
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object FeatureFlagsModule {
@Provides
@Singleton
fun provideFeatureFlags(): FeatureFlags {
return FeatureFlags(
accountManagementEnabled = BuildConfig.FEATURE_ACCOUNT_MANAGEMENT,
twoFactorEnabled = BuildConfig.FEATURE_TWO_FACTOR,
mediaGalleryEnabled = BuildConfig.FEATURE_MEDIA_GALLERY,
)
}
}

View File

@@ -0,0 +1,25 @@
package ru.daemonlord.messenger.di
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import ru.daemonlord.messenger.core.logging.AppLogger
import ru.daemonlord.messenger.core.logging.TimberAppLogger
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
abstract class LoggingModule {
@Binds
@Singleton
abstract fun bindAppLogger(logger: TimberAppLogger): AppLogger
companion object {
@Provides
@Singleton
fun provideCrashlytics(): FirebaseCrashlytics = FirebaseCrashlytics.getInstance()
}
}

View File

@@ -13,11 +13,13 @@ import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import ru.daemonlord.messenger.BuildConfig import ru.daemonlord.messenger.BuildConfig
import ru.daemonlord.messenger.core.network.AuthHeaderInterceptor import ru.daemonlord.messenger.core.network.AuthHeaderInterceptor
import ru.daemonlord.messenger.core.network.ApiVersionInterceptor
import ru.daemonlord.messenger.core.network.TokenRefreshAuthenticator import ru.daemonlord.messenger.core.network.TokenRefreshAuthenticator
import ru.daemonlord.messenger.data.auth.api.AuthApiService import ru.daemonlord.messenger.data.auth.api.AuthApiService
import ru.daemonlord.messenger.data.chat.api.ChatApiService import ru.daemonlord.messenger.data.chat.api.ChatApiService
import ru.daemonlord.messenger.data.media.api.MediaApiService import ru.daemonlord.messenger.data.media.api.MediaApiService
import ru.daemonlord.messenger.data.message.api.MessageApiService import ru.daemonlord.messenger.data.message.api.MessageApiService
import ru.daemonlord.messenger.data.user.api.UserApiService
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Singleton import javax.inject.Singleton
@@ -87,11 +89,13 @@ object NetworkModule {
@Singleton @Singleton
fun provideApiClient( fun provideApiClient(
loggingInterceptor: HttpLoggingInterceptor, loggingInterceptor: HttpLoggingInterceptor,
apiVersionInterceptor: ApiVersionInterceptor,
authHeaderInterceptor: AuthHeaderInterceptor, authHeaderInterceptor: AuthHeaderInterceptor,
tokenRefreshAuthenticator: TokenRefreshAuthenticator, tokenRefreshAuthenticator: TokenRefreshAuthenticator,
): OkHttpClient { ): OkHttpClient {
return OkHttpClient.Builder() return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor) .addInterceptor(loggingInterceptor)
.addInterceptor(apiVersionInterceptor)
.addInterceptor(authHeaderInterceptor) .addInterceptor(authHeaderInterceptor)
.authenticator(tokenRefreshAuthenticator) .authenticator(tokenRefreshAuthenticator)
.connectTimeout(20, TimeUnit.SECONDS) .connectTimeout(20, TimeUnit.SECONDS)
@@ -137,4 +141,10 @@ object NetworkModule {
fun provideMediaApiService(retrofit: Retrofit): MediaApiService { fun provideMediaApiService(retrofit: Retrofit): MediaApiService {
return retrofit.create(MediaApiService::class.java) return retrofit.create(MediaApiService::class.java)
} }
@Provides
@Singleton
fun provideUserApiService(retrofit: Retrofit): UserApiService {
return retrofit.create(UserApiService::class.java)
}
} }

View File

@@ -1,9 +1,12 @@
plugins { plugins {
id("com.android.application") version "8.7.2" apply false id("com.android.application") version "8.7.2" apply false
id("com.android.library") version "8.7.2" apply false
id("org.jetbrains.kotlin.android") version "2.0.21" apply false id("org.jetbrains.kotlin.android") version "2.0.21" apply false
id("org.jetbrains.kotlin.jvm") version "2.0.21" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false
id("com.google.dagger.hilt.android") version "2.52" apply false id("com.google.dagger.hilt.android") version "2.52" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.21" apply false id("org.jetbrains.kotlin.plugin.serialization") version "2.0.21" apply false
id("org.jetbrains.kotlin.kapt") version "2.0.21" apply false id("org.jetbrains.kotlin.kapt") version "2.0.21" apply false
id("com.google.gms.google-services") version "4.4.4" apply false id("com.google.gms.google-services") version "4.4.4" apply false
id("com.google.firebase.crashlytics") version "3.0.3" apply false
} }

View File

@@ -0,0 +1,7 @@
plugins {
id("org.jetbrains.kotlin.jvm")
}
kotlin {
jvmToolchain(17)
}

View File

@@ -0,0 +1,7 @@
package ru.daemonlord.messenger.core.feature
data class FeatureFlags(
val accountManagementEnabled: Boolean,
val twoFactorEnabled: Boolean,
val mediaGalleryEnabled: Boolean,
)

View File

@@ -0,0 +1,8 @@
package ru.daemonlord.messenger.core.logging
interface AppLogger {
fun d(tag: String, message: String)
fun i(tag: String, message: String)
fun w(tag: String, message: String, throwable: Throwable? = null)
fun e(tag: String, message: String, throwable: Throwable? = null)
}

View File

@@ -16,3 +16,4 @@ dependencyResolutionManagement {
rootProject.name = "MessengerAndroid" rootProject.name = "MessengerAndroid"
include(":app") include(":app")
include(":core:common")