Updated on 2026-08-14
This commit is contained in:
parent
552da5be18
commit
35d7b71361
10 changed files with 130 additions and 55 deletions
|
|
@ -222,7 +222,10 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
private fun updateLogFiles() {
|
||||
appLogsStore.deleteOldLogsFile()
|
||||
appLogsStore.deleteLastLogFile()
|
||||
|
||||
if (!BuildConfig.TESTER_MENU_ENABLED) {
|
||||
appLogsStore.deleteLastLogFile()
|
||||
}
|
||||
|
||||
// Temporally logs are not saved
|
||||
// scope.launch {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@ class TangemAppLoggerInitializer(
|
|||
}
|
||||
|
||||
if (PERMITTED_PRIORITY.contains(priority)) {
|
||||
appLogsStore.saveLogMessage(message)
|
||||
appLogsStore.saveLogMessage(
|
||||
tag = tag ?: "TangemAppLogger",
|
||||
message = message,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,6 @@ internal class TangemBlockchainSDKLogger(
|
|||
) : BlockchainSDKLogger {
|
||||
|
||||
override fun log(level: BlockchainSDKLogger.Level, message: String) {
|
||||
appLogsStore.saveLogMessage(message)
|
||||
appLogsStore.saveLogMessage(tag = "BlockchainSDK_${level.name}", message)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.local.logs
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
|
|
@ -58,26 +59,30 @@ class AppLogsStore @Inject constructor(
|
|||
fun getFile(): File? = if (file.exists()) file else null
|
||||
|
||||
/** Save log [message] */
|
||||
fun saveLogMessage(message: String) {
|
||||
// Temporally logs are not saved
|
||||
return
|
||||
fun saveLogMessage(tag: String, message: String) {
|
||||
// Temporally logs are not saved in prod environment
|
||||
if (!BuildConfig.TESTER_MENU_ENABLED) {
|
||||
return
|
||||
}
|
||||
|
||||
launchWithLock {
|
||||
createFileIfNotExist()
|
||||
|
||||
writeMessage(message)
|
||||
writeMessage(tag = tag, message)
|
||||
}
|
||||
}
|
||||
|
||||
/** Save log that consists from [messages] */
|
||||
fun saveLogMessage(vararg messages: String) {
|
||||
// Temporally logs are not saved
|
||||
return
|
||||
fun saveLogMessage(tag: String, vararg messages: String) {
|
||||
// Temporally logs are not saved in prod environment
|
||||
if (!BuildConfig.TESTER_MENU_ENABLED) {
|
||||
return
|
||||
}
|
||||
|
||||
launchWithLock {
|
||||
createFileIfNotExist()
|
||||
|
||||
writeMessage(*messages)
|
||||
writeMessage(tag = tag, *messages)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,10 +107,10 @@ class AppLogsStore @Inject constructor(
|
|||
if (file.exists()) file.delete()
|
||||
}
|
||||
|
||||
private fun writeMessage(vararg messages: String) {
|
||||
private fun writeMessage(tag: String, vararg messages: String) {
|
||||
BufferedWriter(FileWriter(file, true)).use { writer ->
|
||||
writer.append(formatter.print(DateTime.now()))
|
||||
writer.append(": ")
|
||||
writer.append(": $tag ")
|
||||
messages.forEach(writer::append)
|
||||
writer.newLine()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ internal class NetworkLogsSaveInterceptor(
|
|||
try {
|
||||
response = chain.proceed(request)
|
||||
} catch (e: Exception) {
|
||||
appLogsStore.saveLogMessage("<-- HTTP FAILED: $e")
|
||||
saveLogMessage("<-- HTTP FAILED: $e")
|
||||
throw e
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ internal class NetworkLogsSaveInterceptor(
|
|||
val connection = chain.connection()
|
||||
val connectionProtocol = if (connection != null) " ${connection.protocol()}" else ""
|
||||
|
||||
appLogsStore.saveLogMessage(
|
||||
saveLogMessage(
|
||||
"--> ${request.method} ${request.url}$connectionProtocol\n",
|
||||
createRequestEndMessage(request),
|
||||
)
|
||||
|
|
@ -136,7 +136,7 @@ internal class NetworkLogsSaveInterceptor(
|
|||
|
||||
val spaceBeforeResponseMessage = if (response.message.isEmpty()) "" else ' ' + response.message
|
||||
|
||||
appLogsStore.saveLogMessage(
|
||||
saveLogMessage(
|
||||
"<-- ${response.code}",
|
||||
spaceBeforeResponseMessage,
|
||||
response.message,
|
||||
|
|
@ -198,4 +198,8 @@ internal class NetworkLogsSaveInterceptor(
|
|||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveLogMessage(vararg messages: String) {
|
||||
appLogsStore.saveLogMessage(tag = "NetworkLogs", *messages)
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ dependencies {
|
|||
/** Domain modules */
|
||||
implementation(projects.domain.appTheme)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
implementation(projects.domain.feedback)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.arrow.core)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,26 @@
|
|||
package com.tangem.feature.tester.presentation.actions
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import java.io.File
|
||||
|
||||
internal data class TesterActionsContentState(
|
||||
val hideAllCurrenciesConfig: HideAllCurrenciesConfig,
|
||||
val toggleAppThemeConfig: ToggleAppThemeConfig,
|
||||
val hideAllCurrenciesUM: HideAllCurrenciesUM,
|
||||
val toggleAppThemeUM: ToggleAppThemeUM,
|
||||
val shareLogsUM: ShareLogsUM,
|
||||
val onBackClick: () -> Unit,
|
||||
val onApplyChangesClick: () -> Unit,
|
||||
)
|
||||
) {
|
||||
sealed class HideAllCurrenciesUM {
|
||||
data class Clickable(val onClick: () -> Unit) : HideAllCurrenciesUM()
|
||||
|
||||
internal sealed class HideAllCurrenciesConfig {
|
||||
data class Clickable(val onClick: () -> Unit) : HideAllCurrenciesConfig()
|
||||
data object Progress : HideAllCurrenciesUM()
|
||||
}
|
||||
|
||||
data object Progress : HideAllCurrenciesConfig()
|
||||
}
|
||||
data class ToggleAppThemeUM(
|
||||
val currentAppTheme: AppThemeMode,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
internal data class ToggleAppThemeConfig(
|
||||
val currentAppTheme: AppThemeMode,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
@Immutable
|
||||
data class ShareLogsUM(val file: File?)
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.feature.tester.presentation.actions
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -8,14 +10,24 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.feature.tester.impl.R
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsContentState.HideAllCurrenciesUM
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsContentState.ToggleAppThemeUM
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
|
|
@ -24,6 +36,7 @@ internal fun TesterActionsScreen(state: TesterActionsContentState, modifier: Mod
|
|||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
stickyHeader {
|
||||
AppBarWithBackButton(
|
||||
|
|
@ -31,42 +44,75 @@ internal fun TesterActionsScreen(state: TesterActionsContentState, modifier: Mod
|
|||
onBackClick = state.onBackClick,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
val onClick = remember(state.hideAllCurrenciesConfig) {
|
||||
{ (state.hideAllCurrenciesConfig as? HideAllCurrenciesConfig.Clickable)?.onClick?.invoke() ?: Unit }
|
||||
val onClick = remember(state.hideAllCurrenciesUM) {
|
||||
{ (state.hideAllCurrenciesUM as? HideAllCurrenciesUM.Clickable)?.onClick?.invoke() ?: Unit }
|
||||
}
|
||||
TesterActionItem(
|
||||
name = stringResourceSafe(R.string.hide_all_currencies),
|
||||
progress = state.hideAllCurrenciesConfig is HideAllCurrenciesConfig.Progress,
|
||||
progress = state.hideAllCurrenciesUM is HideAllCurrenciesUM.Progress,
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
val config = state.toggleAppThemeConfig
|
||||
val config = state.toggleAppThemeUM
|
||||
|
||||
TesterActionItem(
|
||||
name = stringResourceSafe(id = R.string.toggle_app_theme, config.currentAppTheme.name),
|
||||
onClick = config.onClick,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
|
||||
TesterActionItem(
|
||||
name = stringResourceSafe(id = R.string.share_logs),
|
||||
onClick = { activity.shareFile(file = state.shareLogsUM.file) },
|
||||
enabled = state.shareLogsUM.file != null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TesterActionItem(
|
||||
name: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
progress: Boolean = false,
|
||||
) {
|
||||
Box(modifier = modifier.padding(all = TangemTheme.dimens.spacing16)) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = name,
|
||||
onClick = onClick,
|
||||
showProgress = progress,
|
||||
private fun TesterActionItem(name: String, onClick: () -> Unit, progress: Boolean = false, enabled: Boolean = true) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = name,
|
||||
onClick = onClick,
|
||||
showProgress = progress,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Activity.shareFile(file: File?) {
|
||||
val originalIntent = createEmailShareIntent(activity = this, file = file)
|
||||
|
||||
try {
|
||||
val chooserIntent = Intent.createChooser(originalIntent, "Share logs...")
|
||||
|
||||
ContextCompat.startActivity(this, chooserIntent, null)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e("Failed to share file: $ex")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmailShareIntent(activity: Activity, file: File?): Intent {
|
||||
val builder = ShareCompat.IntentBuilder(activity)
|
||||
.setType("text/plain")
|
||||
|
||||
file?.let {
|
||||
builder.setStream(
|
||||
FileProvider.getUriForFile(activity, "${activity.packageName}.provider", it),
|
||||
)
|
||||
}
|
||||
|
||||
return builder.intent
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
|
@ -78,10 +124,10 @@ private fun TesterActionsScreenSample(modifier: Modifier = Modifier) {
|
|||
) {
|
||||
TesterActionsScreen(
|
||||
state = TesterActionsContentState(
|
||||
hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable {},
|
||||
toggleAppThemeConfig = ToggleAppThemeConfig(AppThemeMode.DEFAULT) {},
|
||||
hideAllCurrenciesUM = HideAllCurrenciesUM.Clickable {},
|
||||
toggleAppThemeUM = ToggleAppThemeUM(AppThemeMode.DEFAULT) {},
|
||||
shareLogsUM = TesterActionsContentState.ShareLogsUM(file = null),
|
||||
onBackClick = {},
|
||||
onApplyChangesClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import arrow.core.getOrElse
|
|||
import com.tangem.domain.apptheme.ChangeAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsContentState.HideAllCurrenciesUM
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsContentState.ToggleAppThemeUM
|
||||
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -24,6 +27,7 @@ internal class TesterActionsViewModel @Inject constructor(
|
|||
private val userWalletManager: UserWalletManager,
|
||||
private val changeAppThemeModeUseCase: ChangeAppThemeModeUseCase,
|
||||
private val getAppThemeModeUseCase: GetAppThemeModeUseCase,
|
||||
private val feedbackRepository: FeedbackRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
var uiState: TesterActionsContentState by mutableStateOf(initialState)
|
||||
|
|
@ -31,10 +35,13 @@ internal class TesterActionsViewModel @Inject constructor(
|
|||
|
||||
private val initialState: TesterActionsContentState
|
||||
get() = TesterActionsContentState(
|
||||
hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable(this::hideAllCurrencies),
|
||||
toggleAppThemeConfig = ToggleAppThemeConfig(AppThemeMode.DEFAULT, this::toggleAppTheme),
|
||||
hideAllCurrenciesUM = HideAllCurrenciesUM.Clickable(this::hideAllCurrencies),
|
||||
toggleAppThemeUM = ToggleAppThemeUM(
|
||||
currentAppTheme = AppThemeMode.DEFAULT,
|
||||
onClick = this::toggleAppTheme,
|
||||
),
|
||||
shareLogsUM = TesterActionsContentState.ShareLogsUM(file = feedbackRepository.getLogFile()),
|
||||
onBackClick = { /* no-op */ },
|
||||
onApplyChangesClick = { /* no-op */ },
|
||||
)
|
||||
|
||||
init {
|
||||
|
|
@ -47,17 +54,17 @@ internal class TesterActionsViewModel @Inject constructor(
|
|||
|
||||
private fun hideAllCurrencies() = viewModelScope.launch {
|
||||
uiState = uiState.copy(
|
||||
hideAllCurrenciesConfig = HideAllCurrenciesConfig.Progress,
|
||||
hideAllCurrenciesUM = HideAllCurrenciesUM.Progress,
|
||||
)
|
||||
userWalletManager.hideAllTokens()
|
||||
|
||||
uiState = uiState.copy(
|
||||
hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable(this@TesterActionsViewModel::hideAllCurrencies),
|
||||
hideAllCurrenciesUM = HideAllCurrenciesUM.Clickable(this@TesterActionsViewModel::hideAllCurrencies),
|
||||
)
|
||||
}
|
||||
|
||||
private fun toggleAppTheme() = viewModelScope.launch {
|
||||
val currentAppThemeMode = uiState.toggleAppThemeConfig.currentAppTheme
|
||||
val currentAppThemeMode = uiState.toggleAppThemeUM.currentAppTheme
|
||||
val newAppThemeMode = when (currentAppThemeMode) {
|
||||
AppThemeMode.FORCE_DARK -> AppThemeMode.FORCE_LIGHT
|
||||
AppThemeMode.FORCE_LIGHT -> AppThemeMode.FOLLOW_SYSTEM
|
||||
|
|
@ -89,13 +96,13 @@ internal class TesterActionsViewModel @Inject constructor(
|
|||
Timber.d(
|
||||
"""
|
||||
Current app theme mode updated
|
||||
|- Previous app theme mode: ${uiState.toggleAppThemeConfig.currentAppTheme}
|
||||
|- Previous app theme mode: ${uiState.toggleAppThemeUM.currentAppTheme}
|
||||
|- New app theme mode: $maybeAppThemeMode
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
uiState = uiState.copy(
|
||||
toggleAppThemeConfig = uiState.toggleAppThemeConfig.copy(
|
||||
toggleAppThemeUM = uiState.toggleAppThemeUM.copy(
|
||||
currentAppTheme = maybeAppThemeMode.getOrElse { error ->
|
||||
Timber.e(
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@
|
|||
<string name="excluded_blockchains" translatable="false">Excluded blockchains</string>
|
||||
<string name="excluded_blockchains_search_placeholder" translatable="false">Filter by name or symbol</string>
|
||||
<string name="blockchain_providers" translatable="false">Blockchain providers</string>
|
||||
<string name="share_logs" translatable="false">Share logs</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue