diff --git a/app/build.gradle b/app/build.gradle index fe5f4538..f4f7f53a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,6 +181,9 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':core-db') implementation project(':common') + + implementation playReview + implementation playAppUpdate implementation project(':feature-splash') implementation project(':feature-onboarding-api') diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt index 0861f027..6d03c419 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt @@ -42,6 +42,7 @@ import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExterna import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository import io.novafoundation.nova.feature_dapp_api.di.deeplinks.DAppDeepLinks import io.novafoundation.nova.feature_deep_linking.presentation.handling.PendingDeepLinkProvider +import io.novafoundation.nova.common.appstore.AppReviewTracker import io.novafoundation.nova.feature_deep_linking.presentation.handling.common.DeepLinkingPreferences import io.novafoundation.nova.feature_gift_api.di.GiftDeepLinks import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState @@ -186,4 +187,6 @@ interface RootDependencies { fun chainMigrationRepository(): ChainMigrationRepository fun migrationInfoRepository(): MigrationInfoRepository + + fun appReviewTracker(): AppReviewTracker } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootActivity.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootActivity.kt index 4160cc7b..b0b6ef42 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootActivity.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootActivity.kt @@ -22,6 +22,9 @@ import io.novafoundation.nova.common.view.dialog.dialog import io.novafoundation.nova.feature_push_notifications.presentation.multisigsWarning.observeEnableMultisigPushesAlert import io.novafoundation.nova.splash.presentation.SplashBackgroundHolder +import io.novafoundation.nova.app.root.presentation.update.AppReviewPrompt +import io.novafoundation.nova.app.root.presentation.update.InAppUpdates +import io.novafoundation.nova.common.appstore.AppReviewTracker import javax.inject.Inject class RootActivity : BaseActivity(), SplashBackgroundHolder { @@ -35,6 +38,12 @@ class RootActivity : BaseActivity(), SplashB @Inject lateinit var contextManager: ContextManager + @Inject + lateinit var appReviewTracker: AppReviewTracker + + private val inAppUpdates by lazy { InAppUpdates(this) } + private val appReviewPrompt by lazy { AppReviewPrompt(this, appReviewTracker) } + override fun createBinding(): ActivityRootBinding { return ActivityRootBinding.inflate(LayoutInflater.from(this)) } @@ -104,6 +113,19 @@ class RootActivity : BaseActivity(), SplashB super.onStart() viewModel.noticeInForeground() + + // Both are no-ops outside a Play install, and both swallow their own failures: + // neither an update check nor a rating card may keep the wallet from opening. + inAppUpdates.checkForUpdate() + appReviewPrompt.requestIfEarned() + } + + override fun onResume() { + super.onResume() + + // Finishes an immediate update that was interrupted, and installs a flexible one + // that finished downloading while the app was in the background. + inAppUpdates.resumeIfNeeded() } override fun subscribe(viewModel: RootViewModel) { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/update/AppReviewPrompt.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/update/AppReviewPrompt.kt new file mode 100644 index 00000000..d7de20d3 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/update/AppReviewPrompt.kt @@ -0,0 +1,45 @@ +package io.novafoundation.nova.app.root.presentation.update + +import android.app.Activity +import android.util.Log +import com.google.android.play.core.review.ReviewManagerFactory +import io.novafoundation.nova.common.appstore.AppReviewTracker + +/** + * Shows Play's native rating card, in-app, when the tracker says the moment is right. + * + * Play decides the rest: whether the user has already rated, and whether their quota + * allows another showing. Neither is visible to us, and neither is reported back — the + * flow reports only that it finished, not what the user did. So there is nothing to + * branch on afterwards, and nothing to record beyond "we spent an attempt". + * + * Failure is silent by design. A rating prompt that surfaces an error is worse than no + * prompt at all. + */ +private const val LOG_TAG = "AppReviewPrompt" + +class AppReviewPrompt( + private val activity: Activity, + private val tracker: AppReviewTracker, +) { + + fun requestIfEarned() { + if (!tracker.shouldRequestReview()) return + + runCatching { + val manager = ReviewManagerFactory.create(activity) + manager.requestReviewFlow() + .addOnSuccessListener { info -> + runCatching { + manager.launchReviewFlow(activity, info) + .addOnCompleteListener { + // Completion says the flow ended, not that a review was + // left. Record either way: the attempt is what Play counts. + tracker.onReviewRequested() + } + }.onFailure { Log.w(LOG_TAG, "Could not launch review flow", it) } + } + .addOnFailureListener { Log.w(LOG_TAG, "Review flow unavailable", it) } + }.onFailure { Log.w(LOG_TAG, "Review manager unavailable", it) } + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/update/InAppUpdates.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/update/InAppUpdates.kt new file mode 100644 index 00000000..1b5e1d2a --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/update/InAppUpdates.kt @@ -0,0 +1,109 @@ +package io.novafoundation.nova.app.root.presentation.update + +import android.app.Activity +import android.util.Log +import com.google.android.play.core.appupdate.AppUpdateManager +import com.google.android.play.core.appupdate.AppUpdateManagerFactory +import com.google.android.play.core.appupdate.AppUpdateOptions +import com.google.android.play.core.install.model.AppUpdateType +import com.google.android.play.core.install.model.InstallStatus +import com.google.android.play.core.install.model.UpdateAvailability + +/** + * Tells the user a newer version exists, and installs it without leaving the app. + * + * There was no such mechanism before: someone on an old build stayed on it until they + * happened to open the Play Store. For a wallet that is worse than an inconvenience — + * a signing fix reaches nobody until they go looking for it. + * + * Two flows, chosen by how far behind the user is: + * + * - FLEXIBLE downloads in the background and keeps the wallet usable, then asks to + * restart. This is the default, because interrupting someone mid-transfer to force + * an update is its own kind of harm. + * - IMMEDIATE blocks until the update is installed. Reserved for releases marked + * high priority in Play Console, and for users who have ignored a flexible prompt + * long enough that staleness alone justifies it. + * + * Only works when Play installed the app. On a Firebase or sideloaded build every call + * here resolves to "no update available" — that is the API's design, not a failure, so + * this must never surface an error to the user. + */ +private const val LOG_TAG = "InAppUpdates" + +/** Beyond this, a flexible prompt has clearly been ignored and the update is forced. */ +private const val IMMEDIATE_AFTER_STALENESS_DAYS = 14 + +/** Play Console marks security-relevant releases at 4+; those are not optional. */ +private const val IMMEDIATE_AT_PRIORITY = 4 + +const val REQUEST_CODE_APP_UPDATE = 4711 + +class InAppUpdates(private val activity: Activity) { + + private val manager: AppUpdateManager by lazy { AppUpdateManagerFactory.create(activity) } + + /** + * Ask Play whether a newer version exists and start the appropriate flow. + * + * Silent on every failure path: no store, no network, no Play install. A wallet + * that cannot check for updates must still open. + */ + fun checkForUpdate() { + runCatching { + manager.appUpdateInfo + .addOnSuccessListener { info -> + runCatching { + if (info.updateAvailability() != UpdateAvailability.UPDATE_AVAILABLE) return@runCatching + + val staleness = info.clientVersionStalenessDays() ?: 0 + val forced = staleness >= IMMEDIATE_AFTER_STALENESS_DAYS || + info.updatePriority() >= IMMEDIATE_AT_PRIORITY + + val type = when { + forced && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> AppUpdateType.IMMEDIATE + info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> AppUpdateType.FLEXIBLE + info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> AppUpdateType.IMMEDIATE + else -> return@runCatching + } + + manager.startUpdateFlowForResult( + info, + activity, + AppUpdateOptions.newBuilder(type).build(), + REQUEST_CODE_APP_UPDATE + ) + }.onFailure { Log.w(LOG_TAG, "Could not start update flow", it) } + } + .addOnFailureListener { Log.w(LOG_TAG, "Update check failed", it) } + }.onFailure { Log.w(LOG_TAG, "Update manager unavailable", it) } + } + + /** + * Finish an interrupted IMMEDIATE update, and install a FLEXIBLE one that finished + * downloading while the app was backgrounded. Call from onResume. + * + * Without this an immediate update that was interrupted leaves the user on a screen + * they cannot get past, and a completed flexible download never installs. + */ + fun resumeIfNeeded() { + runCatching { + manager.appUpdateInfo.addOnSuccessListener { info -> + runCatching { + when { + info.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS -> { + manager.startUpdateFlowForResult( + info, + activity, + AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build(), + REQUEST_CODE_APP_UPDATE + ) + } + + info.installStatus() == InstallStatus.DOWNLOADED -> manager.completeUpdate() + } + }.onFailure { Log.w(LOG_TAG, "Could not resume update", it) } + } + }.onFailure { Log.w(LOG_TAG, "Update resume unavailable", it) } + } +} diff --git a/build.gradle b/build.gradle index 9e68957a..9f38709a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { // App version - versionName = '1.1.2' + versionName = '1.2.0' versionCode = 1 applicationId = "io.pezkuwichain.wallet" @@ -226,6 +226,9 @@ buildscript { playIntegrity = "com.google.android.play:integrity:1.4.0" + playReview = "com.google.android.play:review-ktx:2.0.2" + playAppUpdate = "com.google.android.play:app-update-ktx:2.1.0" + lottie = "com.airbnb.android:lottie:6.6.6" } diff --git a/common/src/main/java/io/novafoundation/nova/common/appstore/AppReviewTracker.kt b/common/src/main/java/io/novafoundation/nova/common/appstore/AppReviewTracker.kt new file mode 100644 index 00000000..23d000a8 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/appstore/AppReviewTracker.kt @@ -0,0 +1,99 @@ +package io.novafoundation.nova.common.appstore + +import io.novafoundation.nova.common.data.storage.Preferences +import java.util.concurrent.TimeUnit + +/** + * Decides when to ask for a Play Store review. + * + * Play gives no way to ask whether someone has already rated, and it throttles the + * prompt to a handful of showings per user per year — a call made too often is + * silently dropped. So the gates below are not there to avoid nagging, which Play + * already handles; they are there to spend the few real chances on a good moment. + * + * Play policy forbids pre-qualifying ("do you like the app?"), rewarding a review, or + * asking for a *positive* one. The prompt has to be unconditional, so the only thing + * left to choose is when. + * + * Nothing here may throw. It is called from the extrinsic submission path, and a + * rating counter must never be able to fail a transfer. + */ +interface AppReviewTracker { + + /** Called after an on-chain operation or a wallet creation succeeds. */ + fun onMeaningfulSuccess() + + /** Called when the user hits an error, which parks the prompt for a while. */ + fun onFailure() + + /** True when every gate is satisfied. Ask Play only then. */ + fun shouldRequestReview(): Boolean + + /** Called once the Play flow has been launched, whatever its outcome. */ + fun onReviewRequested() +} + +private const val KEY_SUCCESSES = "app_review_success_count" +private const val KEY_FIRST_SEEN = "app_review_first_seen_at" +private const val KEY_LAST_ASKED = "app_review_last_asked_at" +private const val KEY_LAST_FAILURE = "app_review_last_failure_at" + +/** Someone with one transfer behind them has no opinion yet. */ +private const val MIN_SUCCESSES = 3 + +/** A first-day user is judging the download, not the wallet. */ +private val MIN_AGE_MS = TimeUnit.DAYS.toMillis(3) + +/** Play's own quota is roughly this scale; asking more often just wastes attempts. */ +private val MIN_INTERVAL_MS = TimeUnit.DAYS.toMillis(90) + +/** Long enough that a fresh failure is no longer what the user has in mind. */ +private val FAILURE_COOLDOWN_MS = TimeUnit.DAYS.toMillis(2) + +class RealAppReviewTracker( + private val preferences: Preferences, + private val currentTimeMillis: () -> Long = System::currentTimeMillis, +) : AppReviewTracker { + + override fun onMeaningfulSuccess() = safely { + val now = currentTimeMillis() + if (preferences.getLong(KEY_FIRST_SEEN, 0L) == 0L) { + preferences.putLong(KEY_FIRST_SEEN, now) + } + preferences.putInt(KEY_SUCCESSES, preferences.getInt(KEY_SUCCESSES, 0) + 1) + } + + override fun onFailure() = safely { + preferences.putLong(KEY_LAST_FAILURE, currentTimeMillis()) + } + + override fun shouldRequestReview(): Boolean { + return runCatching { + val now = currentTimeMillis() + + val successes = preferences.getInt(KEY_SUCCESSES, 0) + if (successes < MIN_SUCCESSES) return@runCatching false + + val firstSeen = preferences.getLong(KEY_FIRST_SEEN, 0L) + if (firstSeen == 0L || now - firstSeen < MIN_AGE_MS) return@runCatching false + + val lastAsked = preferences.getLong(KEY_LAST_ASKED, 0L) + if (lastAsked != 0L && now - lastAsked < MIN_INTERVAL_MS) return@runCatching false + + val lastFailure = preferences.getLong(KEY_LAST_FAILURE, 0L) + if (lastFailure != 0L && now - lastFailure < FAILURE_COOLDOWN_MS) return@runCatching false + + true + }.getOrDefault(false) + } + + override fun onReviewRequested() = safely { + // Written whether or not the user acted on the card: Play does not report the + // outcome, and an attempt spends quota either way. + preferences.putLong(KEY_LAST_ASKED, currentTimeMillis()) + } + + private inline fun safely(block: () -> Unit) { + runCatching(block) + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt b/common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt index 60d5d0f1..d7b54e4a 100644 --- a/common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt +++ b/common/src/main/java/io/novafoundation/nova/common/di/CommonApi.kt @@ -23,6 +23,7 @@ import io.novafoundation.nova.common.data.repository.BannerVisibilityRepository import io.novafoundation.nova.common.data.repository.ToggleFeatureRepository import io.novafoundation.nova.common.data.secrets.v1.SecretStoreV1 import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.common.appstore.AppReviewTracker import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferences import io.novafoundation.nova.common.di.modules.Caching @@ -160,6 +161,8 @@ interface CommonApi { fun providePreferences(): Preferences + fun appReviewTracker(): AppReviewTracker + fun backgroundAccessObserver(): BackgroundAccessObserver fun provideEncryptedPreferences(): EncryptedPreferences diff --git a/common/src/main/java/io/novafoundation/nova/common/di/modules/CommonModule.kt b/common/src/main/java/io/novafoundation/nova/common/di/modules/CommonModule.kt index 083b72bc..796543ea 100644 --- a/common/src/main/java/io/novafoundation/nova/common/di/modules/CommonModule.kt +++ b/common/src/main/java/io/novafoundation/nova/common/di/modules/CommonModule.kt @@ -37,6 +37,8 @@ import io.novafoundation.nova.common.data.repository.ToggleFeatureRepository import io.novafoundation.nova.common.data.secrets.v1.SecretStoreV1 import io.novafoundation.nova.common.data.secrets.v1.SecretStoreV1Impl import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.common.appstore.AppReviewTracker +import io.novafoundation.nova.common.appstore.RealAppReviewTracker import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.data.storage.PreferencesImpl import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferences @@ -165,6 +167,10 @@ class CommonModule { return PreferencesImpl(sharedPreferences) } + @Provides + @ApplicationScope + fun provideAppReviewTracker(preferences: Preferences): AppReviewTracker = RealAppReviewTracker(preferences) + @Provides @ApplicationScope fun provideInteractionGate(): AutomaticInteractionGate = RealAutomaticInteractionGate() diff --git a/distribution/whatsnew/whatsnew-en-US b/distribution/whatsnew/whatsnew-en-US index 3297c731..938508e1 100644 --- a/distribution/whatsnew/whatsnew-en-US +++ b/distribution/whatsnew/whatsnew-en-US @@ -1 +1,5 @@ -Pezkuwi Wallet — the official app for the Digital Kurdistan State. Manage your HEZ tokens, stake, and participate in on-chain governance. \ No newline at end of file +Multisig approvals work again on Pezkuwi chains. Signers could open an operation but not approve it — that is fixed. + +The wallet now tells you when a newer version is available and installs it without leaving the app. + +You can also rate the wallet from inside the app, after you have actually used it. diff --git a/distribution/whatsnew/whatsnew-ku b/distribution/whatsnew/whatsnew-ku index 8d302ec5..09444cff 100644 --- a/distribution/whatsnew/whatsnew-ku +++ b/distribution/whatsnew/whatsnew-ku @@ -1 +1,5 @@ -Pezkuwi Wallet — ئەپی فەرمی دەوڵەتی دیجیتاڵی کوردستان. تۆکنەکانی HEZ بەڕێوەببە، ستەیک بکە و بەشداری بەڕێوەبردنی زنجیرە بکە. \ No newline at end of file +Pesendkirina multisig li ser zincîrên Pezkuwî dîsa dixebite. Îmzeker dikaribûn karê vekin lê pesend nekin; ev hate çareserkirin. + +Berîk niha dema guhertoyeke nû derkeve agahdar dike û nûvekirinê bêyî derketina ji sepanê saz dike. + +Hûn dikarin berîkê ji hundirê sepanê, piştî ku bi rastî bi kar anî, nirxînin. diff --git a/distribution/whatsnew/whatsnew-tr-TR b/distribution/whatsnew/whatsnew-tr-TR index 9346dccd..d8154a05 100644 --- a/distribution/whatsnew/whatsnew-tr-TR +++ b/distribution/whatsnew/whatsnew-tr-TR @@ -1 +1,5 @@ -Pezkuwi Wallet — Dijital Kürdistan Devleti'nin resmi uygulaması. HEZ tokenlarınızı yönetin, stake edin ve zincir üstü yönetime katılın. \ No newline at end of file +Pezkuwi zincirlerinde multisig onayı yeniden çalışıyor. İmzacılar işlemi açabiliyor ama onaylayamıyordu; düzeltildi. + +Cüzdan artık yeni sürüm çıktığında haber veriyor ve güncellemeyi uygulamadan çıkmadan kuruyor. + +Cüzdanı, gerçekten kullandıktan sonra uygulama içinden puanlayabilirsiniz. diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index 19b1092e..ba1db38d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_impl.data.extrinsic import android.util.Log +import io.novafoundation.nova.common.appstore.AppReviewTracker import io.novafoundation.nova.common.data.network.runtime.binding.DispatchError import io.novafoundation.nova.common.data.network.runtime.binding.bindDispatchError import io.novafoundation.nova.common.data.network.runtime.model.FeeResponse @@ -74,6 +75,7 @@ class RealExtrinsicService( private val feePaymentProviderRegistry: FeePaymentProviderRegistry, private val eventsRepository: EventsRepository, private val signingContextFactory: SigningContext.Factory, + private val appReviewTracker: AppReviewTracker, private val coroutineScope: CoroutineScope? // TODO: Make it non-nullable ) : ExtrinsicService { @@ -86,8 +88,13 @@ class RealExtrinsicService( val (extrinsic, submissionOrigin, _, callExecutionType, signingHierarchy) = buildSubmissionExtrinsic(chain, origin, formExtrinsic, submissionOptions) val hash = rpcCalls.submitExtrinsic(chain.id, extrinsic) + // The one place every on-chain action passes through, so counting here covers + // transfers, staking and the rest without a hook per screen. The tracker never + // throws — a rating counter must not be able to fail a submission. + appReviewTracker.onMeaningfulSuccess() + ExtrinsicSubmission(hash, submissionOrigin, callExecutionType, signingHierarchy) - } + }.onFailure { appReviewTracker.onFailure() } override suspend fun submitMultiExtrinsicAwaitingInclusion( chain: Chain, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicServiceFactory.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicServiceFactory.kt index 63a44a1e..cef5ff5c 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicServiceFactory.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicServiceFactory.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_account_impl.data.extrinsic +import io.novafoundation.nova.common.appstore.AppReviewTracker import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSplitter import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentProviderRegistry @@ -21,6 +22,7 @@ class RealExtrinsicServiceFactory( private val eventsRepository: EventsRepository, private val feePaymentProviderRegistry: FeePaymentProviderRegistry, private val signingContextFactory: SigningContext.Factory, + private val appReviewTracker: AppReviewTracker, ) : ExtrinsicService.Factory { override fun create(feeConfig: ExtrinsicService.FeePaymentConfig): ExtrinsicService { @@ -35,7 +37,8 @@ class RealExtrinsicServiceFactory( feePaymentProviderRegistry = registry, eventsRepository = eventsRepository, coroutineScope = feeConfig.coroutineScope, - signingContextFactory = signingContextFactory + signingContextFactory = signingContextFactory, + appReviewTracker = appReviewTracker ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt index f9d5f94a..06157975 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt @@ -12,6 +12,7 @@ import io.novafoundation.nova.common.data.network.NetworkApiCreator import io.novafoundation.nova.common.data.network.rpc.SocketSingleRequestExecutor import io.novafoundation.nova.common.data.secrets.v1.SecretStoreV1 import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.common.appstore.AppReviewTracker import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferences import io.novafoundation.nova.common.di.modules.Caching @@ -148,6 +149,8 @@ interface AccountFeatureDependencies { fun preferences(): Preferences + fun appReviewTracker(): AppReviewTracker + fun encryptedPreferences(): EncryptedPreferences fun resourceManager(): ResourceManager diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index 353b27ac..3650c596 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -4,6 +4,7 @@ import com.google.gson.Gson import dagger.Binds import dagger.Module import dagger.Provides +import io.novafoundation.nova.common.appstore.AppReviewTracker import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.common.data.mappers.mapEncryptionToCryptoType import io.novafoundation.nova.common.data.network.AppLinksProvider @@ -254,7 +255,8 @@ class AccountFeatureModule { extrinsicSplitter: ExtrinsicSplitter, feePaymentProviderRegistry: FeePaymentProviderRegistry, eventsRepository: EventsRepository, - signingContextFactory: SigningContext.Factory + signingContextFactory: SigningContext.Factory, + appReviewTracker: AppReviewTracker, ): ExtrinsicService.Factory = RealExtrinsicServiceFactory( rpcCalls, chainRegistry, @@ -264,7 +266,8 @@ class AccountFeatureModule { extrinsicSplitter, eventsRepository, feePaymentProviderRegistry, - signingContextFactory + signingContextFactory, + appReviewTracker ) @Provides @@ -279,6 +282,7 @@ class AccountFeatureModule { feePaymentProviderRegistry: FeePaymentProviderRegistry, eventsRepository: EventsRepository, signingContextFactory: SigningContext.Factory, + appReviewTracker: AppReviewTracker, ): ExtrinsicService = RealExtrinsicService( rpcCalls, chainRegistry, @@ -289,6 +293,7 @@ class AccountFeatureModule { feePaymentProviderRegistry, eventsRepository, signingContextFactory, + appReviewTracker, coroutineScope = null )