Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: align master with the latest 0.25.2 release #1052

Merged
merged 20 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
13b867a
Merge pull request #1037 from tari-project/master
igordanilcenko Jan 31, 2024
f1b9718
Increased FFI lib version to v1.0.0-rc.4, app version to 0.25.1, buil…
igordanilcenko Feb 5, 2024
e75786c
Merge pull request #1039 from tari-project/release/0.25.1
igordanilcenko Feb 5, 2024
1cb0b25
Added EffectChannelFlow class
igordanilcenko Feb 6, 2024
dedc448
Added AnyExtensions class
igordanilcenko Feb 6, 2024
8d0446f
Fix the multiple staged wallet security dialog bug
igordanilcenko Feb 6, 2024
fbe1286
Merge pull request #1040 from tari-project/feature/staged_security_di…
igordanilcenko Feb 6, 2024
2e0cb19
Increase FFI lib version to "v1.0.0-rc.5", buildNumber to 287 (#1041)
igordanilcenko Feb 6, 2024
c528a1e
Add onFragmentPopped lifecycle event to CommonActivity and CommonFrag…
igordanilcenko Feb 14, 2024
40c8880
Add launchAndRepeatOnLifecycle() extension method
igordanilcenko Feb 14, 2024
86ff239
Clean up Navigation and TariNavigator classes' code and align code style
igordanilcenko Feb 14, 2024
01c88c5
Renamed ButtonView to SettingsRow
igordanilcenko Feb 14, 2024
e640c53
Add Screen Recording option
igordanilcenko Feb 15, 2024
6234208
Merge pull request #1047 from tari-project/feature/disable_screen_rec…
igordanilcenko Feb 15, 2024
ba89786
Fix crash on exported activity start with a wrong payload
igordanilcenko Feb 19, 2024
4ce62c4
Updated build config code
igordanilcenko Feb 20, 2024
d00a40c
Fix import of the Yat library from JitPack
igordanilcenko Feb 21, 2024
7b8a2ba
Merge pull request #1048 from tari-project/feature/exported_activity_fix
igordanilcenko Feb 22, 2024
4816b5f
Increased version to "0.25.2 (288)" (#1049)
igordanilcenko Feb 22, 2024
1f408a3
feat: release/0.25.2 (#1050)
igordanilcenko Feb 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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