diff --git a/app/src/main/java/pm/gnosis/heimdall/HeimdallApplication.kt b/app/src/main/java/pm/gnosis/heimdall/HeimdallApplication.kt index a722d2d863..05aebb3154 100644 --- a/app/src/main/java/pm/gnosis/heimdall/HeimdallApplication.kt +++ b/app/src/main/java/pm/gnosis/heimdall/HeimdallApplication.kt @@ -21,6 +21,7 @@ class HeimdallApplication : MultiDexApplication() { // Init crash tracker to track unhandled exceptions component.crashTracker().init() RxJavaPlugins.setErrorHandler(Timber::e) + component.shortcutRepository().init() try { LinuxSecureRandom() diff --git a/app/src/main/java/pm/gnosis/heimdall/data/db/daos/GnosisSafeDao.kt b/app/src/main/java/pm/gnosis/heimdall/data/db/daos/GnosisSafeDao.kt index ab83a70d60..8ef2f245f1 100644 --- a/app/src/main/java/pm/gnosis/heimdall/data/db/daos/GnosisSafeDao.kt +++ b/app/src/main/java/pm/gnosis/heimdall/data/db/daos/GnosisSafeDao.kt @@ -38,6 +38,9 @@ interface GnosisSafeDao { @Query("SELECT * FROM ${PendingGnosisSafeDb.TABLE_NAME} WHERE ${PendingGnosisSafeDb.COL_TX_HASH} = :hash") fun loadPendingSafe(hash: BigInteger): Single + @Query("SELECT * FROM ${PendingGnosisSafeDb.TABLE_NAME} WHERE ${PendingGnosisSafeDb.COL_TX_HASH} = :hash") + fun queryPendingSafe(hash: BigInteger): PendingGnosisSafeDb + @Query("DELETE FROM ${PendingGnosisSafeDb.TABLE_NAME} WHERE ${PendingGnosisSafeDb.COL_TX_HASH} = :hash") fun removePendingSafe(hash: BigInteger) @@ -48,8 +51,9 @@ interface GnosisSafeDao { fun updatePendingSafe(pendingSafe: PendingGnosisSafeDb) @Transaction - fun pendingSafeToDeployedSafe(pendingSafe: PendingSafe) { - removePendingSafe(pendingSafe.hash) - insertSafe(GnosisSafeDb(pendingSafe.address, pendingSafe.name)) + fun pendingSafeToDeployedSafe(pendingSafeHash: BigInteger) { + val safe = queryPendingSafe(pendingSafeHash) + removePendingSafe(safe.transactionHash) + insertSafe(GnosisSafeDb(safe.address, safe.name)) } } diff --git a/app/src/main/java/pm/gnosis/heimdall/data/repositories/GnosisSafeRepository.kt b/app/src/main/java/pm/gnosis/heimdall/data/repositories/GnosisSafeRepository.kt index 0784515b79..10409ecfc1 100644 --- a/app/src/main/java/pm/gnosis/heimdall/data/repositories/GnosisSafeRepository.kt +++ b/app/src/main/java/pm/gnosis/heimdall/data/repositories/GnosisSafeRepository.kt @@ -15,7 +15,6 @@ interface GnosisSafeRepository { fun observeSafes(): Flowable> fun observeDeployedSafes(): Flowable> - fun addSafe(address: Solidity.Address, name: String): Completable fun removeSafe(address: Solidity.Address): Completable fun updateSafe(safe: Safe): Completable fun loadSafe(address: Solidity.Address): Single diff --git a/app/src/main/java/pm/gnosis/heimdall/data/repositories/ShortcutRepository.kt b/app/src/main/java/pm/gnosis/heimdall/data/repositories/ShortcutRepository.kt new file mode 100644 index 0000000000..073311949a --- /dev/null +++ b/app/src/main/java/pm/gnosis/heimdall/data/repositories/ShortcutRepository.kt @@ -0,0 +1,6 @@ +package pm.gnosis.heimdall.data.repositories + +interface ShortcutRepository { + + fun init() +} diff --git a/app/src/main/java/pm/gnosis/heimdall/data/repositories/impls/DefaultGnosisSafeRepository.kt b/app/src/main/java/pm/gnosis/heimdall/data/repositories/impls/DefaultGnosisSafeRepository.kt index 16e2d26bbe..851d3126cd 100644 --- a/app/src/main/java/pm/gnosis/heimdall/data/repositories/impls/DefaultGnosisSafeRepository.kt +++ b/app/src/main/java/pm/gnosis/heimdall/data/repositories/impls/DefaultGnosisSafeRepository.kt @@ -66,11 +66,6 @@ class DefaultGnosisSafeRepository @Inject constructor( override fun observePendingSafe(transactionHash: BigInteger): Flowable = safeDao.observePendingSafe(transactionHash).map { it.fromDb() } - override fun addSafe(address: Solidity.Address, name: String) = - Completable.fromCallable { - safeDao.insertSafe(GnosisSafeDb(address, name)) - }.subscribeOn(Schedulers.io())!! - override fun savePendingSafe(transactionHash: BigInteger, name: String?, safeAddress: Solidity.Address, payment: Wei): Completable = Completable.fromAction { safeDao.insertPendingSafe(PendingGnosisSafeDb(transactionHash, name, safeAddress, ERC20Token.ETHER_TOKEN.address, payment.value)) @@ -87,7 +82,7 @@ class DefaultGnosisSafeRepository @Inject constructor( }.subscribeOn(Schedulers.io())!! override fun pendingSafeToDeployedSafe(pendingSafe: PendingSafe): Completable = - Completable.fromCallable { safeDao.pendingSafeToDeployedSafe(pendingSafe) } + Completable.fromCallable { safeDao.pendingSafeToDeployedSafe(pendingSafe.hash) } .andThen(sendSafeCreationPush(pendingSafe.address).onErrorComplete()) .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/pm/gnosis/heimdall/data/repositories/impls/DefaultShortcutRepository.kt b/app/src/main/java/pm/gnosis/heimdall/data/repositories/impls/DefaultShortcutRepository.kt new file mode 100644 index 0000000000..019c3ab9e0 --- /dev/null +++ b/app/src/main/java/pm/gnosis/heimdall/data/repositories/impls/DefaultShortcutRepository.kt @@ -0,0 +1,86 @@ +package pm.gnosis.heimdall.data.repositories.impls + +import android.annotation.TargetApi +import android.content.Context +import android.content.Intent +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.Icon +import android.os.Build +import io.reactivex.rxkotlin.subscribeBy +import io.reactivex.schedulers.Schedulers +import pm.gnosis.blockies.Blockies +import pm.gnosis.blockies.BlockiesPainter +import pm.gnosis.heimdall.R +import pm.gnosis.heimdall.data.repositories.GnosisSafeRepository +import pm.gnosis.heimdall.data.repositories.ShortcutRepository +import pm.gnosis.heimdall.data.repositories.models.Safe +import pm.gnosis.heimdall.di.ApplicationContext +import pm.gnosis.heimdall.ui.tokens.select.SelectTokenActivity +import pm.gnosis.model.Solidity +import pm.gnosis.utils.asEthereumAddressString +import pm.gnosis.utils.nullOnThrow +import timber.log.Timber +import javax.inject.Inject + +class DefaultShortcutRepository @Inject constructor( + @ApplicationContext private val context: Context, + private val safeRepository: GnosisSafeRepository +) : ShortcutRepository { + + + private val iconDimension = context.resources.getDimension(R.dimen.shortcut_icon) + + private val blockiesPainter = BlockiesPainter().apply { + setDimensions(iconDimension, iconDimension) + } + + override fun init() { + shortcutManager()?.let { + safeRepository.observeDeployedSafes() + .subscribeOn(Schedulers.io()) + .map(::createShortcutInfo) + .subscribeBy(onNext = ::setupSafeShortcuts, onError = Timber::e) + } + } + + @TargetApi(Build.VERSION_CODES.N_MR1) + private fun createShortcutInfo(safes: List): List = + // This should only be done if we can use the shortcut manager + shortcutManager()?.let { + safes.map { + ShortcutInfo.Builder(context, it.address.asEthereumAddressString()) + .setShortLabel(context.getString(R.string.send_from_x, it.displayName(context))) + .setLongLabel(context.getString(R.string.send_from_x, it.displayName(context))) + .setIcon(Icon.createWithBitmap(blockiesBitmap(it.address))) + .setIntent(SelectTokenActivity.createIntent(context, it.address).prepare()) + .build() + } + } ?: emptyList() + + private fun blockiesBitmap(address: Solidity.Address) = + Bitmap.createBitmap(iconDimension.toInt(), iconDimension.toInt(), Bitmap.Config.ARGB_8888).apply { + blockiesPainter.draw(Canvas(this), Blockies.fromAddress(address)!!) + } + + private fun Intent.prepare(): Intent = apply { + action = Intent.ACTION_VIEW + flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK + } + + @TargetApi(Build.VERSION_CODES.N_MR1) + private fun setupSafeShortcuts(shortcuts: List) { + // This should only be done if we can use the shortcut manager + shortcutManager()?.let { + it.removeAllDynamicShortcuts() + it.dynamicShortcuts = shortcuts + } + } + + private fun shortcutManager(): ShortcutManager? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) + nullOnThrow { context.getSystemService(ShortcutManager::class.java) } + else null +} diff --git a/app/src/main/java/pm/gnosis/heimdall/di/components/ApplicationComponent.kt b/app/src/main/java/pm/gnosis/heimdall/di/components/ApplicationComponent.kt index f40036d2fc..4dec393632 100644 --- a/app/src/main/java/pm/gnosis/heimdall/di/components/ApplicationComponent.kt +++ b/app/src/main/java/pm/gnosis/heimdall/di/components/ApplicationComponent.kt @@ -5,10 +5,7 @@ import android.arch.lifecycle.ViewModelProvider import android.content.Context import com.squareup.picasso.Picasso import dagger.Component -import pm.gnosis.heimdall.data.repositories.AddressBookRepository -import pm.gnosis.heimdall.data.repositories.GnosisSafeRepository -import pm.gnosis.heimdall.data.repositories.TokenRepository -import pm.gnosis.heimdall.data.repositories.TransactionInfoRepository +import pm.gnosis.heimdall.data.repositories.* import pm.gnosis.heimdall.di.ApplicationContext import pm.gnosis.heimdall.di.modules.ApplicationBindingsModule import pm.gnosis.heimdall.di.modules.ApplicationModule @@ -47,6 +44,7 @@ interface ApplicationComponent { fun accountsRepository(): AccountsRepository fun addressBookRepository(): AddressBookRepository fun safeRepository(): GnosisSafeRepository + fun shortcutRepository(): ShortcutRepository fun tokenRepository(): TokenRepository fun transactionInfoRepository(): TransactionInfoRepository diff --git a/app/src/main/java/pm/gnosis/heimdall/di/modules/ApplicationBindingsModule.kt b/app/src/main/java/pm/gnosis/heimdall/di/modules/ApplicationBindingsModule.kt index 87a35bd8bf..9bfc4541a5 100644 --- a/app/src/main/java/pm/gnosis/heimdall/di/modules/ApplicationBindingsModule.kt +++ b/app/src/main/java/pm/gnosis/heimdall/di/modules/ApplicationBindingsModule.kt @@ -66,6 +66,10 @@ abstract class ApplicationBindingsModule { @Singleton abstract fun bindsSafeRepository(repository: DefaultGnosisSafeRepository): GnosisSafeRepository + @Binds + @Singleton + abstract fun bindsShortcutRepository(repository: DefaultShortcutRepository): ShortcutRepository + @Binds @Singleton abstract fun bindsTokenRepository(repository: DefaultTokenRepository): TokenRepository diff --git a/app/src/main/java/pm/gnosis/heimdall/ui/safe/create/ConfirmSafeRecoveryPhraseViewModel.kt b/app/src/main/java/pm/gnosis/heimdall/ui/safe/create/ConfirmSafeRecoveryPhraseViewModel.kt index b1342b8381..a2cef395f7 100644 --- a/app/src/main/java/pm/gnosis/heimdall/ui/safe/create/ConfirmSafeRecoveryPhraseViewModel.kt +++ b/app/src/main/java/pm/gnosis/heimdall/ui/safe/create/ConfirmSafeRecoveryPhraseViewModel.kt @@ -45,12 +45,12 @@ class ConfirmSafeRecoveryPhraseViewModel @Inject constructor( this.mnemonic = mnemonic this.chromeExtensionAddress = chromeExtensionAddress } - .andThen(loadShuffledWords()) + .andThen(loadWordsToDisplay()) .subscribeOn(Schedulers.io()) - private fun loadShuffledWords(): Single> = + private fun loadWordsToDisplay(): Single> = Single.fromCallable { - mnemonic.words().shuffled() + mnemonic.words().sorted() }.subscribeOn(Schedulers.computation()) override fun isCorrectSequence(words: List): Single> = diff --git a/app/src/main/java/pm/gnosis/heimdall/ui/transactions/create/CreateAssetTransferActivity.kt b/app/src/main/java/pm/gnosis/heimdall/ui/transactions/create/CreateAssetTransferActivity.kt index e965b22bc8..f0d57e97f9 100644 --- a/app/src/main/java/pm/gnosis/heimdall/ui/transactions/create/CreateAssetTransferActivity.kt +++ b/app/src/main/java/pm/gnosis/heimdall/ui/transactions/create/CreateAssetTransferActivity.kt @@ -52,12 +52,14 @@ class CreateAssetTransferActivity : ViewModelActivity disableContinue() is CreateAssetTransferContract.ViewUpdate.TokenInfo -> { - update.value.token.name?.let { - layout_create_asset_transfer_title.text = getString(R.string.transfer_x, it) - } + layout_create_asset_transfer_title.text = getString(R.string.send_x, update.value.token.name) layout_create_asset_transfer_safe_balance.text = update.value.displayString() - layout_create_asset_transfer_input_label.text = update.value.token.symbol ?: "???" + layout_create_asset_transfer_input_label.text = update.value.token.symbol } is CreateAssetTransferContract.ViewUpdate.InvalidInput -> { layout_create_asset_transfer_input_value.setTextColor( diff --git a/app/src/main/res/layout/layout_create_asset_transfer.xml b/app/src/main/res/layout/layout_create_asset_transfer.xml index b6a54610e8..8f7d1544dc 100644 --- a/app/src/main/res/layout/layout_create_asset_transfer.xml +++ b/app/src/main/res/layout/layout_create_asset_transfer.xml @@ -36,7 +36,7 @@ android:gravity="center_vertical" android:minHeight="@dimen/toolbar_height" android:paddingEnd="16dp" - android:text="@string/transfer" + android:text="@string/send" android:textColor="@color/dark_slate_blue" android:textSize="16sp" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/layout_create_safe_intro.xml b/app/src/main/res/layout/layout_create_safe_intro.xml index b6871e5372..513f1d7f44 100644 --- a/app/src/main/res/layout/layout_create_safe_intro.xml +++ b/app/src/main/res/layout/layout_create_safe_intro.xml @@ -116,7 +116,7 @@ android:layout_width="match_parent" android:layout_height="@dimen/toolbar_shadow_size" android:background="@drawable/toolbar_dropshadow" - app:layout_constraintTop_toBottomOf="@id/layout_manage_tokens" + app:layout_constraintTop_toBottomOf="@id/layout_create_safe_intro_title" tools:visibility="visible" /> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 48c451904b..9e0133d2f9 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -10,6 +10,8 @@ 40dp + 48dp + 1dp 32dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2bcd30f394..5370371afb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,7 +26,6 @@ Submit Transaction Cancel Invalid ethereum address - Transfer Error fetching transaction details Removed %1s from this device Edit Safe name @@ -124,7 +123,7 @@ confirmed by extension Submitting transaction Rejecting transaction - Transfer %s + Send %s Enter password Use password Place your finger to unlock @@ -193,4 +192,5 @@ Debug Settings Confirm recovery phrase Recovery phrase + Send from %s diff --git a/app/src/test/java/pm/gnosis/heimdall/ui/safe/create/ConfirmSafeRecoveryPhraseViewModelTest.kt b/app/src/test/java/pm/gnosis/heimdall/ui/safe/create/ConfirmSafeRecoveryPhraseViewModelTest.kt index 6af7acb63d..d9ffa45b6d 100644 --- a/app/src/test/java/pm/gnosis/heimdall/ui/safe/create/ConfirmSafeRecoveryPhraseViewModelTest.kt +++ b/app/src/test/java/pm/gnosis/heimdall/ui/safe/create/ConfirmSafeRecoveryPhraseViewModelTest.kt @@ -85,7 +85,7 @@ class ConfirmSafeRecoveryPhraseViewModelTest { viewModel.setup(encryptedMnemonic, Solidity.Address(1.toBigInteger())).subscribe(testObserver) - testObserver.assertTerminated().assertNoErrors() + testObserver.assertResult(mnemonic.words().sorted()) then(encryptionManagerMock).should().decrypt(capture(cryptoDataCaptor)) then(encryptionManagerMock).shouldHaveNoMoreInteractions() assertTrue(cryptoDataCaptor.value.data.contentEquals("ffffff".hexStringToByteArray())) diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 85404369e9..5c89c2efe7 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -36,7 +36,7 @@ ext { rxjava : '2.1.12', rxkotlin : '2.2.0', spongycastle : '1.58.0.0', - svalinn : 'v0.5.3', + svalinn : 'v0.5.4', timber : '4.7.0', zxing : '3.3.1',