diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt
index d54f1c4c78..c8408c6e91 100644
--- a/app/src/main/java/com/tangem/tap/TangemApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt
@@ -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 {
diff --git a/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt b/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt
index 1b3f599f3f..a2011ff5e7 100644
--- a/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt
+++ b/app/src/main/java/com/tangem/tap/common/log/TangemAppLoggerInitializer.kt
@@ -35,7 +35,10 @@ class TangemAppLoggerInitializer(
}
if (PERMITTED_PRIORITY.contains(priority)) {
- appLogsStore.saveLogMessage(message)
+ appLogsStore.saveLogMessage(
+ tag = tag ?: "TangemAppLogger",
+ message = message,
+ )
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt b/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt
index a85c3e4667..f197705464 100644
--- a/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt
+++ b/app/src/main/java/com/tangem/tap/data/TangemBlockchainSDKLogger.kt
@@ -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)
}
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt
index bb1112d268..ac6d80ef03 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/local/logs/AppLogsStore.kt
@@ -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()
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt
index b593688bd9..26703087a9 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt
@@ -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)
+ }
}
\ No newline at end of file
diff --git a/features/tester/impl/build.gradle.kts b/features/tester/impl/build.gradle.kts
index 86afbab205..5c994aba93 100644
--- a/features/tester/impl/build.gradle.kts
+++ b/features/tester/impl/build.gradle.kts
@@ -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)
diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt
index c814bf750b..9307de43a7 100644
--- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt
+++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsContentState.kt
@@ -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,
-)
\ No newline at end of file
+ @Immutable
+ data class ShareLogsUM(val file: File?)
+}
\ No newline at end of file
diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt
index adadf6886f..f19b69a825 100644
--- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt
+++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsScreen.kt
@@ -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 = {},
),
)
}
diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt
index 514edb448d..1c351420c7 100644
--- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt
+++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt
@@ -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(
"""
diff --git a/features/tester/impl/src/main/res/values/strings.xml b/features/tester/impl/src/main/res/values/strings.xml
index 77b8a9c63d..12d59827a6 100644
--- a/features/tester/impl/src/main/res/values/strings.xml
+++ b/features/tester/impl/src/main/res/values/strings.xml
@@ -13,4 +13,5 @@
Excluded blockchains
Filter by name or symbol
Blockchain providers
+ Share logs