Updated on 2026-08-14
This commit is contained in:
parent
8d16f06c08
commit
7d9f1f0cf4
10 changed files with 105 additions and 20 deletions
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,7 +146,21 @@ internal class DetailsViewModel(
|
||||||
|
|
||||||
private fun sendFeedback() {
|
private fun sendFeedback() {
|
||||||
Analytics.send(Settings.ButtonSendFeedback())
|
Analytics.send(Settings.ButtonSendFeedback())
|
||||||
store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail()))
|
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() {
|
private fun navigateToAppSettings() {
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue