Skip to content

Commit

Permalink
Merge pull request #1052 from tari-project/development
Browse files Browse the repository at this point in the history
feat: align master with the latest 0.25.2 release
  • Loading branch information
igordanilcenko authored Feb 23, 2024
2 parents 5721153 + 1f408a3 commit ab9b817
Show file tree
Hide file tree
Showing 60 changed files with 870 additions and 533 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-sentry (1.17.0)
fastlane-plugin-sentry (1.19.0)
os (~> 1.1, >= 1.1.4)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.54.0)
Expand Down
12 changes: 8 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ android {
flavorDimensions.add("privacy-mode")

buildTypes {
loadSentryProps()
def secretProperties = loadSecretProps()

debug {
Expand All @@ -57,8 +58,9 @@ android {
debugSymbolLevel "FULL"
}
sentry {
autoUpload.set(false)
autoUploadProguardMapping.set(false)
autoUpload.set(true)
uploadNativeSymbols.set(true)
includeNativeSources.set(true)
}
}
}
Expand Down Expand Up @@ -288,8 +290,10 @@ dependencies {

implementation "com.dropbox.core:dropbox-core-sdk:5.4.6"

// implementation "com.github.yat-labs:yat-lib-android:0.1.42"
implementation project(":yatlib")
// It's recommended to use the latest version of the library from the JitPack repository,
// but you can also use a locally build version of the library by using the ":yatlib" module and `yat-lib-debug-snapshot.aar` file with your build
implementation "com.github.tari-project:yat-lib-android:0.4.1"
// implementation project(":yatlib")

// custom libraries region
// flex layout
Expand Down
Original file line number Diff line number Diff line change
@@ -1,213 +1,95 @@
package com.tari.android.wallet.application.securityStage

import android.text.SpannableString
import android.text.Spanned
import com.tari.android.wallet.R
import com.tari.android.wallet.application.securityStage.StagedWalletSecurityManager.StagedSecurityEffect.NoStagedSecurityPopUp
import com.tari.android.wallet.application.securityStage.StagedWalletSecurityManager.StagedSecurityEffect.ShowStagedSecurityPopUp
import com.tari.android.wallet.data.sharedPrefs.securityStages.DisabledTimestampsDto
import com.tari.android.wallet.data.sharedPrefs.securityStages.SecurityStagesRepository
import com.tari.android.wallet.data.sharedPrefs.securityStages.WalletSecurityStage
import com.tari.android.wallet.data.sharedPrefs.securityStages.modules.SecurityStageHeadModule
import com.tari.android.wallet.event.EventBus
import com.tari.android.wallet.extension.addTo
import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsSharedRepository
import com.tari.android.wallet.extension.isAfterNow
import com.tari.android.wallet.model.BalanceInfo
import com.tari.android.wallet.model.MicroTari
import com.tari.android.wallet.ui.common.CommonViewModel
import com.tari.android.wallet.ui.dialog.modular.DialogArgs
import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs
import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule
import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule
import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle
import com.tari.android.wallet.ui.dialog.modular.modules.space.SpaceModule
import com.tari.android.wallet.ui.fragment.settings.backup.backupOnboarding.item.BackupOnboardingArgs
import com.tari.android.wallet.ui.fragment.settings.backup.backupOnboarding.module.BackupOnboardingFlowItemModule
import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupSettingsRepository
import yat.android.ui.extension.HtmlHelper
import java.math.BigDecimal
import java.util.Calendar
import javax.inject.Inject

class StagedWalletSecurityManager : CommonViewModel() {

@Inject
lateinit var securityStagesRepository: SecurityStagesRepository

@Inject
lateinit var backupPrefsRepository: BackupSettingsRepository

init {
component.inject(this)

EventBus.balanceUpdates.publishSubject.subscribe { handleChange(it) }.addTo(compositeDisposable)
}

val hasVerifiedSeedPhrase
import javax.inject.Singleton

val MINIMUM_STAGE_ONE_BALANCE = MicroTari((BigDecimal.valueOf(10_000) * MicroTari.precisionValue).toBigInteger())
val STAGE_TWO_THRESHOLD_BALANCE = MicroTari((BigDecimal.valueOf(100_000) * MicroTari.precisionValue).toBigInteger())
val SAFE_HOT_WALLET_BALANCE = MicroTari((BigDecimal.valueOf(500_000_000) * MicroTari.precisionValue).toBigInteger())
val MAX_HOT_WALLET_BALANCE = MicroTari((BigDecimal.valueOf(1_000_000_000) * MicroTari.precisionValue).toBigInteger())

@Singleton
class StagedWalletSecurityManager @Inject constructor(
private val securityStagesRepository: SecurityStagesRepository,
private val backupPrefsRepository: BackupSettingsRepository,
private val tariSettingsSharedRepository: TariSettingsSharedRepository,
) {
private val hasVerifiedSeedPhrase
get() = tariSettingsSharedRepository.hasVerifiedSeedWords

val isBackupOn
private val isBackupOn
get() = backupPrefsRepository.getOptionList.any { it.isEnable }

val isBackupPasswordSet
private val isBackupPasswordSet
get() = !backupPrefsRepository.backupPassword.isNullOrEmpty()

val disabledTimestampSinceNow
private val disabledTimestampSinceNow: Calendar
get() = Calendar.getInstance().also { it.add(Calendar.DAY_OF_YEAR, 7) }

var disabledTimestamps: MutableMap<WalletSecurityStage, Calendar>
private var disabledTimestamps: MutableMap<WalletSecurityStage, Calendar>
get() = securityStagesRepository.disabledTimestamps?.timestamps ?: DisabledTimestampsDto(mutableMapOf()).timestamps
set(value) {
securityStagesRepository.disabledTimestamps = DisabledTimestampsDto(value)
}

private fun updateTimestamp(securityStage: WalletSecurityStage) {
val newTimestamp = disabledTimestampSinceNow
disabledTimestamps = disabledTimestamps.also { it[securityStage] = newTimestamp }
}

private fun handleChange(balance: BalanceInfo) {
val securityStage = getSecurityStages(balance) ?: return
/**
* Check the current security stage based on the balance and the user's security settings.
*/
fun handleBalanceChange(balance: BalanceInfo): StagedSecurityEffect {
val securityStage = checkSecurityStage(balance) ?: return NoStagedSecurityPopUp
//todo Stage 3 is currently disabled
if (securityStage == WalletSecurityStage.Stage3) return
if (isActionDisabled(securityStage)) return
if (securityStage == WalletSecurityStage.Stage3) return NoStagedSecurityPopUp
if (isActionDisabled(securityStage)) return NoStagedSecurityPopUp

updateTimestamp(securityStage)
showPopUp(securityStage)

return ShowStagedSecurityPopUp(securityStage)
}

private fun getSecurityStages(balanceInfo: BalanceInfo): WalletSecurityStage? {
private fun updateTimestamp(securityStage: WalletSecurityStage) {
val newTimestamp = disabledTimestampSinceNow
disabledTimestamps = disabledTimestamps.also { it[securityStage] = newTimestamp }
}

/**
* Returns null if no security stage is required.
*/
private fun checkSecurityStage(balanceInfo: BalanceInfo): WalletSecurityStage? {
val balance = balanceInfo.availableBalance

return when {
balance >= minimumStageOneBalance && !hasVerifiedSeedPhrase -> WalletSecurityStage.Stage1A
balance >= minimumStageOneBalance && !isBackupOn -> WalletSecurityStage.Stage1B
balance >= stageTwoThresholdBalance && !isBackupPasswordSet -> WalletSecurityStage.Stage2
balance >= safeHotWalletBalance -> WalletSecurityStage.Stage3
balance >= MINIMUM_STAGE_ONE_BALANCE && !hasVerifiedSeedPhrase -> WalletSecurityStage.Stage1A
balance >= MINIMUM_STAGE_ONE_BALANCE && !isBackupOn -> WalletSecurityStage.Stage1B
balance >= STAGE_TWO_THRESHOLD_BALANCE && !isBackupPasswordSet -> WalletSecurityStage.Stage2
balance >= SAFE_HOT_WALLET_BALANCE -> WalletSecurityStage.Stage3
else -> null
}
}

private fun isActionDisabled(securityStage: WalletSecurityStage): Boolean {
val timestamp = disabledTimestamps[securityStage] ?: return false
if (timestamp < Calendar.getInstance()) {
if (timestamp.isAfterNow()) {
return true
}

disabledTimestamps = disabledTimestamps.also { it.remove(securityStage) }
return false
}

fun showPopUp(securityStage: WalletSecurityStage) {
when (securityStage) {
WalletSecurityStage.Stage1A -> showStage1APopUp()
WalletSecurityStage.Stage1B -> showStage1BPopUp()
WalletSecurityStage.Stage2 -> showStage2PopUp()
WalletSecurityStage.Stage3 -> showStage3PopUp()
}
}

private fun showStage1APopUp() {
showPopup(
BackupOnboardingArgs.StageOne(resourceManager, this::openStage1),
resourceManager.getString(R.string.staged_wallet_security_stages_1a_title),
resourceManager.getString(R.string.staged_wallet_security_stages_1a_subtitle),
null,
resourceManager.getString(R.string.staged_wallet_security_stages_1a_buttons_positive),
HtmlHelper.getSpannedText(resourceManager.getString(R.string.staged_wallet_security_stages_1a_message))
) { openStage1() }
}

private fun openStage1() {
dismissDialog.postValue(Unit)
tariNavigator?.let {
it.toAllSettings()
it.toBackupSettings(false)
it.toWalletBackupWithRecoveryPhrase()
}
}

private fun showStage1BPopUp() {
showPopup(
BackupOnboardingArgs.StageTwo(resourceManager, this::openStage1B),
resourceManager.getString(R.string.staged_wallet_security_stages_1b_title),
resourceManager.getString(R.string.staged_wallet_security_stages_1b_subtitle),
resourceManager.getString(R.string.staged_wallet_security_stages_1b_message),
resourceManager.getString(R.string.staged_wallet_security_stages_1b_buttons_positive),
) { openStage1() }
}

private fun openStage1B() {
dismissDialog.postValue(Unit)
tariNavigator?.let {
it.toAllSettings()
it.toBackupSettings(true)
}
}

private fun showStage2PopUp() {
showPopup(
BackupOnboardingArgs.StageThree(resourceManager, this::openStage2),
resourceManager.getString(R.string.staged_wallet_security_stages_2_title),
resourceManager.getString(R.string.staged_wallet_security_stages_2_subtitle),
resourceManager.getString(R.string.staged_wallet_security_stages_2_message),
resourceManager.getString(R.string.staged_wallet_security_stages_2_buttons_positive),
) { openStage2() }
}

private fun openStage2() {
dismissDialog.postValue(Unit)
tariNavigator?.let {
it.toAllSettings()
it.toBackupSettings(false)
it.toChangePassword()
}
}

private fun showStage3PopUp() {
showPopup(
BackupOnboardingArgs.StageFour(resourceManager, this::openStage3),
resourceManager.getString(R.string.staged_wallet_security_stages_3_title),
resourceManager.getString(R.string.staged_wallet_security_stages_3_subtitle),
resourceManager.getString(R.string.staged_wallet_security_stages_3_message),
resourceManager.getString(R.string.staged_wallet_security_stages_3_buttons_positive),
) { openStage3() }
}

private fun openStage3() {
dismissDialog.postValue(Unit)
//todo for future
}

private fun showPopup(
stage: BackupOnboardingArgs,
titleEmoji: String,
title: String,
body: String?,
positiveButtonTitle: String,
bodyHtml: Spanned? = null,
positiveAction: () -> Unit = {},
) {
val args = ModularDialogArgs(
DialogArgs(), listOf(
SecurityStageHeadModule(titleEmoji, title) { showBackupInfo(stage) },
BodyModule(body, bodyHtml?.let { SpannableString(it) }),
ButtonModule(positiveButtonTitle, ButtonStyle.Normal) { positiveAction.invoke() },
ButtonModule(resourceManager.getString(R.string.staged_wallet_security_buttons_remind_me_later), ButtonStyle.Close)
)
)
modularDialog.postValue(args)
}

private fun showBackupInfo(stage: BackupOnboardingArgs) {
val args = ModularDialogArgs(DialogArgs(), listOf(
BackupOnboardingFlowItemModule(stage),
SpaceModule(20)
))
modularDialog.postValue(args)
}

companion object {
val minimumStageOneBalance = MicroTari((BigDecimal.valueOf(10_000) * MicroTari.precisionValue).toBigInteger())
val stageTwoThresholdBalance = MicroTari((BigDecimal.valueOf(100_000) * MicroTari.precisionValue).toBigInteger())
val safeHotWalletBalance = MicroTari((BigDecimal.valueOf(500_000_000) * MicroTari.precisionValue).toBigInteger())
val maxHotWalletBalance = MicroTari((BigDecimal.valueOf(1_000_000_000) * MicroTari.precisionValue).toBigInteger())
sealed class StagedSecurityEffect {
data class ShowStagedSecurityPopUp(val stage: WalletSecurityStage) : StagedSecurityEffect()
object NoStagedSecurityPopUp : StagedSecurityEffect()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,22 @@ class TariSettingsSharedRepository @Inject constructor(sharedPrefs: SharedPrefer
const val isRestoredWallet = "tari_is_restored_wallet"
const val hasVerifiedSeedWords = "tari_has_verified_seed_words"
const val backgroundServiceTurnedOnKey = "tari_background_service_turned_on"
const val screenRecordingTurnedOnKey = "tari_screen_recording_turned_on"
const val isOneSidePaymentEnabledKey = "is_one_side_payment_enabled"
const val themeKey = "tari_theme_key"
}

var isRestoredWallet: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.isRestoredWallet))
var isRestoredWallet: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.isRestoredWallet))

var hasVerifiedSeedWords: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.hasVerifiedSeedWords))
var hasVerifiedSeedWords: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.hasVerifiedSeedWords))

var backgroundServiceTurnedOn: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.backgroundServiceTurnedOnKey), true)
var backgroundServiceTurnedOn: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.backgroundServiceTurnedOnKey), true)

var isOneSidePaymentEnabled: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.isOneSidePaymentEnabledKey), false)
var screenRecordingTurnedOn: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.screenRecordingTurnedOnKey), false)

var currentTheme: TariTheme? by SharedPrefGsonDelegate(sharedPrefs, this, Key.themeKey, TariTheme::class.java, TariTheme.AppBased)
var isOneSidePaymentEnabled: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.isOneSidePaymentEnabledKey), false)

var currentTheme: TariTheme? by SharedPrefGsonDelegate(sharedPrefs, this, Key.themeKey, TariTheme::class.java, TariTheme.AppBased)

fun clear() {
isRestoredWallet = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import com.tari.android.wallet.ui.fragment.settings.logs.LogFilesManager
import com.tari.android.wallet.ui.fragment.settings.logs.logFiles.LogFilesViewModel
import com.tari.android.wallet.ui.fragment.settings.logs.logs.LogsViewModel
import com.tari.android.wallet.ui.fragment.settings.networkSelection.NetworkSelectionViewModel
import com.tari.android.wallet.ui.fragment.settings.screenRecording.ScreenRecordingSettingsViewModel
import com.tari.android.wallet.ui.fragment.settings.themeSelector.ThemeSelectorViewModel
import com.tari.android.wallet.ui.fragment.settings.torBridges.TorBridgesSelectionViewModel
import com.tari.android.wallet.ui.fragment.settings.torBridges.customBridges.CustomTorBridgesViewModel
Expand Down Expand Up @@ -147,6 +148,7 @@ interface ApplicationComponent {
fun inject(viewModel: ThumbnailGIFsViewModel)
fun inject(viewModel: GIFViewModel)
fun inject(viewModel: BackgroundServiceSettingsViewModel)
fun inject(viewModel: ScreenRecordingSettingsViewModel)
fun inject(viewModel: ConnectionIndicatorViewModel)
fun inject(viewModel: ChooseRestoreOptionViewModel)
fun inject(viewModel: EnterRestorationPasswordViewModel)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.tari.android.wallet.event

import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.receiveAsFlow

class EffectChannelFlow<Effect : Any> {
private val channel: Channel<Effect> = Channel(Channel.CONFLATED)
val flow: Flow<Effect> = channel.receiveAsFlow()

suspend fun send(effect: Effect) {
channel.send(effect)
}
}
2 changes: 0 additions & 2 deletions app/src/main/java/com/tari/android/wallet/event/EventBus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ object EventBus : GeneralEventBus() {

val walletRestorationState = BehaviorEventBus<WalletRestorationResult>()

val balanceUpdates = BehaviorEventBus<BalanceInfo>()

init {
baseNodeSyncState.post(BaseNodeSyncState.Syncing)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.tari.android.wallet.extension

/**
* These extensions can be used to write casts in a chained way.
* E.g. something.castTo<Other>().doSomething() instead of (something as Other).doSomething()
*/
@Suppress("UNCHECKED_CAST")
fun <T> Any.castTo(): T = this as T

// Simply returning `this as? T` does not work because the Kotlin compiler internally
// then still just casts it to the other type without checks
inline fun <reified T> Any.safeCastTo(): T? = if (this is T) this else null
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@ fun Date.txFormattedDate(): String {
return SimpleDateFormat("MMMM d'$indicator' yyyy 'at' h:mm a", Locale.ENGLISH)
.format(this)
}

fun Calendar.isAfterNow(): Boolean {
return this.after(Calendar.getInstance())
}
Loading

0 comments on commit ab9b817

Please sign in to comment.