diff --git a/api/src/main/java/com/getcode/manager/GiftCardManager.kt b/api/src/main/java/com/getcode/manager/GiftCardManager.kt new file mode 100644 index 000000000..bba751e86 --- /dev/null +++ b/api/src/main/java/com/getcode/manager/GiftCardManager.kt @@ -0,0 +1,19 @@ +package com.getcode.manager + +import android.content.Context +import com.getcode.crypt.MnemonicPhrase +import com.getcode.solana.organizer.GiftCardAccount +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class GiftCardManager @Inject constructor( + @ApplicationContext private val context: Context +) { + fun createGiftCard(mnemonic: MnemonicPhrase? = null): GiftCardAccount { + return GiftCardAccount.newInstance(context, mnemonic) + } + + fun getEntropy(giftCard: GiftCardAccount): String { + return giftCard.mnemonicPhrase.getBase58EncodedEntropy(context) + } +} \ No newline at end of file diff --git a/api/src/main/java/com/getcode/manager/MnemonicManager.kt b/api/src/main/java/com/getcode/manager/MnemonicManager.kt new file mode 100644 index 000000000..5df4e6421 --- /dev/null +++ b/api/src/main/java/com/getcode/manager/MnemonicManager.kt @@ -0,0 +1,34 @@ +package com.getcode.manager + +import android.content.Context +import com.getcode.crypt.MnemonicCode +import com.getcode.crypt.MnemonicPhrase +import com.getcode.ed25519.Ed25519.KeyPair +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class MnemonicManager @Inject constructor( + @ApplicationContext private val context: Context +) { + fun fromCashLink(cashLink: String): MnemonicPhrase { + return MnemonicPhrase.fromEntropyB58(context, cashLink) + } + + fun fromEntropyBase64(entropy: String): MnemonicPhrase { + return MnemonicPhrase.fromEntropyB64(context, entropy) + } + + fun getKeyPair(entropy: String): KeyPair { + return fromEntropyBase64(entropy).getSolanaKeyPair(context) + } + + fun getKeyPair(mnemonicPhrase: MnemonicPhrase): KeyPair { + return mnemonicPhrase.getSolanaKeyPair(context) + } + + fun getEncodedBase64(mnemonicPhrase: MnemonicPhrase): String { + return mnemonicPhrase.getBase64EncodedEntropy(context) + } + + val mnemonicCode: MnemonicCode = context.resources.let(::MnemonicCode) +} \ No newline at end of file diff --git a/api/src/main/java/com/getcode/media/MediaScanner.kt b/api/src/main/java/com/getcode/media/MediaScanner.kt new file mode 100644 index 000000000..90f03ab19 --- /dev/null +++ b/api/src/main/java/com/getcode/media/MediaScanner.kt @@ -0,0 +1,15 @@ +package com.getcode.media + +import android.content.Context +import android.media.MediaScannerConnection +import dagger.hilt.android.qualifiers.ApplicationContext +import java.io.File +import javax.inject.Inject + +class MediaScanner @Inject constructor( + @ApplicationContext private val context: Context +) { + fun scan(directory: File) { + MediaScannerConnection.scanFile(context, arrayOf(directory.toString()), null, null) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/getcode/App.kt b/app/src/main/java/com/getcode/App.kt index 23cab1729..ffa02ff71 100644 --- a/app/src/main/java/com/getcode/App.kt +++ b/app/src/main/java/com/getcode/App.kt @@ -22,7 +22,6 @@ class App : Application() { override fun onCreate() { super.onCreate() - instance = this CashBillAssets.load(this) @@ -63,13 +62,4 @@ class App : Application() { Bugsnag.start(this) } } - - companion object { - private lateinit var instance: Application - - @Deprecated(message = "Anti-pattern; inject @ApplicationContext where needed. Convert objects to classes.") - fun getInstance(): Application { - return instance - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/getcode/util/AccountUtils.kt b/app/src/main/java/com/getcode/util/AccountUtils.kt index 0937349a8..4fa1dcec1 100644 --- a/app/src/main/java/com/getcode/util/AccountUtils.kt +++ b/app/src/main/java/com/getcode/util/AccountUtils.kt @@ -78,7 +78,7 @@ object AccountUtils { val subject = SingleSubject.create>() return subject.doOnSubscribe { CoroutineScope(Dispatchers.IO).launch { - val result = getAccountNoActivity(App.getInstance()) + val result = getAccountNoActivity(context) subject.onSuccess(result ?: (null to null)) } } diff --git a/app/src/main/java/com/getcode/util/Context.kt b/app/src/main/java/com/getcode/util/Context.kt new file mode 100644 index 000000000..6e6089a1b --- /dev/null +++ b/app/src/main/java/com/getcode/util/Context.kt @@ -0,0 +1,31 @@ +package com.getcode.util + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.Settings +import androidx.core.content.ContextCompat +import com.getcode.BuildConfig +import com.getcode.utils.makeE164 + +fun Context.launchAppSettings() { + ContextCompat.startActivity( + this, + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) + flags = Intent.FLAG_ACTIVITY_NEW_TASK + }, + null + ) +} + +fun Context.launchSmsIntent(phoneValue: String, message: String) { + val uri: Uri = Uri.parse("smsto:${phoneValue.makeE164()}") + val intent = Intent(Intent.ACTION_SENDTO, uri) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.putExtra( + "sms_body", + message + ) + ContextCompat.startActivity(this, intent, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/getcode/util/IntentLauncher.kt b/app/src/main/java/com/getcode/util/IntentLauncher.kt new file mode 100644 index 000000000..730a2fb68 --- /dev/null +++ b/app/src/main/java/com/getcode/util/IntentLauncher.kt @@ -0,0 +1,17 @@ +package com.getcode.util + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class IntentLauncher @Inject constructor( + @ApplicationContext private val context: Context +) { + fun launchAppSettings() { + context.launchAppSettings() + } + + fun launchSmsIntent(phoneValue: String, message: String) { + context.launchSmsIntent(phoneValue, message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/getcode/util/IntentUtils.kt b/app/src/main/java/com/getcode/util/IntentUtils.kt deleted file mode 100644 index 380c47307..000000000 --- a/app/src/main/java/com/getcode/util/IntentUtils.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.getcode.util - -import android.content.Intent -import android.net.Uri -import android.provider.Settings -import androidx.core.content.ContextCompat -import com.getcode.App -import com.getcode.BuildConfig -import com.getcode.utils.makeE164 - -object IntentUtils { - fun launchAppSettings() { - ContextCompat.startActivity( - App.getInstance(), - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) - flags = Intent.FLAG_ACTIVITY_NEW_TASK - }, - null - ) - } - - fun launchSmsIntent(phoneValue: String, message: String) { - val uri: Uri = Uri.parse("smsto:${phoneValue.makeE164()}") - val intent = Intent(Intent.ACTION_SENDTO, uri) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - intent.putExtra( - "sms_body", - message - ) - ContextCompat.startActivity(App.getInstance(), intent, null) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/getcode/view/login/AccessKey.kt b/app/src/main/java/com/getcode/view/login/AccessKey.kt index 808c8f6cd..5b05ab117 100644 --- a/app/src/main/java/com/getcode/view/login/AccessKey.kt +++ b/app/src/main/java/com/getcode/view/login/AccessKey.kt @@ -54,7 +54,6 @@ import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.navigation.screens.LoginArgs import com.getcode.theme.CodeTheme import com.getcode.theme.White -import com.getcode.util.IntentUtils import com.getcode.ui.utils.measured import com.getcode.ui.components.AccessKeySelectionContainer import com.getcode.ui.components.ButtonState @@ -65,6 +64,7 @@ import com.getcode.ui.components.getPermissionLauncher import com.getcode.ui.components.rememberSelectionState import com.getcode.ui.utils.addIf import com.getcode.ui.utils.debugBounds +import com.getcode.util.launchAppSettings @OptIn(ExperimentalComposeUiApi::class) @Preview @@ -93,7 +93,7 @@ fun AccessKey( message = context.getString(R.string.error_description_failedToSave), type = TopBarManager.TopBarMessageType.ERROR, secondaryText = context.getString(R.string.action_openSettings), - secondaryAction = { IntentUtils.launchAppSettings() } + secondaryAction = { context.launchAppSettings() } ) ) } diff --git a/app/src/main/java/com/getcode/view/login/AccessKeyViewModel.kt b/app/src/main/java/com/getcode/view/login/AccessKeyViewModel.kt index bbb7101af..95044dbe7 100644 --- a/app/src/main/java/com/getcode/view/login/AccessKeyViewModel.kt +++ b/app/src/main/java/com/getcode/view/login/AccessKeyViewModel.kt @@ -3,24 +3,24 @@ package com.getcode.view.login import android.Manifest import android.annotation.SuppressLint import android.os.Build -import dagger.hilt.android.lifecycle.HiltViewModel -import com.getcode.App -import com.getcode.crypt.MnemonicPhrase import com.getcode.analytics.AnalyticsService import com.getcode.manager.AuthManager -import com.getcode.navigation.screens.CodeLoginPermission +import com.getcode.media.MediaScanner +import com.getcode.manager.MnemonicManager import com.getcode.navigation.core.CodeNavigator +import com.getcode.navigation.screens.CodeLoginPermission import com.getcode.navigation.screens.HomeScreen import com.getcode.navigation.screens.LoginScreen import com.getcode.navigation.screens.PermissionRequestScreen import com.getcode.network.repository.getPublicKeyBase58 import com.getcode.util.permissions.PermissionChecker import com.getcode.util.resources.ResourceHelper -import javax.inject.Inject +import dagger.hilt.android.lifecycle.HiltViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.schedulers.Schedulers import java.util.concurrent.TimeUnit +import javax.inject.Inject @HiltViewModel @@ -28,8 +28,10 @@ class AccessKeyViewModel @Inject constructor( private val authManager: AuthManager, private val analytics: AnalyticsService, private val permissions: PermissionChecker, + private val mnemonicManager: MnemonicManager, resources: ResourceHelper, -) : BaseAccessKeyViewModel(resources) { + mediaScanner: MediaScanner, +) : BaseAccessKeyViewModel(resources, mnemonicManager, mediaScanner) { @SuppressLint("CheckResult") fun onSubmit(navigator: CodeNavigator, isSaveImage: Boolean, isDeepLink: Boolean = false) { val entropyB64 = uiFlow.value.entropyB64 ?: return @@ -69,8 +71,7 @@ class AccessKeyViewModel @Inject constructor( } private fun onComplete(navigator: CodeNavigator, entropyB64: String) { - val owner = MnemonicPhrase.fromEntropyB64(App.getInstance(), entropyB64) - .getSolanaKeyPair(App.getInstance()) + val owner = mnemonicManager.getKeyPair(entropyB64) analytics.createAccount(true, owner.getPublicKeyBase58()) val cameraPermissionDenied = permissions.isDenied(Manifest.permission.CAMERA) diff --git a/app/src/main/java/com/getcode/view/login/BaseAccessKeyViewModel.kt b/app/src/main/java/com/getcode/view/login/BaseAccessKeyViewModel.kt index 2987ef31d..66a9d2fb6 100644 --- a/app/src/main/java/com/getcode/view/login/BaseAccessKeyViewModel.kt +++ b/app/src/main/java/com/getcode/view/login/BaseAccessKeyViewModel.kt @@ -4,21 +4,22 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint import android.graphics.Typeface -import android.media.MediaScannerConnection import android.os.Environment import androidmads.library.qrgenearator.QRGContents import androidmads.library.qrgenearator.QRGEncoder import androidx.core.graphics.applyCanvas import androidx.core.graphics.drawable.toBitmap import androidx.lifecycle.viewModelScope -import com.getcode.App import com.getcode.R -import com.getcode.crypt.MnemonicPhrase +import com.getcode.media.MediaScanner +import com.getcode.manager.MnemonicManager import com.getcode.manager.SessionManager import com.getcode.manager.TopBarManager import com.getcode.network.repository.ApiDeniedException import com.getcode.network.repository.decodeBase64 -import com.getcode.theme.* +import com.getcode.theme.Brand +import com.getcode.theme.Transparent +import com.getcode.theme.White import com.getcode.ui.utils.toAGColor import com.getcode.util.resources.ResourceHelper import com.getcode.utils.ErrorUtils @@ -34,7 +35,8 @@ import java.io.File import java.io.FileOutputStream import java.text.DateFormat import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale data class AccessKeyUiModel( @@ -48,8 +50,11 @@ data class AccessKeyUiModel( val accessKeyCroppedBitmap: Bitmap? = null, ) -abstract class BaseAccessKeyViewModel(private val resources: ResourceHelper) : - BaseViewModel(resources) { +abstract class BaseAccessKeyViewModel( + private val resources: ResourceHelper, + private val mnemonicManager: MnemonicManager, + private val mediaScanner: MediaScanner, +) : BaseViewModel(resources) { val uiFlow = MutableStateFlow(AccessKeyUiModel()) fun init() { @@ -63,7 +68,7 @@ abstract class BaseAccessKeyViewModel(private val resources: ResourceHelper) : fun initWithEntropy(entropyB64: String) { if (uiFlow.value.entropyB64 == entropyB64) return Timber.d("entropy=$entropyB64") - val words = MnemonicPhrase.fromEntropyB64(App.getInstance(), entropyB64).words + val words = mnemonicManager.fromEntropyBase64(entropyB64).words val wordsFormatted = getAccessKeyText(words).joinToString("\n") uiFlow.value = uiFlow.value.copy( @@ -74,8 +79,10 @@ abstract class BaseAccessKeyViewModel(private val resources: ResourceHelper) : CoroutineScope(Dispatchers.IO).launch { val accessKeyBitmap = createBitmapForExport(words = words, entropyB64 = entropyB64) - val accessKeyBitmapDisplay = createBitmapForExport(drawBackground = false, words, entropyB64) - val accessKeyCroppedBitmap = Bitmap.createBitmap(accessKeyBitmapDisplay, 0, 300, 1200, 1450) + val accessKeyBitmapDisplay = + createBitmapForExport(drawBackground = false, words, entropyB64) + val accessKeyCroppedBitmap = + Bitmap.createBitmap(accessKeyBitmapDisplay, 0, 300, 1200, 1450) uiFlow.value = uiFlow.value.copy( accessKeyBitmap = accessKeyBitmap, @@ -123,7 +130,7 @@ abstract class BaseAccessKeyViewModel(private val resources: ResourceHelper) : ErrorUtils.handleError(e) return false } - MediaScannerConnection.scanFile(App.getInstance(), arrayOf(sd.toString()), null, null) + mediaScanner.scan(sd) return true } diff --git a/app/src/main/java/com/getcode/view/login/CameraPermissionCheck.kt b/app/src/main/java/com/getcode/view/login/CameraPermissionCheck.kt index 78c685b4d..d5c45041f 100644 --- a/app/src/main/java/com/getcode/view/login/CameraPermissionCheck.kt +++ b/app/src/main/java/com/getcode/view/login/CameraPermissionCheck.kt @@ -6,9 +6,9 @@ import androidx.compose.ui.platform.LocalContext import com.getcode.App import com.getcode.R import com.getcode.manager.TopBarManager -import com.getcode.util.IntentUtils import com.getcode.ui.components.PermissionCheck import com.getcode.ui.components.getPermissionLauncher +import com.getcode.util.launchAppSettings @Composable fun cameraPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Unit): (Boolean) -> Unit { @@ -21,7 +21,7 @@ fun cameraPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Un message = context.getString(R.string.error_description_cameraAccessRequired), type = TopBarManager.TopBarMessageType.ERROR, secondaryText = context.getString(R.string.action_openSettings), - secondaryAction = { IntentUtils.launchAppSettings() } + secondaryAction = { context.launchAppSettings() } ) ) } diff --git a/app/src/main/java/com/getcode/view/login/NotificationPermissionCheck.kt b/app/src/main/java/com/getcode/view/login/NotificationPermissionCheck.kt index 81ced80c1..04a57acdb 100644 --- a/app/src/main/java/com/getcode/view/login/NotificationPermissionCheck.kt +++ b/app/src/main/java/com/getcode/view/login/NotificationPermissionCheck.kt @@ -7,9 +7,9 @@ import androidx.compose.ui.platform.LocalContext import com.getcode.App import com.getcode.R import com.getcode.manager.TopBarManager -import com.getcode.util.IntentUtils import com.getcode.ui.components.PermissionCheck import com.getcode.ui.components.getPermissionLauncher +import com.getcode.util.launchAppSettings @Composable fun notificationPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Unit): (Boolean) -> Unit { @@ -22,7 +22,7 @@ fun notificationPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) message = context.getString(R.string.permissions_description_push), type = TopBarManager.TopBarMessageType.ERROR, secondaryText = context.getString(R.string.action_openSettings), - secondaryAction = { IntentUtils.launchAppSettings() } + secondaryAction = { context.launchAppSettings() } ) ) } diff --git a/app/src/main/java/com/getcode/view/login/PhoneConfirmViewModel.kt b/app/src/main/java/com/getcode/view/login/PhoneConfirmViewModel.kt index eb8f518d4..2fee88b85 100644 --- a/app/src/main/java/com/getcode/view/login/PhoneConfirmViewModel.kt +++ b/app/src/main/java/com/getcode/view/login/PhoneConfirmViewModel.kt @@ -7,8 +7,8 @@ import com.codeinc.gen.phone.v1.PhoneVerificationService import com.codeinc.gen.user.v1.IdentityService import com.getcode.App import com.getcode.R -import com.getcode.crypt.MnemonicPhrase import com.getcode.ed25519.Ed25519 +import com.getcode.manager.MnemonicManager import com.getcode.manager.SessionManager import com.getcode.manager.TopBarManager import com.getcode.navigation.core.CodeNavigator @@ -64,6 +64,7 @@ class PhoneConfirmViewModel @Inject constructor( private val phoneRepository: PhoneRepository, private val phoneUtils: PhoneUtils, private val resources: ResourceHelper, + private val mnemonicManager: MnemonicManager, ) : BaseViewModel(resources) { companion object { @@ -273,11 +274,10 @@ class PhoneConfirmViewModel @Inject constructor( try { keyPair = if (isNewAccount) { seedB64 = Ed25519.createSeed16().encodeBase64() - MnemonicPhrase.fromEntropyB64(App.getInstance(), seedB64) - .getSolanaKeyPair(App.getInstance()) + mnemonicManager.getKeyPair(seedB64) } else { seedB64 = "" - SessionManager.getOrganizer()?.mnemonic?.getSolanaKeyPair(App.getInstance())!! + SessionManager.getOrganizer()?.mnemonic?.let(mnemonicManager::getKeyPair)!! } } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/com/getcode/view/login/SeedInputViewModel.kt b/app/src/main/java/com/getcode/view/login/SeedInputViewModel.kt index 042e3396a..2b3c38483 100644 --- a/app/src/main/java/com/getcode/view/login/SeedInputViewModel.kt +++ b/app/src/main/java/com/getcode/view/login/SeedInputViewModel.kt @@ -2,13 +2,13 @@ package com.getcode.view.login import android.annotation.SuppressLint import android.app.Activity -import com.getcode.crypt.MnemonicCode import dagger.hilt.android.lifecycle.HiltViewModel import com.getcode.App import com.getcode.R import com.getcode.crypt.MnemonicPhrase import com.getcode.manager.AuthManager import com.getcode.manager.BottomBarManager +import com.getcode.manager.MnemonicManager import com.getcode.manager.TopBarManager import com.getcode.navigation.core.CodeNavigator import com.getcode.navigation.screens.HomeScreen @@ -38,9 +38,10 @@ data class SeedInputUiModel( class SeedInputViewModel @Inject constructor( private val authManager: AuthManager, private val resources: ResourceHelper, + private val mnemonicManager: MnemonicManager, ) : BaseViewModel(resources) { val uiFlow = MutableStateFlow(SeedInputUiModel()) - private val mnemonicCode = MnemonicCode(App.getInstance().resources) + private val mnemonicCode = mnemonicManager.mnemonicCode fun onTextChange(wordsString: String) { val isLoading = uiFlow.value.isLoading @@ -68,7 +69,7 @@ class SeedInputViewModel @Inject constructor( CoroutineScope(Dispatchers.IO).launch { val entropyB64: String try { - entropyB64 = mnemonic.getBase64EncodedEntropy(App.getInstance()) + entropyB64 = mnemonicManager.getEncodedBase64(mnemonic) } catch (e: Exception) { showError(navigator) return@launch diff --git a/app/src/main/java/com/getcode/view/main/account/AccountAccessKeyViewModel.kt b/app/src/main/java/com/getcode/view/main/account/AccountAccessKeyViewModel.kt index f3a55f932..a7be50011 100644 --- a/app/src/main/java/com/getcode/view/main/account/AccountAccessKeyViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/account/AccountAccessKeyViewModel.kt @@ -1,6 +1,8 @@ package com.getcode.view.main.account import android.annotation.SuppressLint +import com.getcode.media.MediaScanner +import com.getcode.manager.MnemonicManager import com.getcode.navigation.core.CodeNavigator import com.getcode.util.resources.ResourceHelper import com.getcode.view.login.BaseAccessKeyViewModel @@ -14,7 +16,9 @@ import javax.inject.Inject @HiltViewModel class AccountAccessKeyViewModel @Inject constructor( resources: ResourceHelper, -) : BaseAccessKeyViewModel(resources) { + mnemonicManager: MnemonicManager, + mediaScanner: MediaScanner, +) : BaseAccessKeyViewModel(resources, mnemonicManager, mediaScanner) { @SuppressLint("CheckResult") fun onSubmit(navigator: CodeNavigator) { Completable.create { diff --git a/app/src/main/java/com/getcode/view/main/account/BackupKey.kt b/app/src/main/java/com/getcode/view/main/account/BackupKey.kt index 44e89c17f..27604a4f7 100644 --- a/app/src/main/java/com/getcode/view/main/account/BackupKey.kt +++ b/app/src/main/java/com/getcode/view/main/account/BackupKey.kt @@ -43,7 +43,6 @@ import com.getcode.manager.TopBarManager import com.getcode.navigation.core.LocalCodeNavigator import com.getcode.theme.BrandLight import com.getcode.theme.CodeTheme -import com.getcode.util.IntentUtils import com.getcode.ui.utils.measured import com.getcode.ui.components.AccessKeySelectionContainer import com.getcode.ui.components.ButtonState @@ -54,6 +53,7 @@ import com.getcode.ui.components.getPermissionLauncher import com.getcode.ui.components.rememberSelectionState import com.getcode.ui.utils.addIf import com.getcode.ui.utils.debugBounds +import com.getcode.util.launchAppSettings @Composable fun BackupKey( @@ -77,7 +77,7 @@ fun BackupKey( message = context.getString(R.string.error_description_failedToSave), type = TopBarManager.TopBarMessageType.ERROR, secondaryText = context.getString(R.string.action_openSettings), - secondaryAction = { IntentUtils.launchAppSettings() } + secondaryAction = { context.launchAppSettings() } ) ) } diff --git a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt index 166ea12c8..270c4d59d 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeScan.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeScan.kt @@ -119,26 +119,17 @@ fun HomeScreen( val context = LocalContext.current LaunchedEffect(homeViewModel) { homeViewModel.eventFlow - .filterIsInstance() - .map { it.url } .onEach { - context.startActivity( - Intent( - Intent.ACTION_VIEW, - it.toUri() - ).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK + when (it) { + HomeEvent.PresentTipEntry -> { + navigator.show(EnterTipModal()) } - ) - }.launchIn(this) - } - - LaunchedEffect(homeViewModel) { - homeViewModel.eventFlow - .filterIsInstance() - .onEach { - navigator.show(EnterTipModal()) - }.launchIn(this) + is HomeEvent.SendIntent -> { + context.startActivity(it.intent) + } + } + } + .launchIn(this) } LaunchedEffect(navigator) { diff --git a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt index 28df21161..54939e034 100644 --- a/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/home/HomeViewModel.kt @@ -2,19 +2,19 @@ package com.getcode.view.main.home import android.annotation.SuppressLint import android.app.Activity -import android.content.Context import android.content.Intent import android.view.WindowManager +import androidx.core.net.toUri import androidx.lifecycle.viewModelScope import cafe.adriel.voyager.core.model.ScreenModel -import com.getcode.App import com.getcode.BuildConfig import com.getcode.R import com.getcode.analytics.AnalyticsManager import com.getcode.analytics.AnalyticsService -import com.getcode.crypt.MnemonicPhrase import com.getcode.manager.AuthManager import com.getcode.manager.BottomBarManager +import com.getcode.manager.GiftCardManager +import com.getcode.manager.MnemonicManager import com.getcode.manager.SessionManager import com.getcode.manager.TopBarManager import com.getcode.model.BuyModuleFeature @@ -87,7 +87,6 @@ import com.getcode.vendor.Base58 import com.getcode.view.BaseViewModel import com.kik.kikx.models.ScannableKikCode import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers @@ -151,8 +150,8 @@ data class HomeUiModel( ) sealed interface HomeEvent { - data class OpenUrl(val url: String) : HomeEvent data object PresentTipEntry : HomeEvent + data class SendIntent(val intent: Intent): HomeEvent } enum class RestrictionType { @@ -164,7 +163,6 @@ enum class RestrictionType { @SuppressLint("CheckResult") @HiltViewModel class HomeViewModel @Inject constructor( - @ApplicationContext private val context: Context, private val client: Client, private val sendTransactionRepository: SendTransactionRepository, private val receiveTransactionRepository: ReceiveTransactionRepository, @@ -180,6 +178,8 @@ class HomeViewModel @Inject constructor( private val vibrator: Vibrator, private val currencyUtils: CurrencyUtils, private val exchange: Exchange, + private val giftCardManager: GiftCardManager, + private val mnemonicManager: MnemonicManager, betaFlags: BetaFlagsRepository, features: FeatureRepository, ) : BaseViewModel(resources), ScreenModel { @@ -401,7 +401,7 @@ class HomeViewModel @Inject constructor( uiFlow.update { it.copy( billState = it.billState.copy( - primaryAction = BillState.Action.Send { onRemoteSend(context) }, + primaryAction = BillState.Action.Send { onRemoteSend() }, secondaryAction = BillState.Action.Cancel(::cancelSend) ) ) @@ -684,7 +684,7 @@ class HomeViewModel @Inject constructor( uiFlow.update { val billState = it.billState.copy( bill = Bill.Tip(code), - primaryAction = BillState.Action.Share { onRemoteSend(context) }, + primaryAction = BillState.Action.Share { onRemoteSend() }, secondaryAction = BillState.Action.Done(::cancelSend) ) @@ -997,16 +997,28 @@ class HomeViewModel @Inject constructor( delay(300) if (rejected) { if (!ignoreRedirect) { - request?.cancelUrl?.let { - _eventFlow.emit(HomeEvent.OpenUrl(it)) + request?.cancelUrl?.let { url -> + val intent = Intent( + Intent.ACTION_VIEW, + url.toUri() + ).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + _eventFlow.emit(HomeEvent.SendIntent(intent)) } } } else { showToast(amount, isDeposit = false) if (!ignoreRedirect) { - request?.successUrl?.let { - _eventFlow.emit(HomeEvent.OpenUrl(it)) + request?.successUrl?.let { url -> + val intent = Intent( + Intent.ACTION_VIEW, + url.toUri() + ).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + _eventFlow.emit(HomeEvent.SendIntent(intent)) } } } @@ -1152,12 +1164,24 @@ class HomeViewModel @Inject constructor( viewModelScope.launch { delay(300) if (rejected) { - request?.cancelUrl?.let { - _eventFlow.emit(HomeEvent.OpenUrl(it)) + request?.cancelUrl?.let { url -> + val intent = Intent( + Intent.ACTION_VIEW, + url.toUri() + ).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + _eventFlow.emit(HomeEvent.SendIntent(intent)) } } else { - request?.successUrl?.let { - _eventFlow.emit(HomeEvent.OpenUrl(it)) + request?.successUrl?.let { url -> + val intent = Intent( + Intent.ACTION_VIEW, + url.toUri() + ).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + _eventFlow.emit(HomeEvent.SendIntent(intent)) } } } @@ -1261,23 +1285,23 @@ class HomeViewModel @Inject constructor( @SuppressLint("CheckResult") - fun onRemoteSend(context: Context) { + fun onRemoteSend() { val bill = uiFlow.value.billState.bill when (bill) { is Bill.Cash -> { - shareGiftCard(context) + shareGiftCard() } is Bill.Tip -> { - shareTipCard(context) + shareTipCard() } else -> Unit } } - private fun shareGiftCard(context: Context) { - val giftCard = GiftCardAccount.newInstance(context) + private fun shareGiftCard() { + val giftCard = giftCardManager.createGiftCard() val amount = sendTransactionRepository.getAmount() var loadingIndicatorTimer: TimerTask? = null @@ -1311,12 +1335,12 @@ class HomeViewModel @Inject constructor( } .timeout(15, TimeUnit.SECONDS) .subscribe( - { showRemoteSendDialog(context, giftCard, amount) }, + { showRemoteSendDialog(giftCard, amount) }, ErrorUtils::handleError ) } - private fun shareTipCard(context: Context) = viewModelScope.launch { + private fun shareTipCard() = viewModelScope.launch { val username = tipController.connectedAccount.value?.username ?: return@launch val url = "https://tipcard.getcode.com/x/$username" @@ -1330,7 +1354,8 @@ class HomeViewModel @Inject constructor( val shareIntent = Intent.createChooser(sendIntent, null).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } - context.startActivity(shareIntent) + + _eventFlow.emit(HomeEvent.SendIntent(shareIntent)) } } @@ -1348,12 +1373,10 @@ class HomeViewModel @Inject constructor( } private fun showRemoteSendDialog( - context: Context, giftCard: GiftCardAccount, amount: KinAmount ) { - val url = "https://cash.getcode.com/c/#/e=" + - giftCard.mnemonicPhrase.getBase58EncodedEntropy(context) + val url = "https://cash.getcode.com/c/#/e=" + giftCardManager.getEntropy(giftCard) val text = getString(R.string.subtitle_remoteSendText) .replaceParam( amount.formatted( @@ -1371,9 +1394,11 @@ class HomeViewModel @Inject constructor( val shareIntent = Intent.createChooser(sendIntent, null).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } - context.startActivity(shareIntent) CoroutineScope(Dispatchers.IO).launch { + viewModelScope.launch { + _eventFlow.emit(HomeEvent.SendIntent(shareIntent)) + } delay(2500) BottomBarManager.showMessage( @@ -1387,7 +1412,7 @@ class HomeViewModel @Inject constructor( cancelSend(style = PresentationStyle.Pop) vibrator.vibrate() }, - onNegative = { showRemoteSendDialog(context, giftCard, amount) }, + onNegative = { showRemoteSendDialog(giftCard, amount) }, onTertiary = { cancelRemoteSend(giftCard, amount) cancelSend(style = PresentationStyle.Slide) @@ -1481,10 +1506,8 @@ class HomeViewModel @Inject constructor( openedLinks.add(cashLink) try { - val mnemonic = - MnemonicPhrase.fromEntropyB58(App.getInstance(), cashLink) - val giftCardAccount = - GiftCardAccount.newInstance(App.getInstance(), mnemonic) + val mnemonic = mnemonicManager.fromCashLink(cashLink) + val giftCardAccount = giftCardManager.createGiftCard(mnemonic) viewModelScope.launch { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/getcode/view/main/invites/InvitesSheetViewModel.kt b/app/src/main/java/com/getcode/view/main/invites/InvitesSheetViewModel.kt index 123fb0579..04bc0d04f 100644 --- a/app/src/main/java/com/getcode/view/main/invites/InvitesSheetViewModel.kt +++ b/app/src/main/java/com/getcode/view/main/invites/InvitesSheetViewModel.kt @@ -12,7 +12,7 @@ import com.getcode.network.repository.ContactsRepository import com.getcode.network.repository.IdentityRepository import com.getcode.network.repository.InviteRepository import com.getcode.network.repository.replaceParam -import com.getcode.util.IntentUtils +import com.getcode.util.IntentLauncher import com.getcode.util.resources.ResourceHelper import com.getcode.utils.makeE164 import com.getcode.view.BaseViewModel @@ -53,6 +53,7 @@ class InvitesSheetViewModel @Inject constructor( private val localContactsManager: LocalContactsManager, private val identityRepository: IdentityRepository, private val resources: ResourceHelper, + private val intentLauncher: IntentLauncher, ) : BaseViewModel(resources) { val uiFlow = MutableStateFlow(InvitesSheetUiModel()) @@ -156,7 +157,7 @@ class InvitesSheetViewModel @Inject constructor( R.string.error_description_noInvitesLeft) ) } else { - IntentUtils.launchSmsIntent( + intentLauncher.launchSmsIntent( phoneValue, getString(R.string.subtitle_inviteText).replaceParam("getcode.com/download") ) @@ -185,7 +186,7 @@ class InvitesSheetViewModel @Inject constructor( message = "Please allow Code access to Contacts in Settings.", type = TopBarManager.TopBarMessageType.ERROR, secondaryText = resources.getString(R.string.action_openSettings), - secondaryAction = { IntentUtils.launchAppSettings() } + secondaryAction = { intentLauncher.launchAppSettings() } ) ) }