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 { ): ShouldShowSwapPromoTokenUseCase {
return ShouldShowSwapPromoTokenUseCase(swapPromoRepository) 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 androidx.compose.ui.Modifier
import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction 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.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder 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.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.features.details.redux.DetailsState import com.tangem.tap.features.details.redux.DetailsState
@ -24,11 +27,26 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
@Inject @Inject
lateinit var walletsRepository: WalletsRepository lateinit var walletsRepository: WalletsRepository
@Inject
lateinit var feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles
@Inject
lateinit var getSupportFeedbackEmailUseCase: GetSupportFeedbackEmailUseCase
@Inject
lateinit var emailSender: EmailSender
private lateinit var detailsViewModel: DetailsViewModel private lateinit var detailsViewModel: DetailsViewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
detailsViewModel = DetailsViewModel(store, walletsRepository) detailsViewModel = DetailsViewModel(
store = store,
walletsRepository = walletsRepository,
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
getSupportFeedbackEmailUseCase = getSupportFeedbackEmailUseCase,
emailSender = emailSender,
)
Analytics.send(Settings.ScreenOpened()) 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.analytics.Analytics
import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction 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.StateEvent
import com.tangem.core.ui.event.consumedEvent import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.common.util.cardTypesResolver 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.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.addContext 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.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.home.LocaleRegionProvider import com.tangem.tap.features.home.LocaleRegionProvider
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.mainScope
import com.tangem.tap.scope import com.tangem.tap.scope
import com.tangem.tap.userWalletsListManager import com.tangem.tap.userWalletsListManager
import com.tangem.wallet.BuildConfig import com.tangem.wallet.BuildConfig
@ -35,6 +39,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.rekotlin.Store import org.rekotlin.Store
import timber.log.Timber import timber.log.Timber
@ -42,6 +47,9 @@ import timber.log.Timber
internal class DetailsViewModel( internal class DetailsViewModel(
private val store: Store<AppState>, private val store: Store<AppState>,
private val walletsRepository: WalletsRepository, 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)) var detailsScreenState: MutableState<DetailsScreenState> = mutableStateOf(updateState(store.state.detailsState))
@ -138,8 +146,22 @@ internal class DetailsViewModel(
private fun sendFeedback() { private fun sendFeedback() {
Analytics.send(Settings.ButtonSendFeedback()) 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())) store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail()))
} }
}
private fun navigateToAppSettings() { private fun navigateToAppSettings() {
Analytics.send(Settings.ButtonAppSettings()) 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.GetBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
import com.tangem.tap.features.main.model.MainScreenState import com.tangem.tap.features.main.model.MainScreenState
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -17,12 +18,14 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel @HiltViewModel
internal class MainViewModel @Inject constructor( internal class MainViewModel @Inject constructor(
private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase, private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase,
private val listenToFlipsUseCase: ListenToFlipsUseCase, private val listenToFlipsUseCase: ListenToFlipsUseCase,
private val reduxNavController: ReduxNavController, private val reduxNavController: ReduxNavController,
private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase, private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase,
private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel(), MainIntents { ) : ViewModel(), MainIntents {
@ -41,6 +44,10 @@ internal class MainViewModel @Inject constructor(
observeFlips() observeFlips()
displayBalancesHidingStatusToast() displayBalancesHidingStatusToast()
displayHiddenBalancesModalNotification() displayHiddenBalancesModalNotification()
viewModelScope.launch(dispatchers.main) {
deleteDeprecatedLogsUseCase()
}
} }
private fun updateAppCurrencies() { private fun updateAppCurrencies() {

View file

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

View file

@ -44,22 +44,21 @@ internal class DefaultSettingsRepository(
} }
} }
override suspend fun deleteDeprecatedLogs() { override suspend fun deleteDeprecatedLogs(maxSize: Int) {
val threeDaysAgo = getThreeDaysAgoTimestamp()
appPreferencesStore.editData { preferences -> appPreferencesStore.editData { preferences ->
val savedLogs = preferences.getObjectMap<String>(PreferencesKeys.APP_LOGS_KEY) val savedLogs = preferences.getObjectMap<String>(PreferencesKeys.APP_LOGS_KEY)
var sum = 0
preferences.setObjectMap( preferences.setObjectMap(
key = PreferencesKeys.APP_LOGS_KEY, 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() }.toString()
} }
private suspend fun getLogs(): List<String> { private suspend fun getLogs(): String {
return feedbackRepository.getAppLogs().map { val builder = StringBuilder()
val date = dateFormatter.print(DateTime(it.timestamp))
"$date: ${it.message}\n" 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 { private companion object {
const val START2COIN_SUPPORT_EMAIL = "cardsupport@start2coin.com" const val START2COIN_SUPPORT_EMAIL = "cardsupport@start2coin.com"
const val TANGEM_SUPPORT_EMAIL = "support@tangem.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 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) suspend fun updateAppLogs(message: String)
@Throws @Throws
suspend fun deleteDeprecatedLogs() suspend fun deleteDeprecatedLogs(maxSize: Int)
} }