Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-18 22:00:59 +08:00
parent 8d16f06c08
commit 7d9f1f0cf4
10 changed files with 105 additions and 20 deletions

View file

@ -120,4 +120,10 @@ internal object SettingsDomainModule {
): ShouldShowSwapPromoTokenUseCase {
return ShouldShowSwapPromoTokenUseCase(swapPromoRepository)
}
@Provides
@ViewModelScoped
fun provideDeleteDeprecatedLogsUseCase(settingsRepository: SettingsRepository): DeleteDeprecatedLogsUseCase {
return DeleteDeprecatedLogsUseCase(settingsRepository)
}
}

View file

@ -5,8 +5,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetSupportFeedbackEmailUseCase
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.features.details.redux.DetailsState
@ -24,11 +27,26 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
@Inject
lateinit var walletsRepository: WalletsRepository
@Inject
lateinit var feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
@Inject
lateinit var getSupportFeedbackEmailUseCase: GetSupportFeedbackEmailUseCase
@Inject
lateinit var emailSender: EmailSender
private lateinit var detailsViewModel: DetailsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
detailsViewModel = DetailsViewModel(store, walletsRepository)
detailsViewModel = DetailsViewModel(
store = store,
walletsRepository = walletsRepository,
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
getSupportFeedbackEmailUseCase = getSupportFeedbackEmailUseCase,
emailSender = emailSender,
)
Analytics.send(Settings.ScreenOpened())
}

View file

@ -6,11 +6,14 @@ import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
import com.tangem.domain.feedback.GetSupportFeedbackEmailUseCase
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.addContext
@ -24,6 +27,7 @@ import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.home.LocaleRegionProvider
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.mainScope
import com.tangem.tap.scope
import com.tangem.tap.userWalletsListManager
import com.tangem.wallet.BuildConfig
@ -35,6 +39,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.rekotlin.Store
import timber.log.Timber
@ -42,6 +47,9 @@ import timber.log.Timber
internal class DetailsViewModel(
private val store: Store<AppState>,
private val walletsRepository: WalletsRepository,
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
private val getSupportFeedbackEmailUseCase: GetSupportFeedbackEmailUseCase,
private val emailSender: EmailSender,
) {
var detailsScreenState: MutableState<DetailsScreenState> = mutableStateOf(updateState(store.state.detailsState))
@ -138,8 +146,22 @@ internal class DetailsViewModel(
private fun sendFeedback() {
Analytics.send(Settings.ButtonSendFeedback())
if (feedbackManagerFeatureToggles.isLocalLogsEnabled) {
mainScope.launch {
val email = getSupportFeedbackEmailUseCase()
emailSender.send(
email = EmailSender.Email(
address = email.address,
subject = email.subject,
message = email.message,
attachment = email.file,
),
)
}
} else {
store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail()))
}
}
private fun navigateToAppSettings() {
Analytics.send(Settings.ButtonAppSettings())

View file

@ -10,6 +10,7 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
import com.tangem.tap.features.main.model.MainScreenState
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
@ -17,12 +18,14 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class MainViewModel @Inject constructor(
private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase,
private val listenToFlipsUseCase: ListenToFlipsUseCase,
private val reduxNavController: ReduxNavController,
private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase,
private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase,
private val dispatchers: CoroutineDispatcherProvider,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel(), MainIntents {
@ -41,6 +44,10 @@ internal class MainViewModel @Inject constructor(
observeFlips()
displayBalancesHidingStatusToast()
displayHiddenBalancesModalNotification()
viewModelScope.launch(dispatchers.main) {
deleteDeprecatedLogsUseCase()
}
}
private fun updateAppCurrencies() {

View file

@ -59,9 +59,10 @@ internal class DefaultFeedbackRepository(
override suspend fun getAppLogs(): List<AppLogModel> {
return appPreferencesStore.getObjectMap<String>(key = PreferencesKeys.APP_LOGS_KEY)
.map { AppLogModel(timestamp = it.key.toLong(), message = it.value) }
.sortedBy(AppLogModel::timestamp)
}
override suspend fun createLogFile(logs: List<String>): File? {
override suspend fun createLogFile(logs: String): File? {
return try {
val file = File(context.filesDir, LOGS_FILE)
file.delete()
@ -69,7 +70,7 @@ internal class DefaultFeedbackRepository(
val stringWriter = StringWriter()
logs.forEach(stringWriter::append)
stringWriter.append(logs)
val fileWriter = FileWriter(file)
fileWriter.write(stringWriter.toString())

View file

@ -44,22 +44,21 @@ internal class DefaultSettingsRepository(
}
}
override suspend fun deleteDeprecatedLogs() {
val threeDaysAgo = getThreeDaysAgoTimestamp()
override suspend fun deleteDeprecatedLogs(maxSize: Int) {
appPreferencesStore.editData { preferences ->
val savedLogs = preferences.getObjectMap<String>(PreferencesKeys.APP_LOGS_KEY)
var sum = 0
preferences.setObjectMap(
key = PreferencesKeys.APP_LOGS_KEY,
value = savedLogs.filterKeys { it.toLong() > threeDaysAgo },
value = savedLogs.entries
.sortedBy(Map.Entry<String, String>::key)
.takeLastWhile {
sum += it.value.length
sum < maxSize
}
.associate { it.key to it.value },
)
}
}
private fun getThreeDaysAgoTimestamp(): Long = DateTime.now().minusDays(THREE_DAYS).millis
private companion object {
const val THREE_DAYS = 3
}
}

View file

@ -81,15 +81,31 @@ class GetSupportFeedbackEmailUseCase(
}.toString()
}
private suspend fun getLogs(): List<String> {
return feedbackRepository.getAppLogs().map {
val date = dateFormatter.print(DateTime(it.timestamp))
"$date: ${it.message}\n"
private suspend fun getLogs(): String {
val builder = StringBuilder()
var sum = 0
val appLogs = feedbackRepository.getAppLogs()
for (i in appLogs.lastIndex downTo 0) {
val log = appLogs[i]
val date = dateFormatter.print(DateTime(log.timestamp))
val formattedLog = "$date: ${log.message}\n"
sum += formattedLog.length
if (sum < GMAIL_MAX_FILE_SIZE) {
builder.insert(0, formattedLog)
} else {
break
}
}
return builder.toString()
}
private companion object {
const val START2COIN_SUPPORT_EMAIL = "cardsupport@start2coin.com"
const val TANGEM_SUPPORT_EMAIL = "support@tangem.com"
const val GMAIL_MAX_FILE_SIZE = 24_900_000 // ≈ 25 MB
}
}

View file

@ -16,5 +16,5 @@ interface FeedbackRepository {
suspend fun getAppLogs(): List<AppLogModel>
suspend fun createLogFile(logs: List<String>): File?
suspend fun createLogFile(logs: String): File?
}

View file

@ -0,0 +1,16 @@
package com.tangem.domain.settings
import com.tangem.domain.settings.repositories.SettingsRepository
class DeleteDeprecatedLogsUseCase(
private val settingsRepository: SettingsRepository,
) {
suspend operator fun invoke() {
settingsRepository.deleteDeprecatedLogs(maxSize = MAX_LOGS_SIZE)
}
private companion object {
const val MAX_LOGS_SIZE = 24_900_000 // ≈ 25 MB
}
}

View file

@ -12,5 +12,5 @@ interface SettingsRepository {
suspend fun updateAppLogs(message: String)
@Throws
suspend fun deleteDeprecatedLogs()
suspend fun deleteDeprecatedLogs(maxSize: Int)
}