From 36504fe4b9f5637060ae54e7ecfe5aad65344419 Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Sat, 30 May 2026 18:31:00 +0300 Subject: [PATCH] refactor(logging): introduce custom FastLogger and replace direct Android logging --- app/build.gradle.kts | 1 + .../kotlin/dev/meloda/fast/MainViewModel.kt | 10 +- .../dev/meloda/fast/common/AppGlobal.kt | 10 ++ .../fast/common/di/ApplicationModule.kt | 3 + .../meloda/fast/presentation/MainActivity.kt | 33 +++--- .../meloda/fast/presentation/RootScreen.kt | 10 +- .../dev/meloda/fast/service/OnlineService.kt | 27 +++-- .../service/longpolling/LongPollingService.kt | 37 +++--- .../model/{LogLevel.kt => NetworkLogLevel.kt} | 4 +- .../dev/meloda/fast/datastore/AppSettings.kt | 6 +- .../fast/domain/LongPollEventsHandler.kt | 50 +++++--- .../fast/domain/LongPollUpdatesParser.kt | 44 +++---- core/logger/.gitignore | 1 + core/logger/build.gradle.kts | 11 ++ .../dev/meloda/fast/logger/FastLogLevel.kt | 17 +++ .../dev/meloda/fast/logger/FastLogger.kt | 108 ++++++++++++++++++ .../dev/meloda/fast/logger/LoggerModule.kt | 8 ++ .../fast/model/api/data/AttachmentType.kt | 6 - core/network/build.gradle.kts | 1 + .../fast/network/ResponseConverterFactory.kt | 14 ++- .../interceptor/Error14HandlingInterceptor.kt | 24 ++-- core/ui/build.gradle.kts | 1 + .../dev/meloda/fast/ui/common/LocalLogger.kt | 6 + .../captcha/presentation/CaptchaScreen.kt | 19 +-- .../meloda/fast/auth/login/LoginViewModel.kt | 7 +- .../chatmaterials/util/ChatMaterialMapper.kt | 6 +- .../MessagesHistoryViewModelImpl.kt | 11 -- .../presentation/MessagesList.kt | 2 - .../presentation/attachments/Previews.kt | 1 - .../meloda/fast/settings/SettingsViewModel.kt | 14 +-- settings.gradle.kts | 1 + 31 files changed, 344 insertions(+), 149 deletions(-) rename core/common/src/main/kotlin/dev/meloda/fast/common/model/{LogLevel.kt => NetworkLogLevel.kt} (60%) create mode 100644 core/logger/.gitignore create mode 100644 core/logger/build.gradle.kts create mode 100644 core/logger/src/main/kotlin/dev/meloda/fast/logger/FastLogLevel.kt create mode 100644 core/logger/src/main/kotlin/dev/meloda/fast/logger/FastLogger.kt create mode 100644 core/logger/src/main/kotlin/dev/meloda/fast/logger/LoggerModule.kt create mode 100644 core/ui/src/main/kotlin/dev/meloda/fast/ui/common/LocalLogger.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6c9661a8..6b085e90 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -92,6 +92,7 @@ dependencies { implementation(projects.feature.photoviewer) implementation(projects.feature.createchat) + implementation(projects.core.logger) implementation(projects.core.common) implementation(projects.core.ui) implementation(projects.core.data) diff --git a/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt b/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt index 7c0a3cc4..72eab7e5 100644 --- a/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt +++ b/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt @@ -2,7 +2,6 @@ package dev.meloda.fast import android.content.Intent import android.os.Build -import android.util.Log import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.NotificationCompat import androidx.core.os.LocaleListCompat @@ -23,6 +22,7 @@ import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.domain.GetCurrentAccountUseCase import dev.meloda.fast.domain.LoadUserByIdUseCase +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.api.domain.VkUser import dev.meloda.fast.navigation.Main @@ -67,7 +67,8 @@ class MainViewModelImpl( private val getCurrentAccountUseCase: GetCurrentAccountUseCase, private val loadUserByIdUseCase: LoadUserByIdUseCase, private val userSettings: UserSettings, - private val longPollController: LongPollController + private val longPollController: LongPollController, + private val logger: FastLogger ) : MainViewModel, ViewModel() { override val startDestination = MutableStateFlow(null) @@ -203,7 +204,10 @@ class MainViewModelImpl( viewModelScope.launch(Dispatchers.IO) { val currentAccount = getCurrentAccountUseCase() - Log.d("MainViewModel", "currentAccount: $currentAccount") + logger.debug( + this@MainViewModelImpl::class, + "loadAccounts(): currentAccount: $currentAccount" + ) listenLongPollState() diff --git a/app/src/main/kotlin/dev/meloda/fast/common/AppGlobal.kt b/app/src/main/kotlin/dev/meloda/fast/common/AppGlobal.kt index 70d5b544..fb9b6c23 100644 --- a/app/src/main/kotlin/dev/meloda/fast/common/AppGlobal.kt +++ b/app/src/main/kotlin/dev/meloda/fast/common/AppGlobal.kt @@ -10,6 +10,8 @@ import com.skydoves.compose.stability.runtime.ComposeStabilityAnalyzer import dev.meloda.fast.auth.BuildConfig import dev.meloda.fast.common.di.applicationModule import dev.meloda.fast.datastore.AppSettings +import dev.meloda.fast.logger.FastLogLevel +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.presentation.CrashActivity import org.koin.android.ext.android.get import org.koin.android.ext.koin.androidContext @@ -30,6 +32,14 @@ class AppGlobal : Application(), ImageLoaderFactory { initKoin() initCrashHandler() + + val logLevel = + if (BuildConfig.DEBUG) FastLogLevel.DEBUG + else FastLogLevel.ERROR + + get() + .apply { setLogLevel(logLevel) } + .also { FastLogger.setInstance(it) } } override fun newImageLoader(): ImageLoader = get() diff --git a/app/src/main/kotlin/dev/meloda/fast/common/di/ApplicationModule.kt b/app/src/main/kotlin/dev/meloda/fast/common/di/ApplicationModule.kt index 99740b4c..296696b4 100644 --- a/app/src/main/kotlin/dev/meloda/fast/common/di/ApplicationModule.kt +++ b/app/src/main/kotlin/dev/meloda/fast/common/di/ApplicationModule.kt @@ -19,6 +19,7 @@ import dev.meloda.fast.convos.di.createChatModule import dev.meloda.fast.domain.di.domainModule import dev.meloda.fast.friends.di.friendsModule import dev.meloda.fast.languagepicker.di.languagePickerModule +import dev.meloda.fast.logger.loggerModule import dev.meloda.fast.messageshistory.di.messagesHistoryModule import dev.meloda.fast.photoviewer.di.photoViewModule import dev.meloda.fast.profile.di.profileModule @@ -49,6 +50,8 @@ val applicationModule = module { createChatModule ) + includes(loggerModule) + // TODO: 14/05/2024, Danil Nikolaev: extract all operations with preferences to standalone class singleOf(PreferenceManager::getDefaultSharedPreferences) single { androidContext().resources } diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt index 15dda193..ba78cfda 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/MainActivity.kt @@ -9,12 +9,11 @@ import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.provider.Settings -import android.util.Log import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.content.ContextCompat @@ -24,10 +23,13 @@ import dev.meloda.fast.MainViewModel import dev.meloda.fast.MainViewModelImpl import dev.meloda.fast.common.AppConstants import dev.meloda.fast.datastore.AppSettings +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.service.OnlineService import dev.meloda.fast.service.longpolling.LongPollingService import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.common.LocalLogger import org.koin.androidx.compose.koinViewModel +import org.koin.compose.koinInject class MainActivity : AppCompatActivity() { @@ -64,25 +66,26 @@ class MainActivity : AppCompatActivity() { requestNotificationPermissions() setContent { - val viewModel: MainViewModel = koinViewModel() - LaunchedEffect(viewModel) { - Log.d("VM_CREATE", "onCreate: viewModel: $viewModel") - } + val logger: FastLogger = koinInject() + val viewModel: MainViewModel = koinViewModel() LifecycleResumeEffect(true) { viewModel.onAppResumed(intent) onPauseOrDispose {} } - RootScreen( - toggleLongPollService = { enable, inBackground -> - toggleLongPollService( - enable = enable, - inBackground = inBackground ?: AppSettings.Experimental.longPollInBackground - ) - }, - toggleOnlineService = ::toggleOnlineService - ) + CompositionLocalProvider(LocalLogger provides logger) { + RootScreen( + toggleLongPollService = { enable, inBackground -> + toggleLongPollService( + enable = enable, + inBackground = inBackground + ?: AppSettings.Experimental.longPollInBackground + ) + }, + toggleOnlineService = ::toggleOnlineService + ) + } } } diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt index c0ef5bc4..bc1be759 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt @@ -4,7 +4,6 @@ import android.Manifest import android.content.Intent import android.net.Uri import android.provider.Settings -import android.util.Log import androidx.activity.compose.LocalActivity import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn @@ -63,6 +62,7 @@ import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog import dev.meloda.fast.settings.navigation.navigateToSettings import dev.meloda.fast.settings.navigation.settingsScreen import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.common.LocalLogger import dev.meloda.fast.ui.common.LocalSizeConfig import dev.meloda.fast.ui.model.DeviceSize import dev.meloda.fast.ui.model.SizeConfig @@ -83,6 +83,7 @@ fun RootScreen( toggleLongPollService: (enable: Boolean, inBackground: Boolean?) -> Unit, toggleOnlineService: (enable: Boolean) -> Unit ) { + val logger = LocalLogger.current val resources = LocalResources.current val userSettings: UserSettings = koinInject() @@ -92,10 +93,6 @@ fun RootScreen( val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle() val viewModel: MainViewModel = koinViewModel() - LaunchedEffect(viewModel) { - Log.d("VM_CREATE", "RootScreen(): viewModel: $viewModel") - } - val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle() val permissionState = @@ -126,13 +123,12 @@ fun RootScreen( } LifecycleResumeEffect(longPollStateToApply) { - Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply") + logger.debug("RootScreen", "longPollStateToApply: $longPollStateToApply") if (longPollStateToApply != LongPollState.Background) { if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched() && longPollCurrentState != longPollStateToApply ) { toggleLongPollService(false, null) - Log.d("LongPoll", "recreate()") } toggleLongPollService( diff --git a/app/src/main/kotlin/dev/meloda/fast/service/OnlineService.kt b/app/src/main/kotlin/dev/meloda/fast/service/OnlineService.kt index 354a1de4..fc956ce1 100644 --- a/app/src/main/kotlin/dev/meloda/fast/service/OnlineService.kt +++ b/app/src/main/kotlin/dev/meloda/fast/service/OnlineService.kt @@ -6,8 +6,9 @@ import android.os.IBinder import android.util.Log import dev.meloda.fast.common.extensions.createTimerFlow import dev.meloda.fast.data.UserConfig -import dev.meloda.fast.domain.AccountUseCase import dev.meloda.fast.data.processState +import dev.meloda.fast.domain.AccountUseCase +import dev.meloda.fast.logger.FastLogger import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -24,11 +25,12 @@ import kotlin.time.Duration.Companion.minutes class OnlineService : Service() { + private val logger: FastLogger by inject() + private val job = SupervisorJob() private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> - Log.d(TAG, "error: $throwable") - throwable.printStackTrace() + logger.error(this::class.java, "CoroutineException", throwable) } private val coroutineContext: CoroutineContext @@ -42,17 +44,20 @@ class OnlineService : Service() { private var onlineJob: Job? = null override fun onBind(intent: Intent?): IBinder? { - Log.d(STATE_TAG, "onBind: intent: $intent") + logger.debug(this::class, "STATE: onBind(): intent: $intent") return null } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (startId > 1) return START_STICKY - Log.d(STATE_TAG, "onStartCommand: flags: $flags; startId: $startId\ninstance: $this") + logger.debug( + this::class, + "STATE: onStartCommand(): flags: %s; startId: %s;\ninstance: %s" + .format("$flags", "$startId", "$this") + ) createTimer() - return START_STICKY } @@ -68,13 +73,13 @@ class OnlineService : Service() { private fun setOnline() { if (onlineJob != null) return - Log.d(TAG, "setOnline()") + logger.debug(this::class, "setOnline()") onlineJob = coroutineScope.launch { val token = UserConfig.fastToken ?: UserConfig.accessToken if (token.isBlank()) { - Log.d(TAG, "setOnline: token is empty") + logger.debug(this::class, "setOnline(): token is empty") return@launch } @@ -84,10 +89,10 @@ class OnlineService : Service() { ).onEach { state -> state.processState( error = { error -> - Log.w(TAG, "setOnline(): error: $error") + logger.error(this@OnlineService::class, "setOnline(): ERROR: $error") }, success = { response -> - Log.d(TAG, "setOnline(): success: $response") + logger.debug(this@OnlineService::class, "setOnline(): response: $response") } ) }.collect() @@ -96,7 +101,7 @@ class OnlineService : Service() { } override fun onDestroy() { - Log.d(STATE_TAG, "onDestroy") + logger.debug(this::class, "onDestroy()") timerJob?.cancel("OnlineService destroyed") onlineJob?.cancel("OnlineService destroyed") diff --git a/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt b/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt index 9d452862..fedb667a 100644 --- a/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt +++ b/app/src/main/kotlin/dev/meloda/fast/service/longpolling/LongPollingService.kt @@ -7,7 +7,6 @@ import android.net.Uri import android.os.Build import android.os.IBinder import android.provider.Settings -import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat import com.conena.nanokt.android.app.stopForegroundCompat @@ -22,6 +21,7 @@ import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.domain.LongPollEventsHandler import dev.meloda.fast.domain.LongPollUpdatesParser import dev.meloda.fast.domain.LongPollUseCase +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.model.api.data.LongPollUpdates import dev.meloda.fast.model.api.data.VkLongPollData import dev.meloda.fast.ui.R @@ -41,6 +41,8 @@ import kotlin.time.Duration.Companion.seconds class LongPollingService : Service() { + private val logger: FastLogger by inject() + private val longPollController: LongPollController by inject() private val job = SupervisorJob() @@ -65,20 +67,21 @@ class LongPollingService : Service() { override fun onCreate() { super.onCreate() - Log.d(STATE_TAG, "onCreate()") + logger.debug(this::class, "STATE: onCreate()") } override fun onBind(intent: Intent?): IBinder? { - Log.d(STATE_TAG, "onBind: intent: $intent") + logger.debug(this::class, "STATE: onBind(): intent: $intent") return null } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (startId > 1) return START_STICKY - Log.d( - STATE_TAG, - "onStartCommand: asForeground: $inBackground; flags: $flags; startId: $startId;\ninstance: $this" + logger.debug( + this::class, + "STATE: onStartCommand(): asForeground: %s; flags: %s; startId: %s;\ninstance: %s" + .format("$inBackground", "$flags", "$startId", "$this") ) startJob() @@ -133,11 +136,15 @@ class LongPollingService : Service() { private fun startPolling(): Job { if (job.isCompleted || job.isCancelled) { - Log.d(STATE_TAG, "Job is completed or cancelled") + logger.debug( + this::class, + "startPolling(): Job is already done. isCompleted: %s; isCancelled: %s" + .format("${job.isCompleted}", "${job.isCancelled}") + ) throw Exception("Job is over") } - Log.d(STATE_TAG, "Starting job...") + logger.debug(this::class, "startPolling(): Starting job.") return coroutineScope.launch(coroutineContext) { longPollController.updateCurrentState( @@ -213,11 +220,11 @@ class LongPollingService : Service() { ).listenValue(coroutineScope) { state -> state.processState( success = { response -> - Log.d(TAG, "getServerInfo: serverInfoResponse: $response") + logger.debug(this::class, "getServerInfo(): response: $response") it.resume(response) }, error = { error -> - Log.e(TAG, "getServerInfo: $error") + logger.error(this::class, "getServerInfo(): ERROR: $error") it.resume(null) } ) @@ -237,11 +244,11 @@ class LongPollingService : Service() { ).listenValue(coroutineScope) { state -> state.processState( success = { response -> - Log.d(TAG, "lastUpdateResponse: $response") + logger.debug(this::class, "getUpdatesResponse(): response: $response") it.resume(response) }, error = { error -> - Log.d(TAG, "getUpdatesResponse: error: $error") + logger.debug(this::class, "getUpdatesResponse(): error: $error") it.resume(null) } ) @@ -254,7 +261,7 @@ class LongPollingService : Service() { } private fun handleError(throwable: Throwable) { - Log.e(TAG, "error: $throwable") + logger.error(this::class, "CoroutineException", throwable) if (throwable !is NoAccessTokenException) { throwable.printStackTrace() @@ -269,7 +276,7 @@ class LongPollingService : Service() { } override fun onDestroy() { - Log.d(STATE_TAG, "onDestroy") + logger.debug(this::class, "STATE: onDestroy()") longPollController.updateCurrentState(LongPollState.Stopped) try { AppSettings.edit { putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true) } @@ -281,7 +288,7 @@ class LongPollingService : Service() { } override fun onTrimMemory(level: Int) { - Log.d(STATE_TAG, "onTrimMemory. Level: $level") + logger.debug(this::class, "STATE: onTrimMemory(): Level: $level") super.onTrimMemory(level) } diff --git a/core/common/src/main/kotlin/dev/meloda/fast/common/model/LogLevel.kt b/core/common/src/main/kotlin/dev/meloda/fast/common/model/NetworkLogLevel.kt similarity index 60% rename from core/common/src/main/kotlin/dev/meloda/fast/common/model/LogLevel.kt rename to core/common/src/main/kotlin/dev/meloda/fast/common/model/NetworkLogLevel.kt index 43b38035..76dc9602 100644 --- a/core/common/src/main/kotlin/dev/meloda/fast/common/model/LogLevel.kt +++ b/core/common/src/main/kotlin/dev/meloda/fast/common/model/NetworkLogLevel.kt @@ -1,13 +1,13 @@ package dev.meloda.fast.common.model -enum class LogLevel(val value: Int) { +enum class NetworkLogLevel(val value: Int) { NONE(0), BASIC(1), HEADERS(2), BODY(3); companion object { - fun parse(value: Int): LogLevel = entries.firstOrNull { it.value == value } + fun parse(value: Int): NetworkLogLevel = entries.firstOrNull { it.value == value } ?: throw IllegalArgumentException("Unknown log level with value: $value") } } diff --git a/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/AppSettings.kt b/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/AppSettings.kt index 9eb39856..07e7f3a9 100644 --- a/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/AppSettings.kt +++ b/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/AppSettings.kt @@ -3,7 +3,7 @@ package dev.meloda.fast.datastore import android.content.SharedPreferences import androidx.core.content.edit import dev.meloda.fast.common.model.DarkMode -import dev.meloda.fast.common.model.LogLevel +import dev.meloda.fast.common.model.NetworkLogLevel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -238,11 +238,11 @@ object AppSettings { ) set(value) = put(SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT, value) - var networkLogLevel: LogLevel + var networkLogLevel: NetworkLogLevel get() = get( SettingsKeys.KEY_DEBUG_NETWORK_LOG_LEVEL, SettingsKeys.DEFAULT_NETWORK_LOG_LEVEL - ).let(LogLevel::parse) + ).let(NetworkLogLevel::parse) set(level) = put(SettingsKeys.KEY_DEBUG_NETWORK_LOG_LEVEL, level.value) var showDebugCategory: Boolean diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollEventsHandler.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollEventsHandler.kt index 837b5803..cb7f4748 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollEventsHandler.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollEventsHandler.kt @@ -1,8 +1,8 @@ package dev.meloda.fast.domain -import android.util.Log import dev.meloda.fast.database.dao.ConvoDao import dev.meloda.fast.database.dao.MessageDao +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.model.LongPollParsedEvent import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope @@ -11,6 +11,7 @@ import kotlinx.coroutines.SupervisorJob import kotlin.coroutines.CoroutineContext class LongPollEventsHandler( + private val logger: FastLogger, private val convoUseCase: ConvoUseCase, private val messagesUseCase: MessagesUseCase, private val convoDao: ConvoDao, @@ -20,8 +21,7 @@ class LongPollEventsHandler( private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> - Log.e("LongPollUpdatesParser", "error: $throwable") - throwable.printStackTrace() + logger.error(this::class, "CoroutineException", throwable) } private val coroutineContext: CoroutineContext @@ -45,8 +45,8 @@ class LongPollEventsHandler( isArchived = event.convo.isArchived ) - Log.d( - "LongPollEventsHandler", + logger.debug( + this::class, "isArchived ${event.convo.isArchived}: updated $affectedRows rows." ) } @@ -57,7 +57,10 @@ class LongPollEventsHandler( cmId = event.toCmId ) - Log.d("LongPollEventsHandler", "updateLastCmId: updated $affectedRows rows.") + logger.debug( + this::class, + "updateLastCmId: updated $affectedRows rows." + ) } is LongPollParsedEvent.ChatMajorChanged -> { @@ -66,7 +69,10 @@ class LongPollEventsHandler( majorId = event.majorId ) - Log.d("LongPollEventsHandler", "updateMajorId: updated $affectedRows rows.") + logger.debug( + this::class, + "updateMajorId: updated $affectedRows rows." + ) } is LongPollParsedEvent.ChatMinorChanged -> { @@ -75,7 +81,10 @@ class LongPollEventsHandler( minorId = event.minorId ) - Log.d("LongPollEventsHandler", "updateMinorId: updated $affectedRows rows.") + logger.debug( + this::class, + "updateMinorId: updated $affectedRows rows." + ) } is LongPollParsedEvent.Interaction -> { @@ -93,7 +102,10 @@ class LongPollEventsHandler( isDeleted = true ) - Log.d("LongPollEventsHandler", "markDeleted: updated $affectedRows rows.") + logger.debug( + this::class, + "markDeleted: updated $affectedRows rows." + ) } is LongPollParsedEvent.MessageEdited -> { @@ -107,7 +119,10 @@ class LongPollEventsHandler( isImportant = event.marked ) - Log.d("LongPollEventsHandler", "markImportant: updated $affectedRows rows.") + logger.debug( + this::class, + "markImportant: updated $affectedRows rows." + ) } is LongPollParsedEvent.MessageMarkedAsNotSpam -> { @@ -121,7 +136,10 @@ class LongPollEventsHandler( isSpam = true ) - Log.d("LongPollEventsHandler", "markSpam: updated $affectedRows rows.") + logger.debug( + this::class, + "markSpam: updated $affectedRows rows." + ) } is LongPollParsedEvent.MessageRestored -> { @@ -143,7 +161,10 @@ class LongPollEventsHandler( unreadCount = event.unreadCount ) - Log.d("LongPollEventsHandler", "inMessageRead: updated $affectedRows rows.") + logger.debug( + this::class, + "inMessageRead: updated $affectedRows rows." + ) } is LongPollParsedEvent.OutgoingMessageRead -> { @@ -153,7 +174,10 @@ class LongPollEventsHandler( unreadCount = event.unreadCount ) - Log.d("LongPollEventsHandler", "outMessageRead: updated $affectedRows rows.") + logger.debug( + this::class, + "outMessageRead: updated $affectedRows rows." + ) } is LongPollParsedEvent.UnreadCounter -> { diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt index 2e044963..d8a94f9d 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt @@ -1,6 +1,5 @@ package dev.meloda.fast.domain -import android.util.Log import dev.meloda.fast.common.VkConstants import dev.meloda.fast.common.extensions.asInt import dev.meloda.fast.common.extensions.asLong @@ -8,6 +7,7 @@ import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.toList import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.processState +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.model.ApiEvent import dev.meloda.fast.model.ConvoFlags import dev.meloda.fast.model.InteractionType @@ -27,6 +27,7 @@ import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume class LongPollUpdatesParser( + private val logger: FastLogger, private val convoUseCase: ConvoUseCase, private val messagesUseCase: MessagesUseCase ) { @@ -34,8 +35,7 @@ class LongPollUpdatesParser( private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> - Log.e("LongPollUpdatesParser", "error: $throwable") - throwable.printStackTrace() + logger.error(this::class, "CoroutineException", throwable) } private val coroutineContext: CoroutineContext @@ -51,7 +51,7 @@ class LongPollUpdatesParser( return when (val eventType = ApiEvent.parseOrNull(eventId)) { null -> { - Log.d("LongPollUpdatesParser", "parseNextUpdate: unknownEvent: $event") + logger.debug(this::class, "parseNextUpdate(): unknownEvent: $event") emptyList() } @@ -83,7 +83,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List { - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseMessageSetFlags(): $eventType: $event") val cmId = event[1].asLong() val flags = event[2].asInt() @@ -171,7 +171,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List = suspendCancellableCoroutine { continuation -> - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseMessageClearFlags(): $eventType: $event") val cmId = event[1].asLong() val flags = event[2].asInt() @@ -246,7 +246,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List = suspendCancellableCoroutine { continuation -> - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseMessageNew(): $eventType: $event") val cmId = event[1].asLong() val peerId = event[4].asLong() @@ -284,7 +284,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List = suspendCancellableCoroutine { continuation -> - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseMessageEdit(): $eventType: $event") val cmId = event[1].asLong() val peerId = event[3].asLong() @@ -305,7 +305,8 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List { - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseMessageReadIncoming(): $eventType: $event") + val peerId = event[1].asLong() val cmId = event[2].asLong() val unreadCount = event[3].asInt() @@ -323,7 +324,8 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List { - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseMessageReadOutgoing(): $eventType: $event") + val peerId = event[1].asLong() val cmId = event[2].asLong() val unreadCount = event[3].asInt() @@ -342,7 +344,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List = suspendCancellableCoroutine { continuation -> - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseChatClearFlags(): $eventType: $event") val peerId = event[1].asLong() val flags = event[2].asInt() @@ -402,7 +404,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List = suspendCancellableCoroutine { continuation -> - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseChatSetFlags(): $eventType: $event") val peerId = event[1].asLong() val flags = event[2].asInt() @@ -462,7 +464,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List { - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseMessagesDeleted(): $eventType: $event") val peerId = event[1].asLong() val cmId = event[2].asLong() @@ -479,7 +481,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List { - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseChatMajorChanged(): $eventType: $event") val peerId = event[1].asLong() val majorId = event[2].asInt() @@ -496,7 +498,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List { - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseChatMinorChanged(): $eventType: $event") val peerId = event[1].asLong() val minorId = event[2].asInt() @@ -513,7 +515,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List { - Log.d("LongPollUpdatesParser", "$eventType: $event") + logger.debug(this::class, "parseInteraction(): $eventType: $event") val interactionType = when (eventType) { ApiEvent.TYPING -> InteractionType.Typing @@ -556,7 +558,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List { - Log.d("LongPollUpdatesParser", "$eventType $event") + logger.debug(this::class, "parseUnreadCounterUpdate(): $eventType: $event") val unreadCount = event[1].asInt() val unreadUnmutedCount = event[2].asInt() @@ -583,7 +585,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List = suspendCancellableCoroutine { continuation -> - Log.d("LongPollUpdatesParser", "$eventType $event") + logger.debug(this::class, "parseMessageUpdated(): $eventType: $event") val cmId = event[1].asLong() val peerId = event[4].asLong() @@ -605,7 +607,7 @@ class LongPollUpdatesParser( eventType: ApiEvent, event: List ): List = suspendCancellableCoroutine { continuation -> - Log.d("LongPollUpdatesParser", "$eventType $event") + logger.debug(this::class, "parseMessageCacheClear(): $eventType: $event") val messageId = event[1].asLong() @@ -639,7 +641,7 @@ class LongPollUpdatesParser( ).listenValue(this) { state -> state.processState( error = { error -> - Log.e("LongPollUpdatesParser", "loadMessage: error: $error") + logger.error(this::class, "loadMessage(): ERROR: $error") continuation.resume(null) }, success = { response -> @@ -668,7 +670,7 @@ class LongPollUpdatesParser( ).listenValue(coroutineScope) { state -> state.processState( error = { error -> - Log.e("LongPollUpdatesParser", "loadConvo: error: $error") + logger.error(this::class, "loadConvo(): ERROR: $error") continuation.resume(null) }, success = { response -> diff --git a/core/logger/.gitignore b/core/logger/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/logger/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/logger/build.gradle.kts b/core/logger/build.gradle.kts new file mode 100644 index 00000000..88770b34 --- /dev/null +++ b/core/logger/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + alias(libs.plugins.fast.android.library) +} + +android { + namespace = "dev.meloda.fast.logger" +} + +dependencies { + implementation(libs.koin.android) +} diff --git a/core/logger/src/main/kotlin/dev/meloda/fast/logger/FastLogLevel.kt b/core/logger/src/main/kotlin/dev/meloda/fast/logger/FastLogLevel.kt new file mode 100644 index 00000000..b8e88624 --- /dev/null +++ b/core/logger/src/main/kotlin/dev/meloda/fast/logger/FastLogLevel.kt @@ -0,0 +1,17 @@ +package dev.meloda.fast.logger; + +enum class FastLogLevel { + VERBOSE, + DEBUG, + INFO, + WARNING, + ERROR, + ASSERT; + + companion object { + fun parse(value: Int): FastLogLevel { + if (value !in 0..5) throw IllegalArgumentException("Unknown LogLevel value $value") + return entries.first { it.ordinal == value } + } + } +} diff --git a/core/logger/src/main/kotlin/dev/meloda/fast/logger/FastLogger.kt b/core/logger/src/main/kotlin/dev/meloda/fast/logger/FastLogger.kt new file mode 100644 index 00000000..a3cee692 --- /dev/null +++ b/core/logger/src/main/kotlin/dev/meloda/fast/logger/FastLogger.kt @@ -0,0 +1,108 @@ +package dev.meloda.fast.logger + +import android.util.Log +import kotlin.reflect.KClass + +class FastLogger { + companion object { + + @Volatile + private lateinit var instance: FastLogger + + fun setInstance(logger: FastLogger) { + if (::instance.isInitialized) { + throw IllegalStateException("FastLogger has already been initialized.") + } + + instance = logger + } + + fun getInstance(): FastLogger { + if (!::instance.isInitialized) { + throw UninitializedPropertyAccessException("FastLogger is not initialized.") + } + return instance + } + } + + private var logLevel: FastLogLevel = FastLogLevel.ERROR + + fun setLogLevel(logLevel: FastLogLevel) { + Log.v(this::class.java.simpleName, "Set LogLevel from ${this.logLevel} to $logLevel") + this.logLevel = logLevel + } + + fun verbose(clazz: Class<*>, message: String, throwable: Throwable? = null) { + verbose(clazz.simpleName, message, throwable) + } + + fun verbose(tag: String, message: String, throwable: Throwable? = null) { + if (shouldLog(FastLogLevel.VERBOSE)) { + Log.v(tag, message, throwable) + } + } + + fun debug(clazz: KClass<*>, message: String, throwable: Throwable? = null) { + debug(clazz.java, message, throwable) + } + + fun debug(clazz: Class<*>, message: String, throwable: Throwable? = null) { + debug(clazz.simpleName, message, throwable) + } + + fun debug(tag: String, message: String, throwable: Throwable? = null) { + if (shouldLog(FastLogLevel.DEBUG)) { + Log.d(tag, message, throwable) + } + } + + fun info(clazz: KClass<*>, message: String, throwable: Throwable? = null) { + info(clazz.java, message, throwable) + } + + fun info(clazz: Class<*>, message: String, throwable: Throwable? = null) { + info(clazz.simpleName, message, throwable) + } + + fun info(tag: String, message: String, throwable: Throwable? = null) { + if (shouldLog(FastLogLevel.INFO)) { + Log.i(tag, message, throwable) + } + } + + fun warning(clazz: Class<*>, message: String, throwable: Throwable? = null) { + warning(clazz.simpleName, message, throwable) + } + + fun warning(tag: String, message: String, throwable: Throwable? = null) { + if (shouldLog(FastLogLevel.WARNING)) { + Log.w(tag, message, throwable) + } + } + + fun error(clazz: KClass<*>, message: String, throwable: Throwable? = null) { + error(clazz.java, message, throwable) + } + + fun error(clazz: Class<*>, message: String, throwable: Throwable? = null) { + error(clazz.simpleName, message, throwable) + } + + fun error(tag: String, message: String, throwable: Throwable? = null) { + if (shouldLog(FastLogLevel.ERROR)) { + Log.e(tag, message, throwable) + } + } + + fun assert(clazz: Class<*>, message: String, throwable: Throwable? = null) { + assert(clazz.simpleName, message, throwable) + } + + fun assert(tag: String, message: String, throwable: Throwable? = null) { + if (shouldLog(FastLogLevel.ASSERT)) { + Log.wtf(tag, message, throwable) + } + } + + private fun shouldLog(level: FastLogLevel): Boolean = level.ordinal >= logLevel.ordinal +} diff --git a/core/logger/src/main/kotlin/dev/meloda/fast/logger/LoggerModule.kt b/core/logger/src/main/kotlin/dev/meloda/fast/logger/LoggerModule.kt new file mode 100644 index 00000000..33ae3e3b --- /dev/null +++ b/core/logger/src/main/kotlin/dev/meloda/fast/logger/LoggerModule.kt @@ -0,0 +1,8 @@ +package dev.meloda.fast.logger + +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val loggerModule = module { + singleOf(::FastLogger) +} diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/AttachmentType.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/AttachmentType.kt index 3527fb60..61c6fb8e 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/AttachmentType.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/AttachmentType.kt @@ -1,7 +1,5 @@ package dev.meloda.fast.model.api.data -import android.util.Log - enum class AttachmentType(var value: String) { UNKNOWN("unknown"), PHOTO("photo"), @@ -42,10 +40,6 @@ enum class AttachmentType(var value: String) { it.value == value } ?: UNKNOWN - if (parsedValue == UNKNOWN) { - Log.e("AttachmentType", "Unknown attachment type: $value") - } - return parsedValue } } diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 6c18a4cd..f86d4e4f 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { api(projects.core.common) api(projects.core.model) api(projects.core.datastore) + api(projects.core.logger) implementation(libs.moshi.kotlin) implementation(libs.koin.android) diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/ResponseConverterFactory.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/ResponseConverterFactory.kt index 07f562f9..03f43667 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/ResponseConverterFactory.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/ResponseConverterFactory.kt @@ -1,10 +1,10 @@ package dev.meloda.fast.network -import android.util.Log import com.slack.eithernet.ApiException import com.slack.eithernet.errorType import com.slack.eithernet.toType import com.squareup.moshi.JsonDataException +import dev.meloda.fast.logger.FastLogger import okhttp3.ResponseBody import retrofit2.Converter import retrofit2.Retrofit @@ -16,7 +16,10 @@ import java.lang.reflect.Type * * допускает Unit как SuccessType в случае невозможности каста ответа в ErrorType */ -class ResponseConverterFactory(private val converter: JsonConverter) : Converter.Factory() { +class ResponseConverterFactory( + private val converter: JsonConverter, + private val logger: FastLogger +) : Converter.Factory() { override fun responseBodyConverter( type: Type, @@ -29,6 +32,7 @@ class ResponseConverterFactory(private val converter: JsonConverter) : Converter successType = type, errorRaw = errorRaw, converter = converter, + logger = logger ) } @@ -36,6 +40,7 @@ class ResponseConverterFactory(private val converter: JsonConverter) : Converter private val successType: Type, private val errorRaw: Class<*>, private val converter: JsonConverter, + private val logger: FastLogger ) : Converter { override fun convert(value: ResponseBody): Any? { val string = value.string() @@ -53,7 +58,7 @@ class ResponseConverterFactory(private val converter: JsonConverter) : Converter }, onFailure = { failure -> if (failure is JsonDataException) { - Log.d("ResponseBodyConverter", "convertJsonDataException: $failure") + logger.error(this::class, "convert(): ERROR", failure) throw ApiException( RestApiError( errorCode = -1, @@ -68,10 +73,11 @@ class ResponseConverterFactory(private val converter: JsonConverter) : Converter converter.fromJson(errorRaw, string) }.fold( onSuccess = { errorModel -> - Log.d("ResponseBodyConverter", "convert: $errorModel") + logger.debug(this::class, "convert(): errorModel: $errorModel") throw ApiException(errorModel) }, onFailure = { exception -> + logger.error(this::class, "convert(): INNER: ERROR", exception) if (!isUnit) { throw exception } else { diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/interceptor/Error14HandlingInterceptor.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/interceptor/Error14HandlingInterceptor.kt index 8e54710b..32e6b359 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/interceptor/Error14HandlingInterceptor.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/interceptor/Error14HandlingInterceptor.kt @@ -1,9 +1,9 @@ package dev.meloda.fast.network.interceptor -import android.util.Log import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.datastore.CaptchaTokenResult +import dev.meloda.fast.logger.FastLogger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -14,11 +14,7 @@ import org.json.JSONObject import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicReference -class Error14HandlingInterceptor( -// private val domains: Set = emptySet(), -) : Interceptor { - - private val cookie = AtomicReference(null) +class Error14HandlingInterceptor(private val logger: FastLogger) : Interceptor { private companion object { private const val CAPTCHA_ERROR_CODE = 14 @@ -26,6 +22,8 @@ class Error14HandlingInterceptor( private val executor = Executors.newSingleThreadExecutor() } + private val cookie = AtomicReference(null) + override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request().withCookie() val response = chain.proceed(request) @@ -41,23 +39,23 @@ class Error14HandlingInterceptor( executor.submit { AppSettings.setCaptchaRedirectUri(redirectUri) - Log.d("Error14Interceptor", "passCaptchaAndGetToken: $redirectUri") + logger.debug(this::class, "passCaptchaAndGetToken: $redirectUri") var job: Job? = null job = AppSettings.getCaptchaResultFlow() .listenValue(CoroutineScope(Dispatchers.IO)) { - Log.d("Error14Interceptor", "passCaptchaAndGetToken: $it") + logger.debug(this::class, "passCaptchaAndGetToken: $it") if (it != CaptchaTokenResult.Initial) { synchronized(tokenResult) { - Log.d( - "Error14Interceptor", + logger.debug( + this::class, "passCaptchaAndGetToken: SYNCHRONIZED: $it" ) tokenResult.set(wrapResult(it)) tokenResult.notifyAll() job?.cancel() - Log.d( - "Error14Interceptor", + logger.debug( + this::class, "passCaptchaAndGetToken: NULL RESULT" ) AppSettings.setCaptchaResult(CaptchaTokenResult.Initial) @@ -71,7 +69,7 @@ class Error14HandlingInterceptor( tokenResult.wait() } - Log.d("Error14Interceptor", "passCaptchaAndGetToken: GET VALUE") + logger.debug(this::class, "passCaptchaAndGetToken: GET VALUE") tokenResult.get().getOrThrow() } } diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 6c05d009..a3a04001 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -12,6 +12,7 @@ android { dependencies { api(projects.core.common) api(projects.core.model) + api(projects.core.logger) implementation(projects.core.presentation) implementation(libs.haze) diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/common/LocalLogger.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/common/LocalLogger.kt new file mode 100644 index 00000000..20061701 --- /dev/null +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/common/LocalLogger.kt @@ -0,0 +1,6 @@ +package dev.meloda.fast.ui.common + +import androidx.compose.runtime.compositionLocalOf +import dev.meloda.fast.logger.FastLogger + +val LocalLogger = compositionLocalOf { FastLogger.getInstance() } diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/captcha/presentation/CaptchaScreen.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/captcha/presentation/CaptchaScreen.kt index 0eb41f42..afbcc422 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/captcha/presentation/CaptchaScreen.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/captcha/presentation/CaptchaScreen.kt @@ -1,7 +1,6 @@ package dev.meloda.fast.auth.captcha.presentation import android.graphics.Bitmap -import android.util.Log import android.webkit.JavascriptInterface import android.webkit.WebResourceRequest import android.webkit.WebView @@ -32,7 +31,9 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.common.LocalLogger import dev.meloda.fast.ui.components.ActionInvokeDismiss import dev.meloda.fast.ui.components.FullScreenDialog import dev.meloda.fast.ui.components.MaterialDialog @@ -46,6 +47,8 @@ fun CaptchaScreen( onBack: () -> Unit = {}, onResult: (String) -> Unit = {} ) { + val logger = LocalLogger.current + if (captchaRedirectUri != null) { val focusManager = LocalFocusManager.current val keyboardController = LocalSoftwareKeyboardController.current @@ -114,7 +117,10 @@ fun CaptchaScreen( view: WebView?, request: WebResourceRequest? ): Boolean { - Log.i(TAG, "shouldOverrideUrlLoading: $request") + logger.info( + "CaptchaScreen", + "WebViewClient(): shouldOverrideUrlLoading(): request: $request" + ) return false } @@ -176,19 +182,18 @@ fun CaptchaScreen( class WebCaptchaListener( private val onSuccessTokenReceived: (String) -> Unit, - private val onCloseRequested: (String) -> Unit + private val onCloseRequested: (String) -> Unit, + private val logger: FastLogger ) { - private val tag = "WebCaptchaListener" - @JavascriptInterface fun VKCaptchaGetResult(arg: String) { onSuccessTokenReceived(arg) - Log.i(tag, "VKCaptchaGetResult($arg)") + logger.info(this::class, "VKCaptchaGetResult(): arg: $arg") } @JavascriptInterface fun VKCaptchaCloseCaptcha(arg: String) { onCloseRequested(arg) - Log.i(tag, "VKCaptchaCloseCaptcha($arg)") + logger.info(this::class, "VKCaptchaCloseCaptcha(): arg: $arg") } } diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt index 96896659..13de9e93 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt @@ -2,7 +2,6 @@ package dev.meloda.fast.auth.login import android.os.Build import android.os.Bundle -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dev.meloda.fast.auth.login.model.CaptchaArguments @@ -28,6 +27,7 @@ import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.domain.LoadUserByIdUseCase import dev.meloda.fast.domain.OAuthUseCase +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.model.database.AccountEntity import dev.meloda.fast.network.OAuthErrorDomain import kotlinx.coroutines.CoroutineExceptionHandler @@ -48,7 +48,8 @@ class LoginViewModel( private val accountsRepository: AccountsRepository, private val loginValidator: LoginValidator, private val longPollController: LongPollController, - private val userSettings: UserSettings + private val userSettings: UserSettings, + private val logger: FastLogger ) : ViewModel() { private val _screenState = MutableStateFlow(LoginScreenState.EMPTY) val screenState = _screenState.asStateFlow() @@ -189,7 +190,7 @@ class LoginViewModel( ).listenValue(viewModelScope) { state -> state.processState( error = { error -> - Log.d("LoginViewModelImpl", "login: error: $error") + logger.error(this::class, "getSilentToken(): ERROR: $error") _screenState.updateValue { copy(isLoading = false) } diff --git a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/util/ChatMaterialMapper.kt b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/util/ChatMaterialMapper.kt index 84186ac2..fd90f5dc 100644 --- a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/util/ChatMaterialMapper.kt +++ b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/util/ChatMaterialMapper.kt @@ -1,6 +1,5 @@ package dev.meloda.fast.chatmaterials.util -import android.util.Log import dev.meloda.fast.chatmaterials.model.UiChatMaterial import dev.meloda.fast.common.util.AndroidUtils import dev.meloda.fast.model.api.data.AttachmentType @@ -135,8 +134,5 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial? = ) } - else -> { - Log.w("ChatMaterialMapper", "Unsupported type: $type") - null - } + else -> null } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt index 3086c4a4..5d714aae 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt @@ -6,7 +6,6 @@ import android.content.Context import android.graphics.Bitmap import android.os.Build import android.os.Bundle -import android.util.Log import android.widget.Toast import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle @@ -685,8 +684,6 @@ class MessagesHistoryViewModelImpl( private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) { val message = event.message - Log.d("MessagesHistoryViewModel", "handleNewMessage: $message") - if (message.peerId != screenState.value.convoId) return if (messages.value.indexOfFirstOrNull { it.id == message.id } != null) return @@ -835,8 +832,6 @@ class MessagesHistoryViewModelImpl( } private fun loadConvo() { - Log.d("MessagesHistoryViewModelImpl", "loadConvo()") - loadConvosByIdUseCase( peerIds = listOf(screenState.value.convoId), extended = true, @@ -904,8 +899,6 @@ class MessagesHistoryViewModelImpl( } private fun loadMessagesHistory(offset: Int = currentOffset.value) { - Log.d("MessagesHistoryViewModel", "loadMessagesHistory: $offset") - messagesUseCase.getMessagesHistory( convoId = screenState.value.convoId, count = MESSAGES_LOAD_COUNT, @@ -1079,8 +1072,6 @@ class MessagesHistoryViewModelImpl( state.processState( any = { sendingMessages.remove(newMessage) }, error = { error -> - Log.d("MessagesHistoryViewModelImpl", "sendMessage: ERROR: $error") - val failedId = -500_000L - failedMessages.size val newFailedMessage = newMessage.copy(id = failedId) failedMessages += newFailedMessage @@ -1144,8 +1135,6 @@ class MessagesHistoryViewModelImpl( ) ?: return // TODO: 13/03/2026, Danil Nikolaev: check if message is exact same, then do not edit - - Log.d("MessagesHistoryViewModelImpl", "editMessage: $newMessage") } private fun markAsImportant( diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesList.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesList.kt index 979c2533..3809f39b 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesList.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesList.kt @@ -1,7 +1,6 @@ package dev.meloda.fast.messageshistory.presentation import android.content.Intent -import android.util.Log import android.view.HapticFeedbackConstants import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState @@ -129,7 +128,6 @@ fun MessagesList( when (attachment) { is VkPhotoDomain -> { val maxSize = attachment.getMaxSize() - Log.d("MessagesList", "onPhotoLongClicked. Max size: ${maxSize?.url}") } } } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/attachments/Previews.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/attachments/Previews.kt index 5018724a..ffa1bfed 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/attachments/Previews.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/attachments/Previews.kt @@ -64,7 +64,6 @@ fun DynamicPreviewGrid( val spacingPx = with(LocalDensity.current) { spacing.toPx() } val rows = previews.chunked(3) - Log.d("ROWS", "DynamicPreviewGrid: ${rows.size}") Column(verticalArrangement = Arrangement.spacedBy(spacing)) { rows.forEachIndexed { outerIndex, row -> diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt index fffff35b..5ee54c7a 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt @@ -12,7 +12,7 @@ import dev.meloda.fast.common.extensions.findWithIndex import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.setValue import dev.meloda.fast.common.model.DarkMode -import dev.meloda.fast.common.model.LogLevel +import dev.meloda.fast.common.model.NetworkLogLevel import dev.meloda.fast.common.model.LongPollState import dev.meloda.fast.common.model.UiText import dev.meloda.fast.common.model.parseString @@ -497,10 +497,10 @@ class SettingsViewModel( ) val logLevelValues = listOf( - LogLevel.NONE to UiText.Simple("None"), - LogLevel.BASIC to UiText.Simple("Basic"), - LogLevel.HEADERS to UiText.Simple("Headers"), - LogLevel.BODY to UiText.Simple("Body") + NetworkLogLevel.NONE to UiText.Simple("None"), + NetworkLogLevel.BASIC to UiText.Simple("Basic"), + NetworkLogLevel.HEADERS to UiText.Simple("Headers"), + NetworkLogLevel.BODY to UiText.Simple("Body") ).toMap() val debugNetworkLogLevel = SettingsItem.ListItem( @@ -509,10 +509,10 @@ class SettingsViewModel( valueClass = Int::class, defaultValue = SettingsKeys.DEFAULT_NETWORK_LOG_LEVEL, titles = logLevelValues.values.toList(), - values = logLevelValues.keys.toList().map(LogLevel::value) + values = logLevelValues.keys.toList().map(NetworkLogLevel::value) ).apply { textProvider = TextProvider { item -> - val textValue = logLevelValues[LogLevel.parse(item.value)].parseString(resources) + val textValue = logLevelValues[NetworkLogLevel.parse(item.value)].parseString(resources) UiText.Simple("Current value: $textValue") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 571ece6c..2a4f82e6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -55,3 +55,4 @@ include(":feature:friends") include(":feature:profile") include(":feature:createchat") include(":core:presentation") +include(":core:logger")