mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-24 05:27:56 +00:00
Initial commit: Pezkuwi Wallet Android
Complete rebrand of Nova Wallet for Pezkuwichain ecosystem. ## Features - Full Pezkuwichain support (HEZ & PEZ tokens) - Polkadot ecosystem compatibility - Staking, Governance, DeFi, NFTs - XCM cross-chain transfers - Hardware wallet support (Ledger, Polkadot Vault) - WalletConnect v2 - Push notifications ## Languages - English, Turkish, Kurmanci (Kurdish), Spanish, French, German, Russian, Japanese, Chinese, Korean, Portuguese, Vietnamese Based on Nova Wallet by Novasama Technologies GmbH © Dijital Kurdistan Tech Institute 2026
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package io.novafoundation.nova.feature_push_notifications
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PushNotificationsService
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureComponent
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationHandler
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class NovaFirebaseMessagingService : FirebaseMessagingService(), CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext = Dispatchers.Default + SupervisorJob()
|
||||
|
||||
@Inject
|
||||
lateinit var pushNotificationsService: PushNotificationsService
|
||||
|
||||
@Inject
|
||||
lateinit var notificationHandler: NotificationHandler
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
injectDependencies()
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
pushNotificationsService.onTokenUpdated(token)
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
launch {
|
||||
notificationHandler.handleNotification(message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
coroutineContext.cancel()
|
||||
}
|
||||
|
||||
private fun injectDependencies() {
|
||||
FeatureUtils.getFeature<PushNotificationsFeatureComponent>(this, PushNotificationsFeatureApi::class.java)
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
suspend fun getToken(): String? {
|
||||
return runCatching { FirebaseMessaging.getInstance().token.await() }.getOrNull()
|
||||
}
|
||||
|
||||
suspend fun requestToken(): String {
|
||||
return FirebaseMessaging.getInstance().token.await()
|
||||
}
|
||||
|
||||
suspend fun deleteToken() {
|
||||
FirebaseMessaging.getInstance().deleteToken()
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_push_notifications
|
||||
|
||||
import android.os.Bundle
|
||||
import io.novafoundation.nova.common.navigation.ReturnableRouter
|
||||
|
||||
interface PushNotificationsRouter : ReturnableRouter {
|
||||
|
||||
fun openPushSettingsWithAccounts()
|
||||
|
||||
fun openPushMultisigsSettings(args: Bundle)
|
||||
|
||||
fun openPushGovernanceSettings(args: Bundle)
|
||||
|
||||
fun openPushStakingSettings(args: Bundle)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data
|
||||
|
||||
object NotificationTypes {
|
||||
const val GOV_NEW_REF = "govNewRef"
|
||||
const val GOV_STATE = "govState"
|
||||
const val STAKING_REWARD = "stakingReward"
|
||||
const val TOKENS_SENT = "tokenSent"
|
||||
const val TOKENS_RECEIVED = "tokenReceived"
|
||||
const val APP_NEW_RELEASE = "appNewRelease"
|
||||
|
||||
const val NEW_MULTISIG = "newMultisig"
|
||||
const val MULTISIG_APPROVAL = "multisigApproval"
|
||||
const val MULTISIG_EXECUTED = "multisigExecuted"
|
||||
const val MULTISIG_CANCELLED = "multisigCancelled"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data
|
||||
|
||||
enum class PushNotificationsAvailabilityState {
|
||||
AVAILABLE,
|
||||
GOOGLE_PLAY_INSTALLATION_REQUIRED,
|
||||
PLAY_SERVICES_REQUIRED
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data
|
||||
|
||||
import com.google.firebase.messaging.messaging
|
||||
import android.util.Log
|
||||
import com.google.android.gms.tasks.OnCompleteListener
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import io.novafoundation.nova.common.data.GoogleApiAvailabilityProvider
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.interfaces.BuildTypeProvider
|
||||
import io.novafoundation.nova.common.interfaces.isMarketRelease
|
||||
import io.novafoundation.nova.common.utils.coroutines.RootScope
|
||||
import io.novafoundation.nova.feature_push_notifications.BuildConfig
|
||||
import io.novafoundation.nova.feature_push_notifications.NovaFirebaseMessagingService
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.PushSettingsProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.data.subscription.PushSubscriptionService
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
const val PUSH_LOG_TAG = "NOVA_PUSH"
|
||||
private const val PREFS_LAST_SYNC_TIME = "PREFS_LAST_SYNC_TIME"
|
||||
private const val MIN_DAYS_TO_START_SYNC = 1
|
||||
private val SAVING_TIMEOUT = 15.seconds
|
||||
|
||||
interface PushNotificationsService {
|
||||
|
||||
fun onTokenUpdated(token: String)
|
||||
|
||||
fun isPushNotificationsEnabled(): Boolean
|
||||
|
||||
fun pushNotificationsAvaiabilityState(): PushNotificationsAvailabilityState
|
||||
|
||||
suspend fun initPushNotifications(): Result<Unit>
|
||||
|
||||
suspend fun updatePushSettings(enabled: Boolean, pushSettings: PushSettings?): Result<Unit>
|
||||
|
||||
fun isPushNotificationsAvailable(): Boolean
|
||||
|
||||
suspend fun syncSettingsIfNeeded()
|
||||
}
|
||||
|
||||
class RealPushNotificationsService(
|
||||
private val settingsProvider: PushSettingsProvider,
|
||||
private val subscriptionService: PushSubscriptionService,
|
||||
private val rootScope: RootScope,
|
||||
private val tokenCache: PushTokenCache,
|
||||
private val googleApiAvailabilityProvider: GoogleApiAvailabilityProvider,
|
||||
private val pushPermissionRepository: PushPermissionRepository,
|
||||
private val preferences: Preferences,
|
||||
private val buildTypeProvider: BuildTypeProvider
|
||||
) : PushNotificationsService {
|
||||
|
||||
// Using to manually sync subscriptions (firestore, topics) after enabling push notifications
|
||||
private var skipTokenReceivingCallback = false
|
||||
|
||||
init {
|
||||
logToken()
|
||||
}
|
||||
|
||||
override fun onTokenUpdated(token: String) {
|
||||
if (!isPushNotificationsAvailable()) return
|
||||
if (!isPushNotificationsEnabled()) return
|
||||
if (skipTokenReceivingCallback) return
|
||||
|
||||
logToken()
|
||||
|
||||
rootScope.launch {
|
||||
tokenCache.updatePushToken(token)
|
||||
updatePushSettings(isPushNotificationsEnabled(), settingsProvider.getPushSettings())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updatePushSettings(enabled: Boolean, pushSettings: PushSettings?): Result<Unit> {
|
||||
if (!isPushNotificationsAvailable()) return googleApiFailureResult()
|
||||
|
||||
return runCatching {
|
||||
withTimeout(SAVING_TIMEOUT) {
|
||||
handlePushTokenIfNeeded(enabled)
|
||||
val pushToken = getPushToken()
|
||||
val oldSettings = settingsProvider.getPushSettings()
|
||||
subscriptionService.handleSubscription(enabled, pushToken, oldSettings, pushSettings)
|
||||
settingsProvider.setPushNotificationsEnabled(enabled)
|
||||
settingsProvider.updateSettings(pushSettings)
|
||||
updateLastSyncTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isPushNotificationsAvailable(): Boolean {
|
||||
return pushNotificationsAvaiabilityState() == PushNotificationsAvailabilityState.AVAILABLE
|
||||
}
|
||||
|
||||
override fun pushNotificationsAvaiabilityState(): PushNotificationsAvailabilityState {
|
||||
return when {
|
||||
!googleApiAvailabilityProvider.isAvailable() -> PushNotificationsAvailabilityState.PLAY_SERVICES_REQUIRED
|
||||
!BuildConfig.DEBUG && !buildTypeProvider.isMarketRelease() -> PushNotificationsAvailabilityState.GOOGLE_PLAY_INSTALLATION_REQUIRED
|
||||
else -> PushNotificationsAvailabilityState.AVAILABLE
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun syncSettingsIfNeeded() {
|
||||
if (!isPushNotificationsEnabled()) return
|
||||
if (!isPushNotificationsAvailable()) return
|
||||
|
||||
if (isPermissionsRevoked() || isTimeToSync()) {
|
||||
val isPermissionGranted = pushPermissionRepository.isPermissionGranted()
|
||||
updatePushSettings(isPermissionGranted, settingsProvider.getPushSettings())
|
||||
}
|
||||
}
|
||||
|
||||
override fun isPushNotificationsEnabled(): Boolean {
|
||||
return settingsProvider.isPushNotificationsEnabled()
|
||||
}
|
||||
|
||||
override suspend fun initPushNotifications(): Result<Unit> {
|
||||
if (!isPushNotificationsAvailable()) return googleApiFailureResult()
|
||||
|
||||
return updatePushSettings(true, settingsProvider.getDefaultPushSettings())
|
||||
}
|
||||
|
||||
private suspend fun handlePushTokenIfNeeded(isEnable: Boolean) {
|
||||
if (!isPushNotificationsAvailable()) return
|
||||
if (isEnable == isPushNotificationsEnabled()) return
|
||||
|
||||
skipTokenReceivingCallback = true
|
||||
|
||||
val pushToken = if (isEnable) {
|
||||
NovaFirebaseMessagingService.requestToken()
|
||||
} else {
|
||||
NovaFirebaseMessagingService.deleteToken()
|
||||
null
|
||||
}
|
||||
|
||||
tokenCache.updatePushToken(pushToken)
|
||||
Firebase.messaging.isAutoInitEnabled = isEnable
|
||||
|
||||
skipTokenReceivingCallback = false
|
||||
}
|
||||
|
||||
private fun getPushToken(): String? {
|
||||
return tokenCache.getPushToken()
|
||||
}
|
||||
|
||||
private fun logToken() {
|
||||
if (!isPushNotificationsEnabled()) return
|
||||
if (!BuildConfig.DEBUG) return
|
||||
if (!isPushNotificationsAvailable()) return
|
||||
|
||||
FirebaseMessaging.getInstance().token.addOnCompleteListener(
|
||||
OnCompleteListener { task ->
|
||||
if (!task.isSuccessful) {
|
||||
return@OnCompleteListener
|
||||
}
|
||||
|
||||
Log.d(PUSH_LOG_TAG, "FCM token: ${task.result}")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun isPermissionsRevoked(): Boolean {
|
||||
return !pushPermissionRepository.isPermissionGranted()
|
||||
}
|
||||
|
||||
private fun isTimeToSync(): Boolean {
|
||||
if (!isPushNotificationsEnabled()) return false
|
||||
|
||||
val lastSyncTime = getLastSyncTimeIfPushEnabled()
|
||||
val deltaTimeBetweenNowAndLastSync = System.currentTimeMillis() - lastSyncTime
|
||||
val wholeDays = deltaTimeBetweenNowAndLastSync.milliseconds.inWholeDays
|
||||
return wholeDays >= MIN_DAYS_TO_START_SYNC
|
||||
}
|
||||
|
||||
private fun updateLastSyncTime() {
|
||||
preferences.putLong(PREFS_LAST_SYNC_TIME, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
private fun getLastSyncTimeIfPushEnabled(): Long {
|
||||
return preferences.getLong(PREFS_LAST_SYNC_TIME, 0)
|
||||
}
|
||||
|
||||
private fun googleApiFailureResult(): Result<Unit> {
|
||||
return Result.failure(IllegalStateException("Google API is not available"))
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
interface PushPermissionRepository {
|
||||
|
||||
fun isPermissionGranted(): Boolean
|
||||
}
|
||||
|
||||
class RealPushPermissionRepository(
|
||||
private val context: Context
|
||||
) : PushPermissionRepository {
|
||||
|
||||
override fun isPermissionGranted(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data
|
||||
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
|
||||
private const val PUSH_TOKEN_KEY = "push_token"
|
||||
|
||||
interface PushTokenCache {
|
||||
|
||||
fun getPushToken(): String?
|
||||
|
||||
fun updatePushToken(pushToken: String?)
|
||||
}
|
||||
|
||||
class RealPushTokenCache(
|
||||
private val preferences: Preferences
|
||||
) : PushTokenCache {
|
||||
|
||||
override fun getPushToken(): String? {
|
||||
return preferences.getString(PUSH_TOKEN_KEY)
|
||||
}
|
||||
|
||||
override fun updatePushToken(pushToken: String?) {
|
||||
preferences.putString(PUSH_TOKEN_KEY, pushToken)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.repository
|
||||
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.AllowingState
|
||||
|
||||
interface MultisigPushAlertRepository {
|
||||
|
||||
fun isMultisigsPushAlertWasShown(): Boolean
|
||||
|
||||
fun setMultisigsPushAlertWasShown()
|
||||
|
||||
fun showAlertAtStartAllowingState(): AllowingState
|
||||
|
||||
fun setAlertAtStartAllowingState(state: AllowingState)
|
||||
}
|
||||
|
||||
private const val IS_MULTISIGS_PUSH_ALERT_WAS_SHOWN = "IS_MULTISIGS_PUSH_ALERT_WAS_SHOWN"
|
||||
private const val MULTISIGS_PUSH_ALERT_ALLOWING_STATE = "MULTISIGS_PUSH_ALERT_ALLOWING_STATE"
|
||||
|
||||
class RealMultisigPushAlertRepository(
|
||||
private val preferences: Preferences
|
||||
) : MultisigPushAlertRepository {
|
||||
override fun isMultisigsPushAlertWasShown(): Boolean {
|
||||
return preferences.getBoolean(IS_MULTISIGS_PUSH_ALERT_WAS_SHOWN, false)
|
||||
}
|
||||
|
||||
override fun setMultisigsPushAlertWasShown() {
|
||||
preferences.putBoolean(IS_MULTISIGS_PUSH_ALERT_WAS_SHOWN, true)
|
||||
}
|
||||
|
||||
override fun showAlertAtStartAllowingState(): AllowingState {
|
||||
val state = preferences.getString(MULTISIGS_PUSH_ALERT_ALLOWING_STATE, AllowingState.INITIAL.toString())
|
||||
return AllowingState.valueOf(state)
|
||||
}
|
||||
|
||||
override fun setAlertAtStartAllowingState(state: AllowingState) {
|
||||
preferences.putString(MULTISIGS_PUSH_ALERT_ALLOWING_STATE, state.toString())
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.repository
|
||||
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
|
||||
interface PushSettingsRepository {
|
||||
fun isMultisigsWasEnabledFirstTime(): Boolean
|
||||
|
||||
fun setMultisigsWasEnabledFirstTime()
|
||||
}
|
||||
|
||||
private const val IS_MULTISIG_WAS_ENABLED_FIRST_TIME = "IS_MULTISIG_WAS_ENABLED_FIRST_TIME"
|
||||
|
||||
class RealPushSettingsRepository(
|
||||
private val preferences: Preferences
|
||||
) : PushSettingsRepository {
|
||||
override fun isMultisigsWasEnabledFirstTime(): Boolean {
|
||||
return preferences.getBoolean(IS_MULTISIG_WAS_ENABLED_FIRST_TIME, false)
|
||||
}
|
||||
|
||||
override fun setMultisigsWasEnabledFirstTime() {
|
||||
preferences.putBoolean(IS_MULTISIG_WAS_ENABLED_FIRST_TIME, true)
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface PushSettingsProvider {
|
||||
|
||||
suspend fun getPushSettings(): PushSettings
|
||||
|
||||
suspend fun getDefaultPushSettings(): PushSettings
|
||||
|
||||
fun updateSettings(pushWalletSettings: PushSettings?)
|
||||
|
||||
fun setPushNotificationsEnabled(isEnabled: Boolean)
|
||||
|
||||
fun isPushNotificationsEnabled(): Boolean
|
||||
|
||||
fun pushEnabledFlow(): Flow<Boolean>
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import io.novafoundation.nova.common.utils.gson.SealedTypeAdapterFactory
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.chain.ChainFeatureCacheV1
|
||||
|
||||
object PushSettingsSerializer {
|
||||
|
||||
fun gson() = GsonBuilder()
|
||||
.registerTypeAdapterFactory(SealedTypeAdapterFactory.of(ChainFeatureCacheV1::class))
|
||||
.create()
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings
|
||||
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.PushSettingsCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.PushSettingsCacheV2
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.VersionedPushSettingsCache
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.toCache
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
private const val PUSH_SETTINGS_KEY = "push_settings"
|
||||
private const val PREFS_PUSH_NOTIFICATIONS_ENABLED = "push_notifications_enabled"
|
||||
|
||||
class RealPushSettingsProvider(
|
||||
private val gson: Gson,
|
||||
private val prefs: Preferences,
|
||||
private val accountRepository: AccountRepository
|
||||
) : PushSettingsProvider {
|
||||
|
||||
override suspend fun getPushSettings(): PushSettings {
|
||||
return prefs.getString(PUSH_SETTINGS_KEY)
|
||||
?.let {
|
||||
gson.fromJson(it, VersionedPushSettingsCache::class.java)
|
||||
.toPushSettings()
|
||||
} ?: getDefaultPushSettings()
|
||||
}
|
||||
|
||||
override suspend fun getDefaultPushSettings(): PushSettings {
|
||||
return PushSettings(
|
||||
announcementsEnabled = true,
|
||||
sentTokensEnabled = true,
|
||||
receivedTokensEnabled = true,
|
||||
subscribedMetaAccounts = setOf(accountRepository.getSelectedMetaAccount().id),
|
||||
stakingReward = PushSettings.ChainFeature.All,
|
||||
governance = emptyMap(),
|
||||
multisigs = PushSettings.MultisigsState.disabled()
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateSettings(pushWalletSettings: PushSettings?) {
|
||||
val versionedCache = pushWalletSettings?.toCache()
|
||||
?.toVersionedPushSettingsCache()
|
||||
|
||||
prefs.putString(PUSH_SETTINGS_KEY, versionedCache?.let(gson::toJson))
|
||||
}
|
||||
|
||||
override fun setPushNotificationsEnabled(isEnabled: Boolean) {
|
||||
prefs.putBoolean(PREFS_PUSH_NOTIFICATIONS_ENABLED, isEnabled)
|
||||
}
|
||||
|
||||
override fun isPushNotificationsEnabled(): Boolean {
|
||||
return prefs.getBoolean(PREFS_PUSH_NOTIFICATIONS_ENABLED, false)
|
||||
}
|
||||
|
||||
override fun pushEnabledFlow(): Flow<Boolean> {
|
||||
return prefs.booleanFlow(PREFS_PUSH_NOTIFICATIONS_ENABLED, false)
|
||||
}
|
||||
|
||||
fun PushSettingsCacheV2.toVersionedPushSettingsCache(): VersionedPushSettingsCache {
|
||||
return VersionedPushSettingsCache(
|
||||
version = version,
|
||||
settings = gson.toJson(this)
|
||||
)
|
||||
}
|
||||
|
||||
fun VersionedPushSettingsCache.toPushSettings(): PushSettings {
|
||||
return when (version) {
|
||||
PushSettingsCacheV1.VERSION -> gson.fromJson(settings, PushSettingsCacheV1::class.java)
|
||||
PushSettingsCacheV2.VERSION -> gson.fromJson(settings, PushSettingsCacheV2::class.java)
|
||||
else -> throw IllegalStateException("Unknown push settings version: $version")
|
||||
}.toPushSettings()
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings.model
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.chain.ChainFeatureCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.governance.GovernanceStateCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.governance.MultisigsStateCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
|
||||
fun PushSettings.toCache(): PushSettingsCacheV2 {
|
||||
return PushSettingsCacheV2(
|
||||
announcementsEnabled = announcementsEnabled,
|
||||
sentTokensEnabled = sentTokensEnabled,
|
||||
receivedTokensEnabled = receivedTokensEnabled,
|
||||
subscribedMetaAccounts = subscribedMetaAccounts,
|
||||
stakingReward = stakingReward.toCache(),
|
||||
governance = governance.mapValues { (_, value) -> value.toCache() },
|
||||
multisigs = multisigs.toCache()
|
||||
)
|
||||
}
|
||||
|
||||
fun PushSettings.ChainFeature.toCache(): ChainFeatureCacheV1 {
|
||||
return when (this) {
|
||||
is PushSettings.ChainFeature.All -> ChainFeatureCacheV1.All
|
||||
is PushSettings.ChainFeature.Concrete -> ChainFeatureCacheV1.Concrete(chainIds)
|
||||
}
|
||||
}
|
||||
|
||||
fun PushSettings.GovernanceState.toCache(): GovernanceStateCacheV1 {
|
||||
return GovernanceStateCacheV1(
|
||||
newReferendaEnabled = newReferendaEnabled,
|
||||
referendumUpdateEnabled = referendumUpdateEnabled,
|
||||
govMyDelegateVotedEnabled = govMyDelegateVotedEnabled,
|
||||
tracks = tracks
|
||||
)
|
||||
}
|
||||
|
||||
fun PushSettings.MultisigsState.toCache(): MultisigsStateCacheV1 {
|
||||
return MultisigsStateCacheV1(
|
||||
isEnabled = isEnabled,
|
||||
isInitialNotificationsEnabled = isInitiatingEnabled,
|
||||
isApprovalNotificationsEnabled = isApprovingEnabled,
|
||||
isExecutionNotificationsEnabled = isExecutionEnabled,
|
||||
isRejectionNotificationsEnabled = isRejectionEnabled
|
||||
)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings.model
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
|
||||
interface PushSettingsCache {
|
||||
|
||||
val version: String
|
||||
|
||||
fun toPushSettings(): PushSettings
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings.model
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.chain.ChainFeatureCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.chain.toDomain
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.governance.GovernanceStateCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.governance.toDomain
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
class PushSettingsCacheV1(
|
||||
val announcementsEnabled: Boolean,
|
||||
val sentTokensEnabled: Boolean,
|
||||
val receivedTokensEnabled: Boolean,
|
||||
val subscribedMetaAccounts: Set<Long>,
|
||||
val stakingReward: ChainFeatureCacheV1,
|
||||
val governance: Map<ChainId, GovernanceStateCacheV1>
|
||||
) : PushSettingsCache {
|
||||
|
||||
companion object {
|
||||
const val VERSION = "V1"
|
||||
}
|
||||
|
||||
override val version: String = VERSION
|
||||
|
||||
override fun toPushSettings(): PushSettings {
|
||||
return PushSettings(
|
||||
announcementsEnabled = announcementsEnabled,
|
||||
sentTokensEnabled = sentTokensEnabled,
|
||||
receivedTokensEnabled = receivedTokensEnabled,
|
||||
subscribedMetaAccounts = subscribedMetaAccounts,
|
||||
stakingReward = stakingReward.toDomain(),
|
||||
governance = governance.mapValues { (_, value) -> value.toDomain() },
|
||||
multisigs = PushSettings.MultisigsState.disabled()
|
||||
)
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings.model
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.chain.ChainFeatureCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.chain.toDomain
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.governance.GovernanceStateCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.governance.MultisigsStateCacheV1
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.model.governance.toDomain
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
class PushSettingsCacheV2(
|
||||
val announcementsEnabled: Boolean,
|
||||
val sentTokensEnabled: Boolean,
|
||||
val receivedTokensEnabled: Boolean,
|
||||
val subscribedMetaAccounts: Set<Long>,
|
||||
val stakingReward: ChainFeatureCacheV1,
|
||||
val governance: Map<ChainId, GovernanceStateCacheV1>,
|
||||
val multisigs: MultisigsStateCacheV1
|
||||
) : PushSettingsCache {
|
||||
|
||||
companion object {
|
||||
const val VERSION = "V2"
|
||||
}
|
||||
|
||||
override val version: String = VERSION
|
||||
|
||||
override fun toPushSettings(): PushSettings {
|
||||
return PushSettings(
|
||||
announcementsEnabled = announcementsEnabled,
|
||||
sentTokensEnabled = sentTokensEnabled,
|
||||
receivedTokensEnabled = receivedTokensEnabled,
|
||||
subscribedMetaAccounts = subscribedMetaAccounts,
|
||||
stakingReward = stakingReward.toDomain(),
|
||||
governance = governance.mapValues { (_, value) -> value.toDomain() },
|
||||
multisigs = multisigs.toDomain()
|
||||
)
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings.model
|
||||
|
||||
typealias Json = String
|
||||
|
||||
class VersionedPushSettingsCache(
|
||||
val version: String,
|
||||
val settings: Json
|
||||
)
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings.model.chain
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
sealed class ChainFeatureCacheV1 {
|
||||
|
||||
object All : ChainFeatureCacheV1()
|
||||
|
||||
data class Concrete(val chainIds: List<ChainId>) : ChainFeatureCacheV1()
|
||||
}
|
||||
|
||||
fun ChainFeatureCacheV1.toDomain(): PushSettings.ChainFeature {
|
||||
return when (this) {
|
||||
is ChainFeatureCacheV1.All -> PushSettings.ChainFeature.All
|
||||
is ChainFeatureCacheV1.Concrete -> PushSettings.ChainFeature.Concrete(chainIds)
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings.model.governance
|
||||
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
|
||||
class GovernanceStateCacheV1(
|
||||
val newReferendaEnabled: Boolean,
|
||||
val referendumUpdateEnabled: Boolean,
|
||||
val govMyDelegateVotedEnabled: Boolean,
|
||||
val tracks: Set<TrackId>
|
||||
)
|
||||
|
||||
fun GovernanceStateCacheV1.toDomain(): PushSettings.GovernanceState {
|
||||
return PushSettings.GovernanceState(
|
||||
newReferendaEnabled = newReferendaEnabled,
|
||||
referendumUpdateEnabled = referendumUpdateEnabled,
|
||||
govMyDelegateVotedEnabled = govMyDelegateVotedEnabled,
|
||||
tracks = tracks
|
||||
)
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.settings.model.governance
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
|
||||
class MultisigsStateCacheV1(
|
||||
val isEnabled: Boolean, // General notifications state. Other states may be enabled to save state when general one is disabled
|
||||
val isInitialNotificationsEnabled: Boolean,
|
||||
val isApprovalNotificationsEnabled: Boolean,
|
||||
val isExecutionNotificationsEnabled: Boolean,
|
||||
val isRejectionNotificationsEnabled: Boolean
|
||||
)
|
||||
|
||||
fun MultisigsStateCacheV1.toDomain(): PushSettings.MultisigsState {
|
||||
return PushSettings.MultisigsState(
|
||||
isEnabled = isEnabled,
|
||||
isInitiatingEnabled = isInitialNotificationsEnabled,
|
||||
isApprovingEnabled = isApprovalNotificationsEnabled,
|
||||
isExecutionEnabled = isExecutionNotificationsEnabled,
|
||||
isRejectionEnabled = isRejectionNotificationsEnabled
|
||||
)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.subscription
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
|
||||
interface PushSubscriptionService {
|
||||
|
||||
suspend fun handleSubscription(pushEnabled: Boolean, token: String?, oldSettings: PushSettings, newSettings: PushSettings?)
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.data.subscription
|
||||
|
||||
import android.util.Log
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.firestore.firestore
|
||||
import com.google.firebase.messaging.messaging
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.utils.CollectionDiffer
|
||||
import io.novafoundation.nova.common.utils.Identifiable
|
||||
import io.novafoundation.nova.common.utils.formatting.formatDateISO_8601_NoMs
|
||||
import io.novafoundation.nova.common.utils.mapOfNotNullValues
|
||||
import io.novafoundation.nova.common.utils.mapValuesNotNull
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.defaultSubstrateAddress
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.mainEthereumAddress
|
||||
import io.novafoundation.nova.feature_push_notifications.BuildConfig
|
||||
import io.novafoundation.nova.common.data.GoogleApiAvailabilityProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PUSH_LOG_TAG
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.isApprovingEnabledTotal
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.isExecutionEnabledTotal
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.isInitiatingEnabledTotal
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.isRejectionEnabledTotal
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.ext.chainIdHexPrefix16
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainsById
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chainsById
|
||||
import java.math.BigInteger
|
||||
import java.util.UUID
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.tasks.await
|
||||
|
||||
private const val COLLECTION_NAME = "users"
|
||||
private const val PREFS_FIRESTORE_UUID = "firestore_uuid"
|
||||
|
||||
private const val GOV_STATE_TOPIC_NAME = "govState"
|
||||
private const val NEW_REFERENDA_TOPIC_NAME = "govNewRef"
|
||||
|
||||
class TrackIdentifiable(val chainId: ChainId, val track: BigInteger) : Identifiable {
|
||||
override val identifier: String = "$chainId:$track"
|
||||
}
|
||||
|
||||
class RealPushSubscriptionService(
|
||||
private val prefs: Preferences,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val googleApiAvailabilityProvider: GoogleApiAvailabilityProvider,
|
||||
private val accountRepository: AccountRepository
|
||||
) : PushSubscriptionService {
|
||||
|
||||
private val generateIdMutex = Mutex()
|
||||
|
||||
override suspend fun handleSubscription(pushEnabled: Boolean, token: String?, oldSettings: PushSettings, newSettings: PushSettings?) {
|
||||
if (!googleApiAvailabilityProvider.isAvailable()) return
|
||||
|
||||
val tokenExist = token != null
|
||||
if (pushEnabled != tokenExist) throw IllegalStateException("Token should exist to enable push notifications")
|
||||
|
||||
handleTopics(pushEnabled, oldSettings, newSettings)
|
||||
handleFirestore(token, newSettings)
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(PUSH_LOG_TAG, "Firestore user updated: ${getFirestoreUUID()}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFirestoreUUID(): String {
|
||||
return generateIdMutex.withLock {
|
||||
var uuid = prefs.getString(PREFS_FIRESTORE_UUID)
|
||||
|
||||
if (uuid == null) {
|
||||
uuid = UUID.randomUUID().toString()
|
||||
prefs.putString(PREFS_FIRESTORE_UUID, uuid)
|
||||
}
|
||||
|
||||
uuid
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFirestore(token: String?, pushSettings: PushSettings?) {
|
||||
val hasAccounts = pushSettings?.subscribedMetaAccounts?.any() ?: false
|
||||
if (token == null || pushSettings == null || !hasAccounts) {
|
||||
Firebase.firestore.collection(COLLECTION_NAME)
|
||||
.document(getFirestoreUUID())
|
||||
.delete()
|
||||
.await()
|
||||
} else {
|
||||
val model = mapToFirestorePushSettings(
|
||||
token,
|
||||
Date(),
|
||||
pushSettings
|
||||
)
|
||||
|
||||
Firebase.firestore.collection(COLLECTION_NAME)
|
||||
.document(getFirestoreUUID())
|
||||
.set(model)
|
||||
.await()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleTopics(pushEnabled: Boolean, oldSettings: PushSettings, newSettings: PushSettings?) {
|
||||
val referendumUpdateTracks = newSettings?.getGovernanceTracksFor { it.referendumUpdateEnabled }
|
||||
?.takeIf { pushEnabled }
|
||||
.orEmpty()
|
||||
|
||||
val newReferendaTracks = newSettings?.getGovernanceTracksFor { it.newReferendaEnabled }
|
||||
?.takeIf { pushEnabled }
|
||||
.orEmpty()
|
||||
|
||||
val govStateTracksDiff = CollectionDiffer.findDiff(
|
||||
oldItems = oldSettings.getGovernanceTracksFor { it.referendumUpdateEnabled },
|
||||
newItems = referendumUpdateTracks,
|
||||
forceUseNewItems = false
|
||||
)
|
||||
val newReferendaDiff = CollectionDiffer.findDiff(
|
||||
oldItems = oldSettings.getGovernanceTracksFor { it.newReferendaEnabled },
|
||||
newItems = newReferendaTracks,
|
||||
forceUseNewItems = false
|
||||
)
|
||||
|
||||
val announcementsEnabled = newSettings?.announcementsEnabled ?: false
|
||||
handleSubscription(announcementsEnabled && pushEnabled, "appUpdates")
|
||||
|
||||
govStateTracksDiff.added
|
||||
.map { subscribeToTopic("${GOV_STATE_TOPIC_NAME}_${it.chainId}_${it.track}") }
|
||||
govStateTracksDiff.removed
|
||||
.map { unsubscribeFromTopic("${GOV_STATE_TOPIC_NAME}_${it.chainId}_${it.track}") }
|
||||
|
||||
newReferendaDiff.added
|
||||
.map { subscribeToTopic("${NEW_REFERENDA_TOPIC_NAME}_${it.chainId}_${it.track}") }
|
||||
newReferendaDiff.removed
|
||||
.map { unsubscribeFromTopic("${NEW_REFERENDA_TOPIC_NAME}_${it.chainId}_${it.track}") }
|
||||
}
|
||||
|
||||
private fun handleSubscription(subscribe: Boolean, topic: String) {
|
||||
return if (subscribe) {
|
||||
subscribeToTopic(topic)
|
||||
} else {
|
||||
unsubscribeFromTopic(topic)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToTopic(topic: String) {
|
||||
Firebase.messaging.subscribeToTopic(topic)
|
||||
}
|
||||
|
||||
private fun unsubscribeFromTopic(topic: String) {
|
||||
Firebase.messaging.unsubscribeFromTopic(topic)
|
||||
}
|
||||
|
||||
private suspend fun mapToFirestorePushSettings(
|
||||
token: String,
|
||||
date: Date,
|
||||
settings: PushSettings
|
||||
): Map<String, Any> {
|
||||
val chainsById = chainRegistry.chainsById()
|
||||
val metaAccountsById = accountRepository
|
||||
.getActiveMetaAccounts()
|
||||
.associateBy { it.id }
|
||||
|
||||
return mapOf(
|
||||
"pushToken" to token,
|
||||
"updatedAt" to formatDateISO_8601_NoMs(date),
|
||||
"wallets" to settings.subscribedMetaAccounts.mapNotNull { mapToFirestoreWallet(it, metaAccountsById, chainsById) },
|
||||
"notifications" to mapOfNotNullValues(
|
||||
"stakingReward" to mapToFirestoreChainFeature(settings.stakingReward),
|
||||
"tokenSent" to settings.sentTokensEnabled.mapToFirestoreChainFeatureOrNull(),
|
||||
"tokenReceived" to settings.receivedTokensEnabled.mapToFirestoreChainFeatureOrNull(),
|
||||
"newMultisig" to settings.multisigs.isInitiatingEnabledTotal().mapToFirestoreChainFeatureOrNull(),
|
||||
"multisigApproval" to settings.multisigs.isApprovingEnabledTotal().mapToFirestoreChainFeatureOrNull(),
|
||||
"multisigExecuted" to settings.multisigs.isExecutionEnabledTotal().mapToFirestoreChainFeatureOrNull(),
|
||||
"multisigCancelled" to settings.multisigs.isRejectionEnabledTotal().mapToFirestoreChainFeatureOrNull()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapToFirestoreWallet(metaId: Long, metaAccountsById: Map<Long, MetaAccount>, chainsById: ChainsById): Map<String, Any>? {
|
||||
val metaAccount = metaAccountsById[metaId] ?: return null
|
||||
return mapOfNotNullValues(
|
||||
"baseEthereum" to metaAccount.mainEthereumAddress(),
|
||||
"baseSubstrate" to metaAccount.defaultSubstrateAddress,
|
||||
"chainSpecific" to metaAccount.chainAccounts.mapValuesNotNull { (chainId, chainAccount) ->
|
||||
val chain = chainsById[chainId] ?: return@mapValuesNotNull null
|
||||
chain.addressOf(chainAccount.accountId)
|
||||
}.transfromChainIdsTo16Hex()
|
||||
.nullIfEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapToFirestoreChainFeature(chainFeature: PushSettings.ChainFeature): Map<String, Any>? {
|
||||
return when (chainFeature) {
|
||||
is PushSettings.ChainFeature.All -> mapOf("type" to "all")
|
||||
is PushSettings.ChainFeature.Concrete -> {
|
||||
if (chainFeature.chainIds.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
mapOf("type" to "concrete", "value" to chainFeature.chainIds.transfromChainIdsTo16Hex())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Boolean.mapToFirestoreChainFeatureOrNull(): Map<String, Any>? {
|
||||
return if (this) mapOf("type" to "all") else null
|
||||
}
|
||||
|
||||
private fun PushSettings.getGovernanceTracksFor(filter: (PushSettings.GovernanceState) -> Boolean): List<TrackIdentifiable> {
|
||||
return governance.filter { (_, state) -> filter(state) }
|
||||
.flatMap { (chainId, state) -> state.tracks.map { TrackIdentifiable(chainId.chainIdHexPrefix16(), it.value) } }
|
||||
}
|
||||
|
||||
private fun Map<String, Any>.nullIfEmpty(): Map<String, Any>? {
|
||||
return if (isEmpty()) null else this
|
||||
}
|
||||
|
||||
private fun <T> Map<ChainId, T>.transfromChainIdsTo16Hex(): Map<String, T> {
|
||||
return mapKeys { (chainId, _) -> chainId.chainIdHexPrefix16() }
|
||||
}
|
||||
|
||||
private fun List<ChainId>.transfromChainIdsTo16Hex(): List<String> {
|
||||
return map { chainId -> chainId.chainIdHexPrefix16() }
|
||||
}
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.di
|
||||
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkConfigurator
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.gson.Gson
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigDetailsRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.common.ReferendaStatusFormatter
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.deeplink.configurators.ReferendumDetailsDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.CompoundNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.SystemNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.RealNotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.DebugNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.NewReferendumNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.NewReleaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.ReferendumStateUpdateNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.StakingRewardNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.TokenReceivedNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.TokenSentNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig.MultisigTransactionCancelledNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig.MultisigTransactionExecutedNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig.MultisigTransactionInitiatedNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig.MultisigTransactionNewApprovalNotificationHandler
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module()
|
||||
class NotificationHandlersModule {
|
||||
|
||||
@Provides
|
||||
fun provideNotificationIdProvider(preferences: Preferences): NotificationIdProvider {
|
||||
return RealNotificationIdProvider(preferences)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideNotificationManagerCompat(context: Context): NotificationManagerCompat {
|
||||
return NotificationManagerCompat.from(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun systemNotificationHandler(
|
||||
context: Context,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
notificationManagerCompat: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
gson: Gson
|
||||
): NotificationHandler {
|
||||
return SystemNotificationHandler(context, activityIntentProvider, notificationIdProvider, gson, notificationManagerCompat, resourceManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun tokenSentNotificationHandler(
|
||||
context: Context,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
notificationManagerCompat: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
gson: Gson,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
accountRepository: AccountRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
tokenRepository: TokenRepository,
|
||||
configurator: AssetDetailsDeepLinkConfigurator
|
||||
): NotificationHandler {
|
||||
return TokenSentNotificationHandler(
|
||||
context,
|
||||
accountRepository,
|
||||
tokenRepository,
|
||||
chainRegistry,
|
||||
configurator,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManagerCompat,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun tokenReceivedNotificationHandler(
|
||||
context: Context,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
notificationManagerCompat: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
gson: Gson,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
accountRepository: AccountRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
tokenRepository: TokenRepository,
|
||||
configurator: AssetDetailsDeepLinkConfigurator
|
||||
): NotificationHandler {
|
||||
return TokenReceivedNotificationHandler(
|
||||
context,
|
||||
accountRepository,
|
||||
tokenRepository,
|
||||
configurator,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManagerCompat,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun stakingRewardNotificationHandler(
|
||||
context: Context,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
notificationManagerCompat: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
gson: Gson,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
accountRepository: AccountRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
tokenRepository: TokenRepository,
|
||||
configurator: AssetDetailsDeepLinkConfigurator
|
||||
): NotificationHandler {
|
||||
return StakingRewardNotificationHandler(
|
||||
context,
|
||||
accountRepository,
|
||||
tokenRepository,
|
||||
configurator,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManagerCompat,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun referendumStateUpdateNotificationHandler(
|
||||
context: Context,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
notificationManagerCompat: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
referendaStatusFormatter: ReferendaStatusFormatter,
|
||||
gson: Gson,
|
||||
chainRegistry: ChainRegistry,
|
||||
configurator: ReferendumDetailsDeepLinkConfigurator
|
||||
): NotificationHandler {
|
||||
return ReferendumStateUpdateNotificationHandler(
|
||||
context,
|
||||
configurator,
|
||||
referendaStatusFormatter,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManagerCompat,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun newReleaseNotificationHandler(
|
||||
context: Context,
|
||||
appLinksProvider: AppLinksProvider,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
notificationManagerCompat: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
gson: Gson
|
||||
): NotificationHandler {
|
||||
return NewReleaseNotificationHandler(
|
||||
context,
|
||||
appLinksProvider,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManagerCompat,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun newReferendumNotificationHandler(
|
||||
context: Context,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
notificationManagerCompat: NotificationManagerCompat,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
resourceManager: ResourceManager,
|
||||
gson: Gson,
|
||||
chainRegistry: ChainRegistry,
|
||||
configurator: ReferendumDetailsDeepLinkConfigurator
|
||||
): NotificationHandler {
|
||||
return NewReferendumNotificationHandler(
|
||||
context,
|
||||
configurator,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManagerCompat,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun multisigTransactionInitiatedNotificationHandler(
|
||||
context: Context,
|
||||
accountRepository: AccountRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
multisigCallFormatter: MultisigCallFormatter,
|
||||
configurator: MultisigOperationDeepLinkConfigurator,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
@LocalIdentity identityProvider: IdentityProvider
|
||||
): NotificationHandler {
|
||||
return MultisigTransactionInitiatedNotificationHandler(
|
||||
context,
|
||||
accountRepository,
|
||||
multisigCallFormatter,
|
||||
configurator,
|
||||
chainRegistry,
|
||||
identityProvider,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun multisigTransactionNewApprovalNotificationHandler(
|
||||
context: Context,
|
||||
accountRepository: AccountRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
multisigDetailsRepository: MultisigDetailsRepository,
|
||||
@LocalIdentity identityProvider: IdentityProvider,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
multisigCallFormatter: MultisigCallFormatter,
|
||||
configurator: MultisigOperationDeepLinkConfigurator,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
): NotificationHandler {
|
||||
return MultisigTransactionNewApprovalNotificationHandler(
|
||||
context,
|
||||
accountRepository,
|
||||
multisigDetailsRepository,
|
||||
multisigCallFormatter,
|
||||
configurator,
|
||||
identityProvider,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun multisigTransactionExecutedNotificationHandler(
|
||||
context: Context,
|
||||
accountRepository: AccountRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
configurator: MultisigOperationDeepLinkConfigurator,
|
||||
@LocalIdentity identityProvider: IdentityProvider,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
multisigCallFormatter: MultisigCallFormatter,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
): NotificationHandler {
|
||||
return MultisigTransactionExecutedNotificationHandler(
|
||||
context,
|
||||
accountRepository,
|
||||
multisigCallFormatter,
|
||||
configurator,
|
||||
identityProvider,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun multisigTransactionCancelledNotificationHandler(
|
||||
context: Context,
|
||||
accountRepository: AccountRepository,
|
||||
chainRegistry: ChainRegistry,
|
||||
configurator: MultisigOperationDeepLinkConfigurator,
|
||||
@LocalIdentity identityProvider: IdentityProvider,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
multisigCallFormatter: MultisigCallFormatter,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
): NotificationHandler {
|
||||
return MultisigTransactionCancelledNotificationHandler(
|
||||
context,
|
||||
accountRepository,
|
||||
multisigCallFormatter,
|
||||
configurator,
|
||||
identityProvider,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun debugNotificationHandler(
|
||||
context: Context,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationManagerCompat: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager
|
||||
): DebugNotificationHandler {
|
||||
return DebugNotificationHandler(context, activityIntentProvider, notificationManagerCompat, resourceManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideCompoundNotificationHandler(
|
||||
handlers: Set<@JvmSuppressWildcards NotificationHandler>,
|
||||
debugNotificationHandler: DebugNotificationHandler
|
||||
): NotificationHandler {
|
||||
val handlersWithDebugHandler = handlers + debugNotificationHandler // Add debug handler as a fallback in the end
|
||||
return CompoundNotificationHandler(handlersWithDebugHandler)
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.di
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.NovaFirebaseMessagingService
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.WelcomePushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigsWarning.MultisigPushNotificationsAlertMixinFactory
|
||||
|
||||
interface PushNotificationsFeatureApi {
|
||||
|
||||
val multisigPushNotificationsAlertMixinFactory: MultisigPushNotificationsAlertMixinFactory
|
||||
|
||||
fun inject(service: NovaFirebaseMessagingService)
|
||||
|
||||
fun pushNotificationInteractor(): PushNotificationsInteractor
|
||||
|
||||
fun welcomePushNotificationsInteractor(): WelcomePushNotificationsInteractor
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.di
|
||||
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import io.novafoundation.nova.common.di.CommonApi
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsCommunicator
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator
|
||||
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
|
||||
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
|
||||
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PushNotificationsService
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.di.PushGovernanceSettingsComponent
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.di.PushMultisigSettingsComponent
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.settings.di.PushSettingsComponent
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.di.PushStakingSettingsComponent
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.welcome.di.PushWelcomeComponent
|
||||
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
PushNotificationsFeatureDependencies::class
|
||||
],
|
||||
modules = [
|
||||
PushNotificationsFeatureModule::class
|
||||
]
|
||||
)
|
||||
@FeatureScope
|
||||
interface PushNotificationsFeatureComponent : PushNotificationsFeatureApi {
|
||||
|
||||
fun getPushNotificationService(): PushNotificationsService
|
||||
|
||||
fun pushWelcomeComponentFactory(): PushWelcomeComponent.Factory
|
||||
|
||||
fun pushSettingsComponentFactory(): PushSettingsComponent.Factory
|
||||
|
||||
fun pushGovernanceSettings(): PushGovernanceSettingsComponent.Factory
|
||||
|
||||
fun pushStakingSettings(): PushStakingSettingsComponent.Factory
|
||||
|
||||
fun pushMultisigSettings(): PushMultisigSettingsComponent.Factory
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance router: PushNotificationsRouter,
|
||||
@BindsInstance selectMultipleWalletsCommunicator: SelectMultipleWalletsCommunicator,
|
||||
@BindsInstance selectTracksCommunicator: SelectTracksCommunicator,
|
||||
@BindsInstance pushGovernanceSettingsCommunicator: PushGovernanceSettingsCommunicator,
|
||||
@BindsInstance pushStakingSettingsCommunicator: PushStakingSettingsCommunicator,
|
||||
@BindsInstance pushMultisigSettingsCommunicator: PushMultisigSettingsCommunicator,
|
||||
deps: PushNotificationsFeatureDependencies
|
||||
): PushNotificationsFeatureComponent
|
||||
}
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
CommonApi::class,
|
||||
RuntimeApi::class,
|
||||
AccountFeatureApi::class,
|
||||
GovernanceFeatureApi::class,
|
||||
WalletFeatureApi::class,
|
||||
AssetsFeatureApi::class,
|
||||
MultisigOperationsFeatureApi::class
|
||||
]
|
||||
)
|
||||
interface PushNotificationsFeatureDependenciesComponent : PushNotificationsFeatureDependencies
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.di
|
||||
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkConfigurator
|
||||
import android.content.Context
|
||||
import coil.ImageLoader
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.data.GoogleApiAvailabilityProvider
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.interfaces.BuildTypeProvider
|
||||
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.coroutines.RootScope
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory
|
||||
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
|
||||
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigDetailsRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.common.ReferendaStatusFormatter
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.deeplink.configurators.ReferendumDetailsDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
interface PushNotificationsFeatureDependencies {
|
||||
|
||||
val rootScope: RootScope
|
||||
|
||||
val preferences: Preferences
|
||||
|
||||
val context: Context
|
||||
|
||||
val chainRegistry: ChainRegistry
|
||||
|
||||
val permissionsAskerFactory: PermissionsAskerFactory
|
||||
|
||||
val resourceManager: ResourceManager
|
||||
|
||||
val accountRepository: AccountRepository
|
||||
|
||||
val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
|
||||
|
||||
val governanceSourceRegistry: GovernanceSourceRegistry
|
||||
|
||||
val imageLoader: ImageLoader
|
||||
|
||||
val gson: Gson
|
||||
|
||||
val referendaStatusFormatter: ReferendaStatusFormatter
|
||||
|
||||
val assetDetailsDeepLinkConfigurator: AssetDetailsDeepLinkConfigurator
|
||||
|
||||
val tokenRepository: TokenRepository
|
||||
|
||||
val provideActivityIntentProvider: ActivityIntentProvider
|
||||
|
||||
val appLinksProvider: AppLinksProvider
|
||||
|
||||
val referendumDetailsDeepLinkConfigurator: ReferendumDetailsDeepLinkConfigurator
|
||||
|
||||
val multisigOperationDeepLinkConfigurator: MultisigOperationDeepLinkConfigurator
|
||||
|
||||
val metaAccountChangesEventBus: MetaAccountChangesEventBus
|
||||
|
||||
val googleApiAvailabilityProvider: GoogleApiAvailabilityProvider
|
||||
|
||||
val multisigCallFormatter: MultisigCallFormatter
|
||||
|
||||
val multisigDetailsRepository: MultisigDetailsRepository
|
||||
|
||||
val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry
|
||||
|
||||
val automaticInteractionGate: AutomaticInteractionGate
|
||||
|
||||
fun buildTypeProvider(): BuildTypeProvider
|
||||
|
||||
@LocalIdentity
|
||||
fun localWithIdentityProvider(): IdentityProvider
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.di
|
||||
|
||||
import io.novafoundation.nova.common.di.FeatureApiHolder
|
||||
import io.novafoundation.nova.common.di.FeatureContainer
|
||||
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsCommunicator
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator
|
||||
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
|
||||
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
|
||||
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
|
||||
import io.novafoundation.nova.runtime.di.RuntimeApi
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushNotificationsFeatureHolder @Inject constructor(
|
||||
featureContainer: FeatureContainer,
|
||||
private val router: PushNotificationsRouter,
|
||||
private val selectMultipleWalletsCommunicator: SelectMultipleWalletsCommunicator,
|
||||
private val selectTracksCommunicator: SelectTracksCommunicator,
|
||||
private val pushGovernanceSettingsCommunicator: PushGovernanceSettingsCommunicator,
|
||||
private val pushStakingSettingsCommunicator: PushStakingSettingsCommunicator,
|
||||
private val pushMultisigSettingsCommunicator: PushMultisigSettingsCommunicator
|
||||
) : FeatureApiHolder(featureContainer) {
|
||||
|
||||
override fun initializeDependencies(): Any {
|
||||
val dependencies = DaggerPushNotificationsFeatureComponent_PushNotificationsFeatureDependenciesComponent.builder()
|
||||
.commonApi(commonApi())
|
||||
.runtimeApi(getFeature(RuntimeApi::class.java))
|
||||
.accountFeatureApi(getFeature(AccountFeatureApi::class.java))
|
||||
.governanceFeatureApi(getFeature(GovernanceFeatureApi::class.java))
|
||||
.walletFeatureApi(getFeature(WalletFeatureApi::class.java))
|
||||
.assetsFeatureApi(getFeature(AssetsFeatureApi::class.java))
|
||||
.multisigOperationsFeatureApi(getFeature(MultisigOperationsFeatureApi::class.java))
|
||||
.build()
|
||||
|
||||
return DaggerPushNotificationsFeatureComponent.factory()
|
||||
.create(
|
||||
router,
|
||||
selectMultipleWalletsCommunicator,
|
||||
selectTracksCommunicator,
|
||||
pushGovernanceSettingsCommunicator,
|
||||
pushStakingSettingsCommunicator,
|
||||
pushMultisigSettingsCommunicator,
|
||||
dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.di
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.Gson
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.data.GoogleApiAvailabilityProvider
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.interfaces.BuildTypeProvider
|
||||
import io.novafoundation.nova.common.utils.coroutines.RootScope
|
||||
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
|
||||
import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PushNotificationsService
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PushPermissionRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PushTokenCache
|
||||
import io.novafoundation.nova.feature_push_notifications.data.RealPushNotificationsService
|
||||
import io.novafoundation.nova.feature_push_notifications.data.RealPushPermissionRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.RealPushTokenCache
|
||||
import io.novafoundation.nova.feature_push_notifications.data.repository.MultisigPushAlertRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.repository.PushSettingsRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.repository.RealMultisigPushAlertRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.repository.RealPushSettingsRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.PushSettingsProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.PushSettingsSerializer
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.RealPushSettingsProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.data.subscription.PushSubscriptionService
|
||||
import io.novafoundation.nova.feature_push_notifications.data.subscription.RealPushSubscriptionService
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.GovernancePushSettingsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.MultisigPushAlertInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.RealGovernancePushSettingsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.RealMultisigPushAlertInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.RealPushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.RealStakingPushSettingsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.RealWelcomePushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.StakingPushSettingsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.WelcomePushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigsWarning.MultisigPushNotificationsAlertMixinFactory
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class PushSettingsSerialization
|
||||
|
||||
@Module(includes = [NotificationHandlersModule::class])
|
||||
class PushNotificationsFeatureModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePushTokenCache(
|
||||
preferences: Preferences
|
||||
): PushTokenCache {
|
||||
return RealPushTokenCache(preferences)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
@PushSettingsSerialization
|
||||
fun providePushSettingsGson() = PushSettingsSerializer.gson()
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePushSettingsProvider(
|
||||
@PushSettingsSerialization gson: Gson,
|
||||
preferences: Preferences,
|
||||
accountRepository: AccountRepository
|
||||
): PushSettingsProvider {
|
||||
return RealPushSettingsProvider(gson, preferences, accountRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePushSubscriptionService(
|
||||
prefs: Preferences,
|
||||
chainRegistry: ChainRegistry,
|
||||
googleApiAvailabilityProvider: GoogleApiAvailabilityProvider,
|
||||
accountRepository: AccountRepository
|
||||
): PushSubscriptionService {
|
||||
return RealPushSubscriptionService(
|
||||
prefs,
|
||||
chainRegistry,
|
||||
googleApiAvailabilityProvider,
|
||||
accountRepository
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePushPermissionRepository(context: Context): PushPermissionRepository {
|
||||
return RealPushPermissionRepository(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePushNotificationsService(
|
||||
pushSettingsProvider: PushSettingsProvider,
|
||||
pushSubscriptionService: PushSubscriptionService,
|
||||
rootScope: RootScope,
|
||||
pushTokenCache: PushTokenCache,
|
||||
googleApiAvailabilityProvider: GoogleApiAvailabilityProvider,
|
||||
pushPermissionRepository: PushPermissionRepository,
|
||||
preferences: Preferences,
|
||||
buildTypeProvider: BuildTypeProvider
|
||||
): PushNotificationsService {
|
||||
return RealPushNotificationsService(
|
||||
pushSettingsProvider,
|
||||
pushSubscriptionService,
|
||||
rootScope,
|
||||
pushTokenCache,
|
||||
googleApiAvailabilityProvider,
|
||||
pushPermissionRepository,
|
||||
preferences,
|
||||
buildTypeProvider
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePushSettingsRepository(preferences: Preferences): PushSettingsRepository {
|
||||
return RealPushSettingsRepository(preferences)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePushNotificationsInteractor(
|
||||
pushNotificationsService: PushNotificationsService,
|
||||
pushSettingsProvider: PushSettingsProvider,
|
||||
accountRepository: AccountRepository,
|
||||
pushSettingsRepository: PushSettingsRepository
|
||||
): PushNotificationsInteractor {
|
||||
return RealPushNotificationsInteractor(pushNotificationsService, pushSettingsProvider, accountRepository, pushSettingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideWelcomePushNotificationsInteractor(
|
||||
preferences: Preferences,
|
||||
pushNotificationsService: PushNotificationsService
|
||||
): WelcomePushNotificationsInteractor {
|
||||
return RealWelcomePushNotificationsInteractor(preferences, pushNotificationsService)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideGovernancePushSettingsInteractor(
|
||||
chainRegistry: ChainRegistry,
|
||||
governanceSourceRegistry: GovernanceSourceRegistry
|
||||
): GovernancePushSettingsInteractor {
|
||||
return RealGovernancePushSettingsInteractor(
|
||||
chainRegistry,
|
||||
governanceSourceRegistry
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideStakingPushSettingsInteractor(chainRegistry: ChainRegistry): StakingPushSettingsInteractor {
|
||||
return RealStakingPushSettingsInteractor(chainRegistry)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMultisigPushAlertRepository(
|
||||
preferences: Preferences
|
||||
): MultisigPushAlertRepository {
|
||||
return RealMultisigPushAlertRepository(preferences)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMultisigPushAlertInteractor(
|
||||
pushSettingsProvider: PushSettingsProvider,
|
||||
accountRepository: AccountRepository,
|
||||
multisigPushAlertRepository: MultisigPushAlertRepository
|
||||
): MultisigPushAlertInteractor {
|
||||
return RealMultisigPushAlertInteractor(
|
||||
pushSettingsProvider,
|
||||
accountRepository,
|
||||
multisigPushAlertRepository
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideMultisigPushNotificationsAlertMixin(
|
||||
automaticInteractionGate: AutomaticInteractionGate,
|
||||
interactor: MultisigPushAlertInteractor,
|
||||
metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry,
|
||||
router: PushNotificationsRouter
|
||||
): MultisigPushNotificationsAlertMixinFactory {
|
||||
return MultisigPushNotificationsAlertMixinFactory(
|
||||
automaticInteractionGate,
|
||||
interactor,
|
||||
metaAccountsUpdatesRegistry,
|
||||
router
|
||||
)
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.domain.interactor
|
||||
|
||||
import io.novafoundation.nova.common.utils.mapToSet
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
|
||||
import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry
|
||||
import io.novafoundation.nova.runtime.ext.defaultComparatorFrom
|
||||
import io.novafoundation.nova.runtime.ext.openGovIfSupported
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.enabledChainsFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ChainWithGovTracks(
|
||||
val chain: Chain,
|
||||
val govVersion: Chain.Governance,
|
||||
val tracks: Set<TrackId>
|
||||
)
|
||||
|
||||
interface GovernancePushSettingsInteractor {
|
||||
|
||||
fun governanceChainsFlow(): Flow<List<ChainWithGovTracks>>
|
||||
}
|
||||
|
||||
class RealGovernancePushSettingsInteractor(
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val governanceSourceRegistry: GovernanceSourceRegistry
|
||||
) : GovernancePushSettingsInteractor {
|
||||
|
||||
override fun governanceChainsFlow(): Flow<List<ChainWithGovTracks>> {
|
||||
return chainRegistry.enabledChainsFlow()
|
||||
.map { chains ->
|
||||
chains.filter { it.pushSupport }
|
||||
.flatMap { it.supportedGovTypes() }
|
||||
.map { (chain, govType) -> ChainWithGovTracks(chain, govType, getTrackIds(chain, govType)) }
|
||||
.sortedWith(Chain.defaultComparatorFrom(ChainWithGovTracks::chain))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Chain.supportedGovTypes(): List<Pair<Chain, Chain.Governance>> {
|
||||
return listOfNotNull(openGovIfSupported()?.let { this to it })
|
||||
}
|
||||
|
||||
private suspend fun getTrackIds(chain: Chain, governance: Chain.Governance): Set<TrackId> {
|
||||
return governanceSourceRegistry.sourceFor(governance)
|
||||
.referenda
|
||||
.getTracks(chain.id)
|
||||
.mapToSet { it.id }
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.domain.interactor
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.isMultisig
|
||||
import io.novafoundation.nova.feature_push_notifications.data.repository.MultisigPushAlertRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.PushSettingsProvider
|
||||
|
||||
interface MultisigPushAlertInteractor {
|
||||
|
||||
fun isPushNotificationsEnabled(): Boolean
|
||||
|
||||
fun isAlertAlreadyShown(): Boolean
|
||||
|
||||
fun setAlertWasAlreadyShown()
|
||||
|
||||
suspend fun allowedToShowAlertAtStart(): Boolean
|
||||
|
||||
suspend fun hasMultisigWallets(consumedMetaIdsUpdates: List<Long>): Boolean
|
||||
}
|
||||
|
||||
enum class AllowingState {
|
||||
INITIAL, ALLOWED, NOT_ALLOWED
|
||||
}
|
||||
|
||||
class RealMultisigPushAlertInteractor(
|
||||
private val pushSettingsProvider: PushSettingsProvider,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val multisigPushAlertRepository: MultisigPushAlertRepository
|
||||
) : MultisigPushAlertInteractor {
|
||||
|
||||
override fun isPushNotificationsEnabled(): Boolean {
|
||||
return pushSettingsProvider.isPushNotificationsEnabled()
|
||||
}
|
||||
|
||||
override fun isAlertAlreadyShown(): Boolean {
|
||||
return multisigPushAlertRepository.isMultisigsPushAlertWasShown()
|
||||
}
|
||||
|
||||
override fun setAlertWasAlreadyShown() {
|
||||
multisigPushAlertRepository.setMultisigsPushAlertWasShown()
|
||||
}
|
||||
|
||||
/**
|
||||
* We have to check if we can show alert right after user update the app.
|
||||
* Showing is allowed when user have multisig accounts in first app start after update
|
||||
*/
|
||||
override suspend fun allowedToShowAlertAtStart(): Boolean {
|
||||
val allowingState = multisigPushAlertRepository.showAlertAtStartAllowingState()
|
||||
|
||||
if (allowingState == AllowingState.INITIAL) {
|
||||
val userHasMultisigs = accountRepository.hasMetaAccountsByType(LightMetaAccount.Type.MULTISIG)
|
||||
if (userHasMultisigs) {
|
||||
multisigPushAlertRepository.setAlertAtStartAllowingState(AllowingState.ALLOWED)
|
||||
return true
|
||||
} else {
|
||||
multisigPushAlertRepository.setAlertAtStartAllowingState(AllowingState.NOT_ALLOWED)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return allowingState == AllowingState.ALLOWED
|
||||
}
|
||||
|
||||
override suspend fun hasMultisigWallets(consumedMetaIdsUpdates: List<Long>): Boolean {
|
||||
val consumedMetaAccountUpdates = accountRepository.getMetaAccountsByIds(consumedMetaIdsUpdates)
|
||||
return consumedMetaAccountUpdates.any { it.isMultisig() }
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.domain.interactor
|
||||
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PushNotificationsAvailabilityState
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PushNotificationsService
|
||||
import io.novafoundation.nova.feature_push_notifications.data.repository.PushSettingsRepository
|
||||
import io.novafoundation.nova.feature_push_notifications.data.settings.PushSettingsProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface PushNotificationsInteractor {
|
||||
|
||||
suspend fun initialSyncSettings()
|
||||
|
||||
fun pushNotificationsEnabledFlow(): Flow<Boolean>
|
||||
|
||||
suspend fun initPushSettings(): Result<Unit>
|
||||
|
||||
suspend fun updatePushSettings(enable: Boolean, pushSettings: PushSettings): Result<Unit>
|
||||
|
||||
suspend fun getPushSettings(): PushSettings
|
||||
|
||||
suspend fun getDefaultSettings(): PushSettings
|
||||
|
||||
suspend fun getMetaAccounts(metaIds: List<Long>): List<MetaAccount>
|
||||
|
||||
fun isPushNotificationsEnabled(): Boolean
|
||||
|
||||
fun isMultisigsWasEnabledFirstTime(): Boolean
|
||||
|
||||
fun setMultisigsWasEnabledFirstTime()
|
||||
|
||||
fun pushNotificationsAvailabilityState(): PushNotificationsAvailabilityState
|
||||
|
||||
fun isPushNotificationsAvailable(): Boolean
|
||||
|
||||
suspend fun onMetaAccountChange(changed: List<Long>, deleted: List<Long>)
|
||||
|
||||
suspend fun filterAvailableMetaIdsAndGetNewState(pushSettings: PushSettings): PushSettings
|
||||
|
||||
suspend fun getNewStateForChangedMetaAccounts(currentSettings: PushSettings, newMetaIds: Set<Long>): PushSettings
|
||||
}
|
||||
|
||||
class RealPushNotificationsInteractor(
|
||||
private val pushNotificationsService: PushNotificationsService,
|
||||
private val pushSettingsProvider: PushSettingsProvider,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val pushSettingsRepository: PushSettingsRepository
|
||||
) : PushNotificationsInteractor {
|
||||
|
||||
override suspend fun initialSyncSettings() {
|
||||
pushNotificationsService.syncSettingsIfNeeded()
|
||||
}
|
||||
|
||||
override fun pushNotificationsEnabledFlow(): Flow<Boolean> {
|
||||
return pushSettingsProvider.pushEnabledFlow()
|
||||
}
|
||||
|
||||
override suspend fun initPushSettings(): Result<Unit> {
|
||||
return pushNotificationsService.initPushNotifications()
|
||||
}
|
||||
|
||||
override suspend fun updatePushSettings(enable: Boolean, pushSettings: PushSettings): Result<Unit> {
|
||||
return pushNotificationsService.updatePushSettings(enable, pushSettings)
|
||||
}
|
||||
|
||||
override suspend fun getPushSettings(): PushSettings {
|
||||
return pushSettingsProvider.getPushSettings()
|
||||
}
|
||||
|
||||
override suspend fun getDefaultSettings(): PushSettings {
|
||||
return pushSettingsProvider.getDefaultPushSettings()
|
||||
}
|
||||
|
||||
override suspend fun getMetaAccounts(metaIds: List<Long>): List<MetaAccount> {
|
||||
return accountRepository.getMetaAccountsByIds(metaIds)
|
||||
}
|
||||
|
||||
override fun isPushNotificationsEnabled(): Boolean {
|
||||
return pushSettingsProvider.isPushNotificationsEnabled()
|
||||
}
|
||||
|
||||
override fun isMultisigsWasEnabledFirstTime(): Boolean {
|
||||
return pushSettingsRepository.isMultisigsWasEnabledFirstTime()
|
||||
}
|
||||
|
||||
override fun setMultisigsWasEnabledFirstTime() {
|
||||
pushSettingsRepository.setMultisigsWasEnabledFirstTime()
|
||||
}
|
||||
|
||||
override fun isPushNotificationsAvailable(): Boolean {
|
||||
return pushNotificationsService.isPushNotificationsAvailable()
|
||||
}
|
||||
|
||||
override fun pushNotificationsAvailabilityState(): PushNotificationsAvailabilityState {
|
||||
return pushNotificationsService.pushNotificationsAvaiabilityState()
|
||||
}
|
||||
|
||||
override suspend fun onMetaAccountChange(changed: List<Long>, deleted: List<Long>) {
|
||||
if (changed.isEmpty() && deleted.isEmpty()) return
|
||||
|
||||
val notificationsEnabled = pushSettingsProvider.isPushNotificationsEnabled()
|
||||
val noAccounts = accountRepository.getActiveMetaAccountsQuantity() == 0
|
||||
val pushSettings = pushSettingsProvider.getPushSettings()
|
||||
|
||||
val allAffected = (changed + deleted).toSet()
|
||||
val subscribedAccountsAffected = pushSettings.subscribedMetaAccounts.intersect(allAffected).isNotEmpty()
|
||||
|
||||
when {
|
||||
notificationsEnabled && noAccounts -> pushNotificationsService.updatePushSettings(enabled = false, pushSettings = null)
|
||||
noAccounts -> pushSettingsProvider.updateSettings(pushWalletSettings = null)
|
||||
subscribedAccountsAffected -> {
|
||||
val newSubscribedMetaAccounts = pushSettings.subscribedMetaAccounts - deleted.toSet()
|
||||
val newEnabledState = notificationsEnabled && newSubscribedMetaAccounts.isNotEmpty()
|
||||
val newPushSettings = getNewStateForChangedMetaAccounts(pushSettings, newSubscribedMetaAccounts)
|
||||
pushNotificationsService.updatePushSettings(enabled = newEnabledState, pushSettings = newPushSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun filterAvailableMetaIdsAndGetNewState(pushSettings: PushSettings): PushSettings {
|
||||
val availableMetaIds = accountRepository.getAvailableMetaIdsFromSet(pushSettings.subscribedMetaAccounts)
|
||||
if (availableMetaIds == pushSettings.subscribedMetaAccounts) return pushSettings
|
||||
|
||||
return getNewStateForChangedMetaAccounts(pushSettings, availableMetaIds)
|
||||
}
|
||||
|
||||
override suspend fun getNewStateForChangedMetaAccounts(currentSettings: PushSettings, newMetaIds: Set<Long>): PushSettings {
|
||||
val noMultisigWalletsForNewAccounts = !accountRepository.hasMetaAccountsByType(newMetaIds, LightMetaAccount.Type.MULTISIG)
|
||||
if (noMultisigWalletsForNewAccounts) {
|
||||
val disabledMultisigSettings = PushSettings.MultisigsState.disabled()
|
||||
return currentSettings.copy(subscribedMetaAccounts = newMetaIds, multisigs = disabledMultisigSettings)
|
||||
}
|
||||
|
||||
return if (!currentSettings.multisigs.isEnabled) {
|
||||
currentSettings.copy(subscribedMetaAccounts = newMetaIds, multisigs = PushSettings.MultisigsState.enabled())
|
||||
} else {
|
||||
currentSettings.copy(subscribedMetaAccounts = newMetaIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.domain.interactor
|
||||
|
||||
import io.novafoundation.nova.feature_staking_api.data.dashboard.common.supportedStakingOptions
|
||||
import io.novafoundation.nova.runtime.ext.defaultComparator
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
interface StakingPushSettingsInteractor {
|
||||
|
||||
fun stakingChainsFlow(): Flow<List<Chain>>
|
||||
}
|
||||
|
||||
class RealStakingPushSettingsInteractor(
|
||||
private val chainRegistry: ChainRegistry
|
||||
) : StakingPushSettingsInteractor {
|
||||
|
||||
override fun stakingChainsFlow(): Flow<List<Chain>> {
|
||||
return chainRegistry.stakingChainsFlow()
|
||||
.map { chains ->
|
||||
chains.filter { it.pushSupport }
|
||||
.sortedWith(Chain.defaultComparator())
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChainRegistry.stakingChainsFlow(): Flow<List<Chain>> {
|
||||
return currentChains.map { chains ->
|
||||
chains.filter { it.supportedStakingOptions() }
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.domain.interactor
|
||||
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.feature_push_notifications.data.PushNotificationsService
|
||||
|
||||
interface WelcomePushNotificationsInteractor {
|
||||
fun needToShowWelcomeScreen(): Boolean
|
||||
|
||||
fun setWelcomeScreenShown()
|
||||
}
|
||||
|
||||
class RealWelcomePushNotificationsInteractor(
|
||||
private val preferences: Preferences,
|
||||
private val pushNotificationsService: PushNotificationsService
|
||||
) : WelcomePushNotificationsInteractor {
|
||||
|
||||
override fun needToShowWelcomeScreen(): Boolean {
|
||||
return pushNotificationsService.isPushNotificationsAvailable() &&
|
||||
preferences.getBoolean(PREFS_WELCOME_SCREEN_SHOWN, true)
|
||||
}
|
||||
|
||||
override fun setWelcomeScreenShown() {
|
||||
return preferences.putBoolean(PREFS_WELCOME_SCREEN_SHOWN, false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFS_WELCOME_SCREEN_SHOWN = "welcome_screen_shown"
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.domain.model
|
||||
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
data class PushSettings(
|
||||
val announcementsEnabled: Boolean,
|
||||
val sentTokensEnabled: Boolean,
|
||||
val receivedTokensEnabled: Boolean,
|
||||
val subscribedMetaAccounts: Set<Long>,
|
||||
val stakingReward: ChainFeature,
|
||||
val governance: Map<ChainId, GovernanceState>,
|
||||
val multisigs: MultisigsState
|
||||
) {
|
||||
|
||||
data class GovernanceState(
|
||||
val newReferendaEnabled: Boolean,
|
||||
val referendumUpdateEnabled: Boolean,
|
||||
val govMyDelegateVotedEnabled: Boolean,
|
||||
val tracks: Set<TrackId>
|
||||
)
|
||||
|
||||
data class MultisigsState(
|
||||
val isEnabled: Boolean, // General notifications state. Other states may be enabled to save state when general one is disabled
|
||||
val isInitiatingEnabled: Boolean,
|
||||
val isApprovingEnabled: Boolean,
|
||||
val isExecutionEnabled: Boolean,
|
||||
val isRejectionEnabled: Boolean
|
||||
) {
|
||||
companion object {
|
||||
fun disabled() = MultisigsState(
|
||||
isEnabled = false,
|
||||
isInitiatingEnabled = false,
|
||||
isApprovingEnabled = false,
|
||||
isExecutionEnabled = false,
|
||||
isRejectionEnabled = false
|
||||
)
|
||||
|
||||
fun enabled() = MultisigsState(
|
||||
isEnabled = true,
|
||||
isInitiatingEnabled = true,
|
||||
isApprovingEnabled = true,
|
||||
isExecutionEnabled = true,
|
||||
isRejectionEnabled = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ChainFeature {
|
||||
|
||||
object All : ChainFeature()
|
||||
|
||||
data class Concrete(val chainIds: List<ChainId>) : ChainFeature()
|
||||
}
|
||||
|
||||
fun settingsIsEmpty(): Boolean {
|
||||
return !announcementsEnabled &&
|
||||
!sentTokensEnabled &&
|
||||
!receivedTokensEnabled &&
|
||||
stakingReward.isEmpty() &&
|
||||
!isGovEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
fun PushSettings.ChainFeature.isEmpty(): Boolean {
|
||||
return when (this) {
|
||||
is PushSettings.ChainFeature.All -> false
|
||||
is PushSettings.ChainFeature.Concrete -> chainIds.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
fun PushSettings.ChainFeature.isNotEmpty(): Boolean {
|
||||
return !isEmpty()
|
||||
}
|
||||
|
||||
fun PushSettings.isGovEnabled(): Boolean {
|
||||
return governance.values.any {
|
||||
(it.newReferendaEnabled || it.referendumUpdateEnabled || it.govMyDelegateVotedEnabled) && it.tracks.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
fun PushSettings.MultisigsState.isAllTypesDisabled(): Boolean {
|
||||
return !isInitiatingEnabled && !isApprovingEnabled && !isExecutionEnabled && !isRejectionEnabled
|
||||
}
|
||||
|
||||
fun PushSettings.MultisigsState.disableIfAllTypesDisabled(): PushSettings.MultisigsState {
|
||||
return if (isAllTypesDisabled()) copy(isEnabled = false) else this
|
||||
}
|
||||
|
||||
fun PushSettings.MultisigsState.isInitiatingEnabledTotal() = isInitiatingEnabled && isEnabled
|
||||
fun PushSettings.MultisigsState.isApprovingEnabledTotal() = isApprovingEnabled && isEnabled
|
||||
fun PushSettings.MultisigsState.isExecutionEnabledTotal() = isExecutionEnabled && isEnabled
|
||||
fun PushSettings.MultisigsState.isRejectionEnabledTotal() = isRejectionEnabled && isEnabled
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.governance
|
||||
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
data class PushGovernanceModel(
|
||||
val chainId: ChainId,
|
||||
val governance: Chain.Governance,
|
||||
val chainName: String,
|
||||
val chainIconUrl: String?,
|
||||
val isEnabled: Boolean,
|
||||
val isNewReferendaEnabled: Boolean,
|
||||
val isReferendaUpdatesEnabled: Boolean,
|
||||
val trackIds: Set<TrackId>
|
||||
) {
|
||||
companion object
|
||||
}
|
||||
|
||||
fun PushGovernanceModel.Companion.default(
|
||||
chain: Chain,
|
||||
governance: Chain.Governance,
|
||||
tracks: Set<TrackId>
|
||||
): PushGovernanceModel {
|
||||
return PushGovernanceModel(
|
||||
chainId = chain.id,
|
||||
governance = governance,
|
||||
chainName = chain.name,
|
||||
chainIconUrl = chain.icon,
|
||||
false,
|
||||
isNewReferendaEnabled = true,
|
||||
isReferendaUpdatesEnabled = true,
|
||||
trackIds = tracks
|
||||
)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.governance
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.common.navigation.InterScreenRequester
|
||||
import io.novafoundation.nova.common.navigation.InterScreenResponder
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import java.math.BigInteger
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
interface PushGovernanceSettingsRequester : InterScreenRequester<PushGovernanceSettingsRequester.Request, PushGovernanceSettingsResponder.Response> {
|
||||
|
||||
@Parcelize
|
||||
class Request(val enabledGovernanceSettings: List<PushGovernanceSettingsPayload>) : Parcelable
|
||||
}
|
||||
|
||||
interface PushGovernanceSettingsResponder : InterScreenResponder<PushGovernanceSettingsRequester.Request, PushGovernanceSettingsResponder.Response> {
|
||||
|
||||
@Parcelize
|
||||
class Response(val enabledGovernanceSettings: List<PushGovernanceSettingsPayload>) : Parcelable
|
||||
}
|
||||
|
||||
interface PushGovernanceSettingsCommunicator : PushGovernanceSettingsRequester, PushGovernanceSettingsResponder
|
||||
|
||||
@Parcelize
|
||||
class PushGovernanceSettingsPayload(
|
||||
val chainId: ChainId,
|
||||
val governance: Chain.Governance,
|
||||
val newReferenda: Boolean,
|
||||
val referendaUpdates: Boolean,
|
||||
val delegateVotes: Boolean,
|
||||
val tracksIds: Set<BigInteger>
|
||||
) : Parcelable
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.governance
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.view.isVisible
|
||||
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.domain.ExtendedLoadingState
|
||||
import io.novafoundation.nova.common.utils.observe
|
||||
import io.novafoundation.nova.feature_push_notifications.databinding.FragmentPushGovernanceSettingsBinding
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureComponent
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.adapter.PushGovernanceRVItem
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.adapter.PushGovernanceSettingsAdapter
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushGovernanceSettingsFragment :
|
||||
BaseFragment<PushGovernanceSettingsViewModel, FragmentPushGovernanceSettingsBinding>(),
|
||||
PushGovernanceSettingsAdapter.ItemHandler {
|
||||
|
||||
companion object {
|
||||
private const val KEY_REQUEST = "KEY_REQUEST"
|
||||
|
||||
fun getBundle(request: PushGovernanceSettingsRequester.Request): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(KEY_REQUEST, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createBinding() = FragmentPushGovernanceSettingsBinding.inflate(layoutInflater)
|
||||
|
||||
@Inject
|
||||
lateinit var imageLoader: ImageLoader
|
||||
|
||||
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
PushGovernanceSettingsAdapter(imageLoader, this)
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
binder.pushGovernanceToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
binder.pushGovernanceToolbar.setRightActionClickListener { viewModel.clearClicked() }
|
||||
onBackPressed { viewModel.backClicked() }
|
||||
|
||||
binder.pushGovernanceList.adapter = adapter
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<PushNotificationsFeatureComponent>(requireContext(), PushNotificationsFeatureApi::class.java)
|
||||
.pushGovernanceSettings()
|
||||
.create(this, argument(KEY_REQUEST))
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: PushGovernanceSettingsViewModel) {
|
||||
viewModel.clearButtonEnabledFlow.observe {
|
||||
binder.pushGovernanceToolbar.setRightActionEnabled(it)
|
||||
}
|
||||
|
||||
viewModel.governanceSettingsList.observe {
|
||||
binder.pushGovernanceList.isVisible = it is ExtendedLoadingState.Loaded
|
||||
binder.pushGovernanceProgress.isVisible = it is ExtendedLoadingState.Loading
|
||||
|
||||
if (it is ExtendedLoadingState.Loaded) {
|
||||
adapter.submitList(it.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun enableSwitcherClick(item: PushGovernanceRVItem) {
|
||||
viewModel.enableSwitcherClicked(item)
|
||||
}
|
||||
|
||||
override fun newReferendaClick(item: PushGovernanceRVItem) {
|
||||
viewModel.newReferendaClicked(item)
|
||||
}
|
||||
|
||||
override fun referendaUpdatesClick(item: PushGovernanceRVItem) {
|
||||
viewModel.referendaUpdatesClicked(item)
|
||||
}
|
||||
|
||||
override fun tracksClicked(item: PushGovernanceRVItem) {
|
||||
viewModel.tracksClicked(item)
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.governance
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.mapToSet
|
||||
import io.novafoundation.nova.common.utils.updateValue
|
||||
import io.novafoundation.nova.common.utils.withSafeLoading
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksRequester
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.fromTrackIds
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.toTrackIds
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.ChainWithGovTracks
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.GovernancePushSettingsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.adapter.PushGovernanceRVItem
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chainsById
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val MIN_TRACKS = 1
|
||||
|
||||
data class GovChainKey(val chainId: ChainId, val governance: Chain.Governance)
|
||||
|
||||
class PushGovernanceSettingsViewModel(
|
||||
private val router: PushNotificationsRouter,
|
||||
private val interactor: GovernancePushSettingsInteractor,
|
||||
private val pushGovernanceSettingsResponder: PushGovernanceSettingsResponder,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val request: PushGovernanceSettingsRequester.Request,
|
||||
private val selectTracksRequester: SelectTracksRequester,
|
||||
private val resourceManager: ResourceManager
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val chainsWithTracks = interactor.governanceChainsFlow()
|
||||
.shareInBackground()
|
||||
|
||||
val _changedGovernanceSettingsList: MutableStateFlow<Map<GovChainKey, PushGovernanceModel>> = MutableStateFlow(emptyMap())
|
||||
|
||||
val governanceSettingsList = combine(chainsWithTracks, _changedGovernanceSettingsList) { chainsWithTracksQuantity, changedSettings ->
|
||||
chainsWithTracksQuantity.map { chainAndTracks ->
|
||||
val pushSettingsModel = changedSettings[chainAndTracks.key()]
|
||||
?: PushGovernanceModel.default(
|
||||
chainAndTracks.chain,
|
||||
chainAndTracks.govVersion,
|
||||
chainAndTracks.tracks
|
||||
)
|
||||
|
||||
PushGovernanceRVItem(
|
||||
pushSettingsModel,
|
||||
formatTracksText(pushSettingsModel.trackIds, chainAndTracks.tracks)
|
||||
)
|
||||
}
|
||||
}.withSafeLoading()
|
||||
|
||||
val clearButtonEnabledFlow = _changedGovernanceSettingsList.map {
|
||||
it.any { it.value.isEnabled }
|
||||
}
|
||||
|
||||
init {
|
||||
launch {
|
||||
val chainsById = chainRegistry.chainsById()
|
||||
|
||||
_changedGovernanceSettingsList.value = request.enabledGovernanceSettings
|
||||
.mapNotNull { chainIdToSettings ->
|
||||
val chain = chainsById[chainIdToSettings.chainId] ?: return@mapNotNull null
|
||||
mapCommunicatorModelToItem(chainIdToSettings, chain)
|
||||
}.associateBy { it.key() }
|
||||
}
|
||||
|
||||
subscribeOnSelectTracks()
|
||||
}
|
||||
|
||||
fun backClicked() {
|
||||
launch {
|
||||
val enabledGovernanceSettings = _changedGovernanceSettingsList.value
|
||||
.values
|
||||
.filter { it.isEnabled }
|
||||
.map { mapItemToCommunicatorModel(it) }
|
||||
|
||||
val response = PushGovernanceSettingsResponder.Response(enabledGovernanceSettings)
|
||||
pushGovernanceSettingsResponder.respond(response)
|
||||
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
fun enableSwitcherClicked(item: PushGovernanceRVItem) {
|
||||
_changedGovernanceSettingsList.updateValue {
|
||||
it + item.model.copy(isEnabled = !item.isEnabled)
|
||||
.enableEverythingIfFeaturesDisabled()
|
||||
.withKey()
|
||||
}
|
||||
}
|
||||
|
||||
fun newReferendaClicked(item: PushGovernanceRVItem) {
|
||||
_changedGovernanceSettingsList.updateValue {
|
||||
it + item.model.copy(isNewReferendaEnabled = !item.isNewReferendaEnabled)
|
||||
.disableCompletelyIfFeaturesDisabled()
|
||||
.withKey()
|
||||
}
|
||||
}
|
||||
|
||||
fun referendaUpdatesClicked(item: PushGovernanceRVItem) {
|
||||
_changedGovernanceSettingsList.updateValue {
|
||||
it + item.model.copy(isReferendaUpdatesEnabled = !item.isReferendaUpdatesEnabled)
|
||||
.disableCompletelyIfFeaturesDisabled()
|
||||
.withKey()
|
||||
}
|
||||
}
|
||||
|
||||
fun tracksClicked(item: PushGovernanceRVItem) {
|
||||
launch {
|
||||
val selectedTracks = item.model.trackIds.mapToSet { it.value }
|
||||
selectTracksRequester.openRequest(SelectTracksRequester.Request(item.chainId, item.governance, selectedTracks, MIN_TRACKS))
|
||||
}
|
||||
}
|
||||
|
||||
fun clearClicked() {
|
||||
_changedGovernanceSettingsList.value = emptyMap()
|
||||
}
|
||||
|
||||
private fun mapCommunicatorModelToItem(
|
||||
item: PushGovernanceSettingsPayload,
|
||||
chain: Chain
|
||||
): PushGovernanceModel {
|
||||
val tracks = item.tracksIds.toTrackIds()
|
||||
return PushGovernanceModel(
|
||||
chainId = item.chainId,
|
||||
governance = item.governance,
|
||||
chainName = chain.name,
|
||||
chainIconUrl = chain.icon,
|
||||
isEnabled = true,
|
||||
isNewReferendaEnabled = item.newReferenda,
|
||||
isReferendaUpdatesEnabled = item.referendaUpdates,
|
||||
trackIds = tracks
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapItemToCommunicatorModel(item: PushGovernanceModel): PushGovernanceSettingsPayload {
|
||||
return PushGovernanceSettingsPayload(
|
||||
item.chainId,
|
||||
item.governance,
|
||||
item.isNewReferendaEnabled,
|
||||
item.isReferendaUpdatesEnabled,
|
||||
false, // Not supported yet
|
||||
item.trackIds.fromTrackIds()
|
||||
)
|
||||
}
|
||||
|
||||
private fun PushGovernanceModel.disableCompletelyIfFeaturesDisabled(): PushGovernanceModel {
|
||||
if (!isNewReferendaEnabled && !isReferendaUpdatesEnabled) {
|
||||
return copy(isEnabled = false)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun PushGovernanceModel.enableEverythingIfFeaturesDisabled(): PushGovernanceModel {
|
||||
if (!isNewReferendaEnabled && !isReferendaUpdatesEnabled) {
|
||||
return copy(isEnabled = true, isNewReferendaEnabled = true, isReferendaUpdatesEnabled = true)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun subscribeOnSelectTracks() {
|
||||
selectTracksRequester.responseFlow
|
||||
.onEach { response ->
|
||||
val chain = chainRegistry.getChain(response.chainId)
|
||||
val key = GovChainKey(response.chainId, response.governanceType)
|
||||
val selectedTracks = response.selectedTracks.toTrackIds()
|
||||
|
||||
_changedGovernanceSettingsList.updateValue { governanceSettings ->
|
||||
val model = governanceSettings[key]?.copy(trackIds = selectedTracks)
|
||||
?: PushGovernanceModel.default(
|
||||
chain = chain,
|
||||
governance = response.governanceType,
|
||||
tracks = selectedTracks
|
||||
)
|
||||
|
||||
governanceSettings.plus(key to model)
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun PushGovernanceModel.key() = GovChainKey(chainId, governance)
|
||||
|
||||
private fun PushGovernanceModel.withKey() = key() to this
|
||||
|
||||
private fun ChainWithGovTracks.key() = GovChainKey(chain.id, govVersion)
|
||||
|
||||
private fun formatTracksText(selectedTracks: Set<TrackId>, allTracks: Set<TrackId>): String {
|
||||
return if (selectedTracks.size == allTracks.size) {
|
||||
resourceManager.getString(R.string.common_all)
|
||||
} else {
|
||||
resourceManager.getString(R.string.selected_tracks_quantity, selectedTracks.size, allTracks.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.governance.adapter
|
||||
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceModel
|
||||
|
||||
data class PushGovernanceRVItem(
|
||||
val model: PushGovernanceModel,
|
||||
val tracksText: String
|
||||
) {
|
||||
val chainId = model.chainId
|
||||
|
||||
val governance = model.governance
|
||||
|
||||
val chainName = model.chainName
|
||||
|
||||
val chainIconUrl = model.chainIconUrl
|
||||
|
||||
val isEnabled = model.isEnabled
|
||||
|
||||
val isNewReferendaEnabled = model.isNewReferendaEnabled
|
||||
|
||||
val isReferendaUpdatesEnabled = model.isReferendaUpdatesEnabled
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.governance.adapter
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.list.PayloadGenerator
|
||||
import io.novafoundation.nova.common.list.resolvePayload
|
||||
import io.novafoundation.nova.common.utils.inflater
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.loadChainIconToTarget
|
||||
import io.novafoundation.nova.feature_push_notifications.databinding.ItemPushGovernanceSettingsBinding
|
||||
|
||||
class PushGovernanceSettingsAdapter(
|
||||
private val imageLoader: ImageLoader,
|
||||
private val itemHandler: ItemHandler
|
||||
) : ListAdapter<PushGovernanceRVItem, PushGovernanceItemViewHolder>(PushGovernanceItemCallback()) {
|
||||
|
||||
interface ItemHandler {
|
||||
fun enableSwitcherClick(item: PushGovernanceRVItem)
|
||||
|
||||
fun newReferendaClick(item: PushGovernanceRVItem)
|
||||
|
||||
fun referendaUpdatesClick(item: PushGovernanceRVItem)
|
||||
|
||||
fun tracksClicked(item: PushGovernanceRVItem)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PushGovernanceItemViewHolder {
|
||||
return PushGovernanceItemViewHolder(ItemPushGovernanceSettingsBinding.inflate(parent.inflater(), parent, false), imageLoader, itemHandler)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PushGovernanceItemViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PushGovernanceItemViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
resolvePayload(holder, position, payloads) {
|
||||
val item = getItem(position)
|
||||
holder.updateListenners(item)
|
||||
|
||||
when (it) {
|
||||
PushGovernanceRVItem::isEnabled -> holder.setEnabled(item)
|
||||
PushGovernanceRVItem::isNewReferendaEnabled -> holder.setNewReferendaEnabled(item)
|
||||
PushGovernanceRVItem::isReferendaUpdatesEnabled -> holder.setReferendaUpdatesEnabled(item)
|
||||
PushGovernanceRVItem::tracksText -> holder.setTracks(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PushGovernanceItemCallback() : DiffUtil.ItemCallback<PushGovernanceRVItem>() {
|
||||
override fun areItemsTheSame(oldItem: PushGovernanceRVItem, newItem: PushGovernanceRVItem): Boolean {
|
||||
return oldItem.chainId == newItem.chainId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: PushGovernanceRVItem, newItem: PushGovernanceRVItem): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: PushGovernanceRVItem, newItem: PushGovernanceRVItem): Any? {
|
||||
return PushGovernancePayloadGenerator.diff(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
|
||||
class PushGovernanceItemViewHolder(
|
||||
private val binder: ItemPushGovernanceSettingsBinding,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val itemHandler: PushGovernanceSettingsAdapter.ItemHandler
|
||||
) : ViewHolder(binder.root) {
|
||||
|
||||
init {
|
||||
binder.pushGovernanceItemState.setIconTintColor(null)
|
||||
}
|
||||
|
||||
fun bind(item: PushGovernanceRVItem) {
|
||||
with(itemView) {
|
||||
updateListenners(item)
|
||||
|
||||
binder.pushGovernanceItemState.setTitle(item.chainName)
|
||||
imageLoader.loadChainIconToTarget(item.chainIconUrl, context) {
|
||||
binder.pushGovernanceItemState.setIcon(it)
|
||||
}
|
||||
|
||||
setEnabled(item)
|
||||
setNewReferendaEnabled(item)
|
||||
setReferendaUpdatesEnabled(item)
|
||||
setTracks(item)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTracks(item: PushGovernanceRVItem) {
|
||||
binder.pushGovernanceItemTracks.setValue(item.tracksText)
|
||||
}
|
||||
|
||||
fun setEnabled(item: PushGovernanceRVItem) {
|
||||
with(binder) {
|
||||
pushGovernanceItemState.setChecked(item.isEnabled)
|
||||
pushGovernanceItemNewReferenda.isVisible = item.isEnabled
|
||||
pushGovernanceItemReferendumUpdate.isVisible = item.isEnabled
|
||||
// pushGovernanceItemDelegateVotes.isVisible = item.isEnabled // currently disabled
|
||||
pushGovernanceItemTracks.isVisible = item.isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun setNewReferendaEnabled(item: PushGovernanceRVItem) {
|
||||
binder.pushGovernanceItemNewReferenda.setChecked(item.isNewReferendaEnabled)
|
||||
}
|
||||
|
||||
fun setReferendaUpdatesEnabled(item: PushGovernanceRVItem) {
|
||||
binder.pushGovernanceItemReferendumUpdate.setChecked(item.isReferendaUpdatesEnabled)
|
||||
}
|
||||
|
||||
fun updateListenners(item: PushGovernanceRVItem) {
|
||||
binder.pushGovernanceItemState.setOnClickListener { itemHandler.enableSwitcherClick(item) }
|
||||
binder.pushGovernanceItemNewReferenda.setOnClickListener { itemHandler.newReferendaClick(item) }
|
||||
binder.pushGovernanceItemReferendumUpdate.setOnClickListener { itemHandler.referendaUpdatesClick(item) }
|
||||
binder.pushGovernanceItemTracks.setOnClickListener { itemHandler.tracksClicked(item) }
|
||||
}
|
||||
}
|
||||
|
||||
private object PushGovernancePayloadGenerator : PayloadGenerator<PushGovernanceRVItem>(
|
||||
PushGovernanceRVItem::isEnabled,
|
||||
PushGovernanceRVItem::isNewReferendaEnabled,
|
||||
PushGovernanceRVItem::isReferendaUpdatesEnabled,
|
||||
PushGovernanceRVItem::tracksText,
|
||||
)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.governance.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsFragment
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsRequester
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
PushGovernanceSettingsModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface PushGovernanceSettingsComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance request: PushGovernanceSettingsRequester.Request
|
||||
): PushGovernanceSettingsComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: PushGovernanceSettingsFragment)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.governance.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.GovernancePushSettingsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsRequester
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsViewModel
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class PushGovernanceSettingsModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(PushGovernanceSettingsViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: PushNotificationsRouter,
|
||||
interactor: GovernancePushSettingsInteractor,
|
||||
pushGovernanceSettingsCommunicator: PushGovernanceSettingsCommunicator,
|
||||
chainRegistry: ChainRegistry,
|
||||
request: PushGovernanceSettingsRequester.Request,
|
||||
selectTracksCommunicator: SelectTracksCommunicator,
|
||||
resourceManager: ResourceManager
|
||||
): ViewModel {
|
||||
return PushGovernanceSettingsViewModel(
|
||||
router = router,
|
||||
interactor = interactor,
|
||||
pushGovernanceSettingsResponder = pushGovernanceSettingsCommunicator,
|
||||
chainRegistry = chainRegistry,
|
||||
request = request,
|
||||
selectTracksRequester = selectTracksCommunicator,
|
||||
resourceManager = resourceManager
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory
|
||||
): PushGovernanceSettingsViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(PushGovernanceSettingsViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.fromJson
|
||||
|
||||
abstract class BaseNotificationHandler(
|
||||
private val activityIntentProvider: ActivityIntentProvider,
|
||||
private val notificationIdProvider: NotificationIdProvider,
|
||||
private val gson: Gson,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
val resourceManager: ResourceManager,
|
||||
private val channel: NovaNotificationChannel,
|
||||
private val importance: Int = NotificationManager.IMPORTANCE_DEFAULT,
|
||||
) : NotificationHandler {
|
||||
|
||||
final override suspend fun handleNotification(message: RemoteMessage): Boolean {
|
||||
val channelId = resourceManager.getString(channel.idRes)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
resourceManager.getString(channel.nameRes),
|
||||
importance
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
return runCatching { handleNotificationInternal(channelId, message) }
|
||||
.onFailure { it.printStackTrace() }
|
||||
.getOrNull() ?: false
|
||||
}
|
||||
|
||||
internal fun notify(notification: Notification) {
|
||||
notificationManager.notify(notificationIdProvider.getId(), notification)
|
||||
}
|
||||
|
||||
internal fun notify(id: Int, notification: Notification) {
|
||||
notificationManager.notify(id, notification)
|
||||
}
|
||||
|
||||
internal fun activityIntent() = activityIntentProvider.getIntent()
|
||||
|
||||
protected abstract suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean
|
||||
|
||||
internal fun RemoteMessage.getMessageContent(): NotificationData {
|
||||
val payload: Map<String, Any> = data["payload"]?.let { payload -> gson.fromJson(payload) } ?: emptyMap()
|
||||
|
||||
return NotificationData(
|
||||
type = data.getValue("type"),
|
||||
chainId = data["chainId"],
|
||||
payload = payload
|
||||
)
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling
|
||||
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
|
||||
class CompoundNotificationHandler(
|
||||
val handlers: Set<NotificationHandler>
|
||||
) : NotificationHandler {
|
||||
|
||||
override suspend fun handleNotification(message: RemoteMessage): Boolean {
|
||||
for (handler in handlers) {
|
||||
if (handler.handleNotification(message)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling
|
||||
|
||||
class NotificationData(
|
||||
val type: String,
|
||||
val chainId: String?,
|
||||
val payload: Map<String, Any>
|
||||
)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling
|
||||
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
|
||||
interface NotificationHandler {
|
||||
|
||||
/**
|
||||
* @return true if the notification was handled, false otherwise
|
||||
*/
|
||||
suspend fun handleNotification(message: RemoteMessage): Boolean
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.app.NotificationCompat
|
||||
import io.novafoundation.nova.common.utils.asGsonParsedNumber
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_currency_api.presentation.formatters.formatAsCurrency
|
||||
import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumStatusType
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.model.Token
|
||||
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatPlanks
|
||||
import io.novafoundation.nova.runtime.ext.chainIdHexPrefix16
|
||||
import io.novafoundation.nova.runtime.ext.onChainAssetId
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chainsById
|
||||
import java.math.BigInteger
|
||||
|
||||
private const val PEDDING_INTENT_REQUEST_CODE = 1
|
||||
|
||||
interface PushChainRegestryHolder {
|
||||
|
||||
val chainRegistry: ChainRegistry
|
||||
|
||||
suspend fun NotificationData.getChain(): Chain {
|
||||
val chainId = chainId ?: throw NullPointerException("Chain id is null")
|
||||
return chainRegistry.chainsById()
|
||||
.mapKeys { it.key.chainIdHexPrefix16() }
|
||||
.getValue(chainId)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun NotificationData.requireType(type: String) {
|
||||
require(this.type == type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: {a_field: {b_field: {c_field: "value"}}}
|
||||
* To take a value from c_field use getPayloadFieldContent("a_field", "b_field", "c_field")
|
||||
*/
|
||||
internal inline fun <reified T> NotificationData.extractPayloadFieldsWithPath(vararg fields: String): T {
|
||||
val fieldsBeforeLast = fields.dropLast(1)
|
||||
val last = fields.last()
|
||||
|
||||
val lastSearchingValue = fieldsBeforeLast.fold(payload) { acc, field ->
|
||||
acc[field] as? Map<String, Any> ?: throw NullPointerException("Notification parameter $field is null")
|
||||
}
|
||||
|
||||
val result = lastSearchingValue[last] ?: return null as T
|
||||
|
||||
return result as? T ?: throw NullPointerException("Notification parameter $last is null")
|
||||
}
|
||||
|
||||
internal fun NotificationData.extractBigInteger(vararg fields: String): BigInteger {
|
||||
return extractPayloadFieldsWithPath<Any>(*fields)
|
||||
.asGsonParsedNumber()
|
||||
}
|
||||
|
||||
internal fun MetaAccount.formattedAccountName(): String {
|
||||
return "[$name]"
|
||||
}
|
||||
|
||||
fun Context.makePendingIntent(intent: Intent): PendingIntent {
|
||||
return PendingIntent.getActivity(
|
||||
this,
|
||||
PEDDING_INTENT_REQUEST_CODE,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
fun NotificationCompat.Builder.buildWithDefaults(
|
||||
context: Context,
|
||||
title: CharSequence,
|
||||
message: CharSequence,
|
||||
contentIntent: Intent
|
||||
): NotificationCompat.Builder {
|
||||
return setContentTitle(title)
|
||||
.setContentText(message)
|
||||
.setSmallIcon(R.drawable.ic_pezkuwi)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true)
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.bigText(message)
|
||||
)
|
||||
.setContentIntent(context.makePendingIntent(contentIntent))
|
||||
}
|
||||
|
||||
fun makeNewReleasesIntent(
|
||||
storeLink: String
|
||||
): Intent {
|
||||
return Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(storeLink) }
|
||||
}
|
||||
|
||||
fun ReferendumStatusType.Companion.fromRemoteNotificationType(type: String): ReferendumStatusType {
|
||||
return when (type) {
|
||||
"Created" -> ReferendumStatusType.PREPARING
|
||||
"Deciding" -> ReferendumStatusType.DECIDING
|
||||
"Confirming" -> ReferendumStatusType.CONFIRMING
|
||||
"Approved" -> ReferendumStatusType.APPROVED
|
||||
"Rejected" -> ReferendumStatusType.REJECTED
|
||||
"TimedOut" -> ReferendumStatusType.TIMED_OUT
|
||||
"Cancelled" -> ReferendumStatusType.CANCELLED
|
||||
"Killed" -> ReferendumStatusType.KILLED
|
||||
else -> throw IllegalArgumentException("Unknown referendum status type: $this")
|
||||
}
|
||||
}
|
||||
|
||||
fun Chain.assetByOnChainAssetIdOrUtility(assetId: String?): Chain.Asset? {
|
||||
if (assetId == null) return utilityAsset
|
||||
|
||||
return assets.firstOrNull { it.onChainAssetId == assetId }
|
||||
}
|
||||
|
||||
fun notificationAmountFormat(asset: Chain.Asset, token: Token?, amount: BigInteger): String {
|
||||
val tokenAmount = amount.formatPlanks(asset)
|
||||
val fiatAmount = token?.planksToFiat(amount)
|
||||
?.formatAsCurrency(token.currency)
|
||||
|
||||
return if (fiatAmount != null) {
|
||||
"$tokenAmount ($fiatAmount)"
|
||||
} else {
|
||||
tokenAmount
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun AccountRepository.isNotSingleMetaAccount(): Boolean {
|
||||
return getActiveMetaAccountsQuantity() > 1
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling
|
||||
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
|
||||
interface NotificationIdProvider {
|
||||
fun getId(): Int
|
||||
}
|
||||
|
||||
class RealNotificationIdProvider(
|
||||
private val preferences: Preferences
|
||||
) : NotificationIdProvider {
|
||||
|
||||
override fun getId(): Int {
|
||||
val id = preferences.getInt(KEY, START_ID)
|
||||
preferences.putInt(KEY, id + 1)
|
||||
return id
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY = "notification_id"
|
||||
private const val START_ID = 0
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
|
||||
enum class NovaNotificationChannel(
|
||||
@StringRes val idRes: Int,
|
||||
@StringRes val nameRes: Int
|
||||
) {
|
||||
|
||||
DEFAULT(R.string.default_notification_channel_id, R.string.default_notification_channel_name),
|
||||
|
||||
GOVERNANCE(R.string.governance_notification_channel_id, R.string.governance_notification_channel_name),
|
||||
|
||||
TRANSACTIONS(R.string.transactions_notification_channel_id, R.string.transactions_notification_channel_name),
|
||||
|
||||
STAKING(R.string.staking_notification_channel_id, R.string.staking_notification_channel_name),
|
||||
|
||||
MULTISIG(R.string.multisigs_notification_channel_id, R.string.multisigs_notification_channel_name),
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_push_notifications.BuildConfig
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
|
||||
private const val DEBUG_NOTIFICATION_ID = -1
|
||||
|
||||
/**
|
||||
* A [NotificationHandler] that is used as a fallback if previous handlers didn't handle the notification
|
||||
*/
|
||||
class DebugNotificationHandler(
|
||||
private val context: Context,
|
||||
private val activityIntentProvider: ActivityIntentProvider,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val resourceManager: ResourceManager
|
||||
) : NotificationHandler {
|
||||
|
||||
override suspend fun handleNotification(message: RemoteMessage): Boolean {
|
||||
if (!BuildConfig.DEBUG) return false
|
||||
|
||||
val channelId = resourceManager.getString(R.string.default_notification_channel_id)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
resourceManager.getString(R.string.default_notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
"Notification handling error!",
|
||||
"The notification was not handled\n${message.data}",
|
||||
activityIntentProvider.getIntent()
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun notify(notification: Notification) {
|
||||
notificationManager.notify(DEBUG_NOTIFICATION_ID, notification)
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.formatting.format
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.deeplink.configurators.ReferendumDeepLinkData
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.deeplink.configurators.ReferendumDetailsDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.BaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NovaNotificationChannel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractBigInteger
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
class NewReferendumNotificationHandler(
|
||||
private val context: Context,
|
||||
private val configurator: ReferendumDetailsDeepLinkConfigurator,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : BaseNotificationHandler(
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager,
|
||||
channel = NovaNotificationChannel.GOVERNANCE
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.GOV_NEW_REF)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val referendumId = content.extractBigInteger("referendumId")
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
resourceManager.getString(R.string.push_new_referendum_title),
|
||||
resourceManager.getString(R.string.push_new_referendum_message, chain.name, referendumId.format()),
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
ReferendumDeepLinkData(chain.id, referendumId, Chain.Governance.V2)
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.BaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NovaNotificationChannel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractPayloadFieldsWithPath
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.makeNewReleasesIntent
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
|
||||
class NewReleaseNotificationHandler(
|
||||
private val context: Context,
|
||||
private val appLinksProvider: AppLinksProvider,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : BaseNotificationHandler(
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager,
|
||||
channel = NovaNotificationChannel.DEFAULT
|
||||
) {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.APP_NEW_RELEASE)
|
||||
val version = content.extractPayloadFieldsWithPath<String>("version")
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
resourceManager.getString(R.string.push_new_update_title),
|
||||
resourceManager.getString(R.string.push_new_update_message, version),
|
||||
makeNewReleasesIntent(appLinksProvider.storeUrl)
|
||||
)
|
||||
.build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.formatting.format
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_governance_api.domain.referendum.list.ReferendumStatusType
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.common.ReferendaStatusFormatter
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.deeplink.configurators.ReferendumDeepLinkData
|
||||
import io.novafoundation.nova.feature_governance_api.presentation.referenda.details.deeplink.configurators.ReferendumDetailsDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.BaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NovaNotificationChannel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractBigInteger
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractPayloadFieldsWithPath
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.fromRemoteNotificationType
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigInteger
|
||||
|
||||
class ReferendumStateUpdateNotificationHandler(
|
||||
private val context: Context,
|
||||
private val configurator: ReferendumDetailsDeepLinkConfigurator,
|
||||
private val referendaStatusFormatter: ReferendaStatusFormatter,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : BaseNotificationHandler(
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager,
|
||||
channel = NovaNotificationChannel.GOVERNANCE
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.GOV_STATE)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val referendumId = content.extractBigInteger("referendumId")
|
||||
val stateFrom = content.extractPayloadFieldsWithPath<String?>("from")?.let { ReferendumStatusType.fromRemoteNotificationType(it) }
|
||||
val stateTo = content.extractPayloadFieldsWithPath<String>("to").let { ReferendumStatusType.fromRemoteNotificationType(it) }
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
getTitle(stateTo),
|
||||
getMessage(chain, referendumId, stateFrom, stateTo),
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
ReferendumDeepLinkData(chain.id, referendumId, Chain.Governance.V2)
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getTitle(refStateTo: ReferendumStatusType): String {
|
||||
return when (refStateTo) {
|
||||
ReferendumStatusType.APPROVED -> resourceManager.getString(R.string.push_referendum_approved_title)
|
||||
ReferendumStatusType.REJECTED -> resourceManager.getString(R.string.push_referendum_rejected_title)
|
||||
else -> resourceManager.getString(R.string.push_referendum_status_changed_title)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMessage(chain: Chain, referendumId: BigInteger, stateFrom: ReferendumStatusType?, stateTo: ReferendumStatusType): String {
|
||||
return when {
|
||||
stateTo == ReferendumStatusType.APPROVED -> resourceManager.getString(R.string.push_referendum_approved_message, chain.name, referendumId.format())
|
||||
stateTo == ReferendumStatusType.REJECTED -> resourceManager.getString(R.string.push_referendum_rejected_message, chain.name, referendumId.format())
|
||||
stateFrom == null -> resourceManager.getString(
|
||||
R.string.push_referendum_to_status_changed_message,
|
||||
chain.name,
|
||||
referendumId.format(),
|
||||
referendaStatusFormatter.formatStatus(stateTo)
|
||||
)
|
||||
|
||||
else -> resourceManager.getString(
|
||||
R.string.push_referendum_from_to_status_changed_message,
|
||||
chain.name,
|
||||
referendumId.format(),
|
||||
referendaStatusFormatter.formatStatus(stateFrom),
|
||||
referendaStatusFormatter.formatStatus(stateTo)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkData
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.BaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NovaNotificationChannel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractBigInteger
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractPayloadFieldsWithPath
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.notificationAmountFormat
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.formattedAccountName
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.isNotSingleMetaAccount
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository
|
||||
import io.novafoundation.nova.runtime.ext.accountIdOf
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.ext.utilityAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigInteger
|
||||
|
||||
class StakingRewardNotificationHandler(
|
||||
private val context: Context,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val tokenRepository: TokenRepository,
|
||||
private val configurator: io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkConfigurator,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : BaseNotificationHandler(
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager,
|
||||
channel = NovaNotificationChannel.STAKING
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.STAKING_REWARD)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val recipient = content.extractPayloadFieldsWithPath<String>("recipient")
|
||||
val amount = content.extractBigInteger("amount")
|
||||
|
||||
val metaAccount = accountRepository.findMetaAccount(chain.accountIdOf(recipient), chain.id) ?: return false
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
getTitle(metaAccount),
|
||||
getMessage(chain, amount),
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
AssetDetailsDeepLinkData(recipient, chain.id, chain.utilityAsset.id)
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun getTitle(metaAccount: MetaAccount): String {
|
||||
return when {
|
||||
accountRepository.isNotSingleMetaAccount() -> resourceManager.getString(
|
||||
R.string.push_staking_reward_many_accounts_title,
|
||||
metaAccount.formattedAccountName()
|
||||
)
|
||||
|
||||
else -> resourceManager.getString(R.string.push_staking_reward_single_account_title)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMessage(
|
||||
chain: Chain,
|
||||
amount: BigInteger
|
||||
): String {
|
||||
val asset = chain.utilityAsset
|
||||
val token = tokenRepository.getTokenOrNull(asset)
|
||||
val formattedAmount = notificationAmountFormat(asset, token, amount)
|
||||
|
||||
return resourceManager.getString(R.string.push_staking_reward_message, formattedAmount, chain.name)
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.BaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NovaNotificationChannel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
|
||||
class SystemNotificationHandler(
|
||||
private val context: Context,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : BaseNotificationHandler(
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager,
|
||||
channel = NovaNotificationChannel.DEFAULT
|
||||
) {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val notificationPart = message.notification ?: return false
|
||||
|
||||
val title = notificationPart.title ?: return false
|
||||
val body = notificationPart.body ?: return false
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.buildWithDefaults(context, title, body, activityIntent())
|
||||
.build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkData
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.BaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NovaNotificationChannel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.assetByOnChainAssetIdOrUtility
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractBigInteger
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractPayloadFieldsWithPath
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.formattedAccountName
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.isNotSingleMetaAccount
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.notificationAmountFormat
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository
|
||||
import io.novafoundation.nova.runtime.ext.accountIdOf
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigInteger
|
||||
|
||||
class TokenReceivedNotificationHandler(
|
||||
private val context: Context,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val tokenRepository: TokenRepository,
|
||||
private val configurator: io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkConfigurator,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : BaseNotificationHandler(
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager,
|
||||
channel = NovaNotificationChannel.TRANSACTIONS
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.TOKENS_RECEIVED)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val recipient = content.extractPayloadFieldsWithPath<String>("recipient")
|
||||
val assetId = content.extractPayloadFieldsWithPath<String?>("assetId")
|
||||
val amount = content.extractBigInteger("amount")
|
||||
|
||||
val asset = chain.assetByOnChainAssetIdOrUtility(assetId) ?: return false
|
||||
val recipientMetaAccount = accountRepository.findMetaAccount(chain.accountIdOf(recipient), chain.id) ?: return false
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
getTitle(recipientMetaAccount),
|
||||
getMessage(chain, asset, amount),
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
AssetDetailsDeepLinkData(recipient, chain.id, asset.id)
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun getTitle(senderMetaAccount: MetaAccount?): String {
|
||||
val accountName = senderMetaAccount?.formattedAccountName()
|
||||
return when {
|
||||
accountRepository.isNotSingleMetaAccount() && accountName != null -> resourceManager.getString(R.string.push_token_received_title, accountName)
|
||||
else -> resourceManager.getString(R.string.push_token_received_no_account_name_title)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMessage(
|
||||
chain: Chain,
|
||||
asset: Chain.Asset,
|
||||
amount: BigInteger
|
||||
): String {
|
||||
val token = tokenRepository.getTokenOrNull(asset)
|
||||
val formattedAmount = notificationAmountFormat(asset, token, amount)
|
||||
|
||||
return resourceManager.getString(R.string.push_token_received_message, formattedAmount, chain.name)
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_assets.presentation.balance.detail.deeplink.AssetDetailsDeepLinkData
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.BaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NovaNotificationChannel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.assetByOnChainAssetIdOrUtility
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractBigInteger
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractPayloadFieldsWithPath
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.formattedAccountName
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.isNotSingleMetaAccount
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.notificationAmountFormat
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository
|
||||
import io.novafoundation.nova.runtime.ext.accountIdOf
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigInteger
|
||||
|
||||
class TokenSentNotificationHandler(
|
||||
private val context: Context,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val tokenRepository: TokenRepository,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
private val configurator: AssetDetailsDeepLinkConfigurator,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : BaseNotificationHandler(
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager,
|
||||
channel = NovaNotificationChannel.TRANSACTIONS
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.TOKENS_SENT)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val sender = content.extractPayloadFieldsWithPath<String>("sender")
|
||||
val recipient = content.extractPayloadFieldsWithPath<String>("recipient")
|
||||
val assetId = content.extractPayloadFieldsWithPath<String?>("assetId")
|
||||
val amount = content.extractBigInteger("amount")
|
||||
|
||||
val asset = chain.assetByOnChainAssetIdOrUtility(assetId) ?: return false
|
||||
val senderMetaAccount = accountRepository.findMetaAccount(chain.accountIdOf(sender), chain.id) ?: return false
|
||||
val recipientMetaAccount = accountRepository.findMetaAccount(chain.accountIdOf(recipient), chain.id)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
getTitle(senderMetaAccount),
|
||||
getMessage(chain, recipientMetaAccount, recipient, asset, amount),
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
AssetDetailsDeepLinkData(sender, chain.id, asset.id)
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun getTitle(senderMetaAccount: MetaAccount?): String {
|
||||
val accountName = senderMetaAccount?.formattedAccountName()
|
||||
return when {
|
||||
accountRepository.isNotSingleMetaAccount() && accountName != null -> resourceManager.getString(R.string.push_token_sent_title, accountName)
|
||||
else -> resourceManager.getString(R.string.push_token_sent_no_account_name_title)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getMessage(
|
||||
chain: Chain,
|
||||
recipientMetaAccount: MetaAccount?,
|
||||
recipientAddress: String,
|
||||
asset: Chain.Asset,
|
||||
amount: BigInteger
|
||||
): String {
|
||||
val token = tokenRepository.getTokenOrNull(asset)
|
||||
val formattedAmount = notificationAmountFormat(asset, token, amount)
|
||||
|
||||
val accountNameOrAddress = recipientMetaAccount?.formattedAccountName() ?: recipientAddress
|
||||
|
||||
return resourceManager.getString(R.string.push_token_sent_message, formattedAmount, accountNameOrAddress, chain.name)
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig
|
||||
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.address.AddressModel
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.BaseNotificationHandler
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NovaNotificationChannel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
|
||||
abstract class MultisigBaseNotificationHandler(
|
||||
private val multisigCallFormatter: MultisigCallFormatter,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager
|
||||
) : BaseNotificationHandler(
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager,
|
||||
channel = NovaNotificationChannel.MULTISIG
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
fun getSubText(metaAccount: MetaAccount): String {
|
||||
return resourceManager.getString(R.string.multisig_notification_message_header, metaAccount.name)
|
||||
}
|
||||
|
||||
suspend fun getMessage(
|
||||
chain: Chain,
|
||||
payload: MultisigNotificationPayload,
|
||||
footer: String?
|
||||
): String {
|
||||
val runtime = chainRegistry.getRuntime(chain.id)
|
||||
val call = payload.callData?.let { GenericCall.fromHex(runtime, payload.callData) }
|
||||
|
||||
return buildString {
|
||||
val formattedCall = multisigCallFormatter.formatPushNotificationMessage(call, payload.signatory.accountId, chain)
|
||||
append(formattedCall.formattedCall)
|
||||
formattedCall.onBehalfOf?.let { appendLine().append(formatOnBehalfOf(it)) }
|
||||
footer?.let { appendLine().append(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatOnBehalfOf(addressMode: AddressModel): String {
|
||||
return resourceManager.getString(R.string.multisig_notification_on_behalf_of, addressMode.nameOrAddress)
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperation
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.createOperationHash
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.getNameOrAddress
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkData
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
class MultisigTransactionCancelledNotificationHandler(
|
||||
private val context: Context,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val multisigCallFormatter: MultisigCallFormatter,
|
||||
private val configurator: MultisigOperationDeepLinkConfigurator,
|
||||
@LocalIdentity private val identityProvider: IdentityProvider,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : MultisigBaseNotificationHandler(
|
||||
multisigCallFormatter,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.MULTISIG_CANCELLED)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val payload = content.extractMultisigPayload(signatoryRole = "canceller", chain)
|
||||
|
||||
val multisigAccount = accountRepository.getMultisigForPayload(chain, payload) ?: return true
|
||||
|
||||
val rejecterIdentity = identityProvider.getNameOrAddress(payload.signatory.accountId, chain)
|
||||
val messageText = getMessage(
|
||||
chain,
|
||||
payload,
|
||||
footer = resourceManager.getString(R.string.multisig_notification_rejected_transaction_message, rejecterIdentity)
|
||||
)
|
||||
|
||||
val operationHash = PendingMultisigOperation.createOperationHash(multisigAccount, chain, payload.callHashString)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.setSubText(getSubText(multisigAccount))
|
||||
.setGroup(operationHash)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
resourceManager.getString(R.string.multisig_notification_rejected_transaction_title),
|
||||
messageText,
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
multisigOperationDeepLinkData(multisigAccount, chain, payload, MultisigOperationDeepLinkData.State.Rejected(rejecterIdentity))
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
notifyMultisigGroupNotificationWithId(
|
||||
context = context,
|
||||
groupId = operationHash,
|
||||
channelId = channelId,
|
||||
metaAccount = multisigAccount
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperation
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.createOperationHash
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.getNameOrAddress
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkConfigurator
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkData
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
class MultisigTransactionExecutedNotificationHandler(
|
||||
private val context: Context,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val multisigCallFormatter: MultisigCallFormatter,
|
||||
private val configurator: MultisigOperationDeepLinkConfigurator,
|
||||
@LocalIdentity private val identityProvider: IdentityProvider,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : MultisigBaseNotificationHandler(
|
||||
multisigCallFormatter,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.MULTISIG_EXECUTED)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val payload = content.extractMultisigPayload(signatoryRole = "approver", chain)
|
||||
|
||||
val multisigAccount = accountRepository.getMultisigForPayload(chain, payload) ?: return true
|
||||
|
||||
val approverIdentity = identityProvider.getNameOrAddress(payload.signatory.accountId, chain)
|
||||
val messageText = getMessage(
|
||||
chain,
|
||||
payload,
|
||||
footer = resourceManager.getString(R.string.multisig_notification_executed_transaction_message, approverIdentity)
|
||||
)
|
||||
|
||||
val operationHash = PendingMultisigOperation.createOperationHash(multisigAccount, chain, payload.callHashString)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.setSubText(getSubText(multisigAccount))
|
||||
.setGroup(operationHash)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
resourceManager.getString(R.string.multisig_notification_executed_transaction_title),
|
||||
messageText,
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
multisigOperationDeepLinkData(multisigAccount, chain, payload, MultisigOperationDeepLinkData.State.Executed(approverIdentity))
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
notifyMultisigGroupNotificationWithId(
|
||||
context = context,
|
||||
groupId = operationHash,
|
||||
channelId = channelId,
|
||||
metaAccount = multisigAccount
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig
|
||||
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkConfigurator
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperation
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.createOperationHash
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.getNameOrAddress
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkData
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
class MultisigTransactionInitiatedNotificationHandler(
|
||||
private val context: Context,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val multisigCallFormatter: MultisigCallFormatter,
|
||||
private val configurator: MultisigOperationDeepLinkConfigurator,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
@LocalIdentity private val identityProvider: IdentityProvider,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : MultisigBaseNotificationHandler(
|
||||
multisigCallFormatter,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.NEW_MULTISIG)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val payload = content.extractMultisigPayload(signatoryRole = "initiator", chain)
|
||||
|
||||
val multisigAccount = accountRepository.getMultisigForPayload(chain, payload) ?: return true
|
||||
|
||||
val initiatorIdentity = identityProvider.getNameOrAddress(payload.signatory.accountId, chain)
|
||||
|
||||
val operationHash = PendingMultisigOperation.createOperationHash(multisigAccount, chain, payload.callHashString)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.setSubText(getSubText(multisigAccount))
|
||||
.setGroup(operationHash)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
resourceManager.getString(R.string.multisig_notification_init_transaction_title),
|
||||
getMessage(chain, payload, footer = signFooter(initiatorIdentity)),
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
multisigOperationDeepLinkData(multisigAccount, chain, payload, MultisigOperationDeepLinkData.State.Active)
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
notifyMultisigGroupNotificationWithId(
|
||||
context = context,
|
||||
groupId = operationHash,
|
||||
channelId = channelId,
|
||||
metaAccount = multisigAccount
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun signFooter(initiatorIdentity: String) = resourceManager.getString(R.string.multisig_notification_initiator_footer, initiatorIdentity)
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig
|
||||
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkConfigurator
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.google.gson.Gson
|
||||
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigDetailsRepository
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.PendingMultisigOperation
|
||||
import io.novafoundation.nova.feature_account_api.data.multisig.model.createOperationHash
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity
|
||||
import io.novafoundation.nova.feature_account_api.domain.account.identity.getNameOrAddress
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_deep_linking.presentation.configuring.applyDeepLink
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.callFormatting.MultisigCallFormatter
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkData
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.data.NotificationTypes
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationIdProvider
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.PushChainRegestryHolder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.buildWithDefaults
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractPayloadFieldsWithPath
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.requireType
|
||||
import io.novafoundation.nova.runtime.ext.isEnabled
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
class MultisigTransactionNewApprovalNotificationHandler(
|
||||
private val context: Context,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val multisigDetailsRepository: MultisigDetailsRepository,
|
||||
private val multisigCallFormatter: MultisigCallFormatter,
|
||||
private val configurator: MultisigOperationDeepLinkConfigurator,
|
||||
@LocalIdentity private val identityProvider: IdentityProvider,
|
||||
override val chainRegistry: ChainRegistry,
|
||||
activityIntentProvider: ActivityIntentProvider,
|
||||
notificationIdProvider: NotificationIdProvider,
|
||||
gson: Gson,
|
||||
notificationManager: NotificationManagerCompat,
|
||||
resourceManager: ResourceManager,
|
||||
) : MultisigBaseNotificationHandler(
|
||||
multisigCallFormatter,
|
||||
chainRegistry,
|
||||
activityIntentProvider,
|
||||
notificationIdProvider,
|
||||
gson,
|
||||
notificationManager,
|
||||
resourceManager
|
||||
),
|
||||
PushChainRegestryHolder {
|
||||
|
||||
override suspend fun handleNotificationInternal(channelId: String, message: RemoteMessage): Boolean {
|
||||
val content = message.getMessageContent()
|
||||
content.requireType(NotificationTypes.MULTISIG_APPROVAL)
|
||||
|
||||
val chain = content.getChain()
|
||||
require(chain.isEnabled)
|
||||
|
||||
val payload = content.extractMultisigPayload(signatoryRole = "approver", chain)
|
||||
val approvals = content.extractPayloadFieldsWithPath<Double?>("approvals")?.toInt() ?: return true
|
||||
|
||||
val multisigAccount = accountRepository.getMultisigForPayload(chain, payload) ?: return true
|
||||
|
||||
val approverIdentity = identityProvider.getNameOrAddress(payload.signatory.accountId, chain)
|
||||
|
||||
val operationHash = PendingMultisigOperation.createOperationHash(multisigAccount, chain, payload.callHashString)
|
||||
|
||||
val messageText = getMessage(
|
||||
chain,
|
||||
payload,
|
||||
footer = resourceManager.getString(
|
||||
R.string.multisig_notification_new_approval_title_additional_message,
|
||||
approvals,
|
||||
multisigAccount.threshold
|
||||
)
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.setSubText(getSubText(multisigAccount))
|
||||
.setGroup(operationHash)
|
||||
.buildWithDefaults(
|
||||
context,
|
||||
resourceManager.getString(R.string.multisig_notification_new_approval_title, approverIdentity),
|
||||
messageText,
|
||||
activityIntent().applyDeepLink(
|
||||
configurator,
|
||||
multisigOperationDeepLinkData(multisigAccount, chain, payload, MultisigOperationDeepLinkData.State.Active)
|
||||
)
|
||||
).build()
|
||||
|
||||
notify(notification)
|
||||
|
||||
notifyMultisigGroupNotificationWithId(
|
||||
context = context,
|
||||
groupId = operationHash,
|
||||
channelId = channelId,
|
||||
metaAccount = multisigAccount
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.handling.types.multisig
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.address.intoKey
|
||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.MultisigMetaAccount
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.accountIdKeyIn
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.requireAddressIn
|
||||
import io.novafoundation.nova.feature_multisig_operations.presentation.details.deeplink.MultisigOperationDeepLinkData
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.NotificationData
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.handling.extractPayloadFieldsWithPath
|
||||
import io.novafoundation.nova.runtime.ext.addressOf
|
||||
import io.novafoundation.nova.runtime.ext.toAccountIdKey
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.extensions.fromHex
|
||||
|
||||
class AddressWithAccountId(
|
||||
val address: String,
|
||||
val accountId: AccountIdKey
|
||||
)
|
||||
|
||||
class MultisigNotificationPayload(
|
||||
val multisig: AddressWithAccountId,
|
||||
val signatory: AddressWithAccountId,
|
||||
val callHashString: String,
|
||||
val callHash: AccountIdKey,
|
||||
val callData: String?
|
||||
)
|
||||
|
||||
fun String.toAddressWithAccountId(chain: Chain) = AddressWithAccountId(this, this.toAccountIdKey(chain))
|
||||
|
||||
fun NotificationData.extractMultisigPayload(signatoryRole: String, chain: Chain): MultisigNotificationPayload {
|
||||
val callHashString = extractPayloadFieldsWithPath<String>("callHash")
|
||||
return MultisigNotificationPayload(
|
||||
extractPayloadFieldsWithPath<String>("multisig").toAddressWithAccountId(chain),
|
||||
extractPayloadFieldsWithPath<String>(signatoryRole).toAddressWithAccountId(chain),
|
||||
callHashString,
|
||||
callHashString.fromHex().intoKey(),
|
||||
extractPayloadFieldsWithPath<String?>("callData")
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun AccountRepository.getMultisigForPayload(chain: Chain, payload: MultisigNotificationPayload): MultisigMetaAccount? {
|
||||
return getActiveMetaAccounts()
|
||||
.filterIsInstance<MultisigMetaAccount>()
|
||||
.filter { it.accountIdKeyIn(chain) == payload.multisig.accountId }
|
||||
.getFirstActorExcept(payload.signatory)
|
||||
}
|
||||
|
||||
fun List<MultisigMetaAccount>.getFirstActorExcept(signatory: AddressWithAccountId): MultisigMetaAccount? {
|
||||
return firstOrNull { it.signatoryAccountId != signatory.accountId }
|
||||
}
|
||||
|
||||
fun multisigOperationDeepLinkData(
|
||||
metaAccount: MultisigMetaAccount,
|
||||
chain: Chain,
|
||||
payload: MultisigNotificationPayload,
|
||||
operationState: MultisigOperationDeepLinkData.State?
|
||||
): MultisigOperationDeepLinkData {
|
||||
return MultisigOperationDeepLinkData(
|
||||
chain.id,
|
||||
metaAccount.requireAddressIn(chain),
|
||||
chain.addressOf(metaAccount.signatoryAccountId),
|
||||
payload.callHashString,
|
||||
payload.callData,
|
||||
operationState
|
||||
)
|
||||
}
|
||||
|
||||
fun MultisigBaseNotificationHandler.notifyMultisigGroupNotificationWithId(context: Context, groupId: String, channelId: String, metaAccount: MetaAccount) {
|
||||
val notificationId = createGroupMessageId(groupId)
|
||||
val notification = createMultisigGroupNotification(context, groupId, channelId, getSubText(metaAccount))
|
||||
notify(notificationId, notification)
|
||||
}
|
||||
|
||||
fun createGroupMessageId(groupId: String): Int {
|
||||
return groupId.hashCode()
|
||||
}
|
||||
|
||||
fun createMultisigGroupNotification(context: Context, groupId: String, channelId: String, walletName: String): Notification {
|
||||
return NotificationCompat.Builder(context, channelId)
|
||||
.setSmallIcon(R.drawable.ic_pezkuwi)
|
||||
.setStyle(
|
||||
NotificationCompat.InboxStyle()
|
||||
.setSummaryText(walletName)
|
||||
)
|
||||
.setGroup(groupId)
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||
.setGroupSummary(true)
|
||||
.build()
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigs
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.common.navigation.InterScreenRequester
|
||||
import io.novafoundation.nova.common.navigation.InterScreenResponder
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
interface PushMultisigSettingsRequester : InterScreenRequester<PushMultisigSettingsRequester.Request, PushMultisigSettingsResponder.Response> {
|
||||
|
||||
@Parcelize
|
||||
class Request(val isAtLeastOneMultisigWalletSelected: Boolean, val settings: PushMultisigSettingsModel) : Parcelable
|
||||
}
|
||||
|
||||
interface PushMultisigSettingsResponder : InterScreenResponder<PushMultisigSettingsRequester.Request, PushMultisigSettingsResponder.Response> {
|
||||
|
||||
@Parcelize
|
||||
class Response(val settings: PushMultisigSettingsModel) : Parcelable
|
||||
}
|
||||
|
||||
interface PushMultisigSettingsCommunicator : PushMultisigSettingsRequester, PushMultisigSettingsResponder
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigs
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents
|
||||
import io.novafoundation.nova.common.utils.FragmentPayloadCreator
|
||||
import io.novafoundation.nova.common.utils.PayloadCreator
|
||||
import io.novafoundation.nova.common.utils.payload
|
||||
import io.novafoundation.nova.common.view.dialog.infoDialog
|
||||
import io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.databinding.FragmentPushMultisigSettingsBinding
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureComponent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class PushMultisigSettingsFragment : BaseFragment<PushMultisigSettingsViewModel, FragmentPushMultisigSettingsBinding>() {
|
||||
|
||||
companion object : PayloadCreator<PushMultisigSettingsRequester.Request> by FragmentPayloadCreator()
|
||||
|
||||
override fun createBinding() = FragmentPushMultisigSettingsBinding.inflate(layoutInflater)
|
||||
|
||||
override fun initViews() {
|
||||
binder.pushMultisigsToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
onBackPressed { viewModel.backClicked() }
|
||||
|
||||
binder.pushMultisigSettingsSwitcher.setOnClickListener { viewModel.switchMultisigNotificationsState() }
|
||||
binder.pushMultisigInitiatingSwitcher.setOnClickListener { viewModel.switchInitialNotificationsState() }
|
||||
binder.pushMultisigApprovalSwitcher.setOnClickListener { viewModel.switchApprovingNotificationsState() }
|
||||
binder.pushMultisigExecutedSwitcher.setOnClickListener { viewModel.switchExecutionNotificationsState() }
|
||||
binder.pushMultisigRejectedSwitcher.setOnClickListener { viewModel.switchRejectionNotificationsState() }
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<PushNotificationsFeatureComponent>(requireContext(), PushNotificationsFeatureApi::class.java)
|
||||
.pushMultisigSettings()
|
||||
.create(this, payload())
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: PushMultisigSettingsViewModel) {
|
||||
observeBrowserEvents(viewModel)
|
||||
|
||||
viewModel.isMultisigNotificationsEnabled.observe {
|
||||
binder.pushMultisigSettingsSwitcher.setChecked(it)
|
||||
|
||||
binder.pushMultisigInitiatingSwitcher.isEnabled = it
|
||||
binder.pushMultisigApprovalSwitcher.isEnabled = it
|
||||
binder.pushMultisigExecutedSwitcher.isEnabled = it
|
||||
binder.pushMultisigRejectedSwitcher.isEnabled = it
|
||||
}
|
||||
|
||||
binder.pushMultisigInitiatingSwitcher.bindWithFlow(viewModel.isInitiationEnabled)
|
||||
binder.pushMultisigApprovalSwitcher.bindWithFlow(viewModel.isApprovingEnabled)
|
||||
binder.pushMultisigExecutedSwitcher.bindWithFlow(viewModel.isExecutionEnabled)
|
||||
binder.pushMultisigRejectedSwitcher.bindWithFlow(viewModel.isRejectionEnabled)
|
||||
|
||||
viewModel.noOneMultisigWalletSelectedEvent.observeEvent {
|
||||
infoDialog(requireContext()) {
|
||||
setTitle(R.string.no_ms_accounts_found_dialog_title)
|
||||
setMessage(R.string.no_ms_accounts_found_dialog_message)
|
||||
setNegativeButton(R.string.common_learn_more) { _, _ -> viewModel.learnMoreClicked() }
|
||||
setPositiveButton(R.string.common_got_it, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SettingsSwitcherView.bindWithFlow(flow: Flow<Boolean>) {
|
||||
flow.observe { setChecked(it) }
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigs
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class PushMultisigSettingsModel(
|
||||
val isEnabled: Boolean,
|
||||
val isInitiatingEnabled: Boolean,
|
||||
val isApprovingEnabled: Boolean,
|
||||
val isExecutionEnabled: Boolean,
|
||||
val isRejectionEnabled: Boolean
|
||||
) : Parcelable
|
||||
|
||||
fun PushMultisigSettingsModel.toDomain() = PushSettings.MultisigsState(
|
||||
isEnabled = isEnabled,
|
||||
isInitiatingEnabled = isInitiatingEnabled,
|
||||
isApprovingEnabled = isApprovingEnabled,
|
||||
isExecutionEnabled = isExecutionEnabled,
|
||||
isRejectionEnabled = isRejectionEnabled
|
||||
)
|
||||
|
||||
fun PushSettings.MultisigsState.toModel() = PushMultisigSettingsModel(
|
||||
isEnabled = isEnabled,
|
||||
isInitiatingEnabled = isInitiatingEnabled,
|
||||
isApprovingEnabled = isApprovingEnabled,
|
||||
isExecutionEnabled = isExecutionEnabled,
|
||||
isRejectionEnabled = isRejectionEnabled
|
||||
)
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigs
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.sendEvent
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.disableIfAllTypesDisabled
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.isAllTypesDisabled
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class PushMultisigSettingsViewModel(
|
||||
private val router: PushNotificationsRouter,
|
||||
private val pushMultisigSettingsResponder: PushMultisigSettingsResponder,
|
||||
private val request: PushMultisigSettingsRequester.Request,
|
||||
private val appLinksProvider: AppLinksProvider
|
||||
) : BaseViewModel(), Browserable {
|
||||
|
||||
private val settingsState = MutableStateFlow(request.settings.toDomain())
|
||||
|
||||
val isMultisigNotificationsEnabled = settingsState.map { it.isEnabled }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val isInitiationEnabled = settingsState.map { it.isInitiatingEnabled }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val isApprovingEnabled = settingsState.map { it.isApprovingEnabled }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val isExecutionEnabled = settingsState.map { it.isExecutionEnabled }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val isRejectionEnabled = settingsState.map { it.isRejectionEnabled }
|
||||
.distinctUntilChanged()
|
||||
|
||||
private val _noOneMultisigWalletSelectedEvent = MutableLiveData<Event<Unit>>()
|
||||
val noOneMultisigWalletSelectedEvent: LiveData<Event<Unit>> = _noOneMultisigWalletSelectedEvent
|
||||
|
||||
override val openBrowserEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
fun backClicked() {
|
||||
pushMultisigSettingsResponder.respond(PushMultisigSettingsResponder.Response(settingsState.value.toModel()))
|
||||
router.back()
|
||||
}
|
||||
|
||||
fun switchMultisigNotificationsState() {
|
||||
val noMultisigWalletSelected = !request.isAtLeastOneMultisigWalletSelected
|
||||
if (isMultisigNotificationsDisabled() && noMultisigWalletSelected) {
|
||||
_noOneMultisigWalletSelectedEvent.sendEvent()
|
||||
return
|
||||
}
|
||||
|
||||
toggleMultisigEnablingState()
|
||||
}
|
||||
|
||||
private fun isMultisigNotificationsDisabled() = !settingsState.value.isEnabled
|
||||
|
||||
private fun toggleMultisigEnablingState() {
|
||||
settingsState.update {
|
||||
if (!it.isEnabled && it.isAllTypesDisabled()) {
|
||||
it.copy(isEnabled = true, isInitiatingEnabled = true, isApprovingEnabled = true, isExecutionEnabled = true, isRejectionEnabled = true)
|
||||
} else {
|
||||
it.copy(isEnabled = !it.isEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun switchInitialNotificationsState() {
|
||||
settingsState.update {
|
||||
it.copy(isInitiatingEnabled = !it.isInitiatingEnabled)
|
||||
.disableIfAllTypesDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
fun switchApprovingNotificationsState() {
|
||||
settingsState.update {
|
||||
it.copy(isApprovingEnabled = !it.isApprovingEnabled)
|
||||
.disableIfAllTypesDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
fun switchExecutionNotificationsState() {
|
||||
settingsState.update {
|
||||
it.copy(isExecutionEnabled = !it.isExecutionEnabled)
|
||||
.disableIfAllTypesDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
fun switchRejectionNotificationsState() {
|
||||
settingsState.update {
|
||||
it.copy(isRejectionEnabled = !it.isRejectionEnabled)
|
||||
.disableIfAllTypesDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
fun learnMoreClicked() {
|
||||
openBrowserEvent.value = Event(appLinksProvider.multisigsWikiUrl)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigs.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsFragment
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsRequester
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
PushMultisigSettingsModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface PushMultisigSettingsComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance request: PushMultisigSettingsRequester.Request
|
||||
): PushMultisigSettingsComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: PushMultisigSettingsFragment)
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigs.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsRequester
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsViewModel
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class PushMultisigSettingsModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(PushMultisigSettingsViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: PushNotificationsRouter,
|
||||
pushMultisigSettingsCommunicator: PushMultisigSettingsCommunicator,
|
||||
request: PushMultisigSettingsRequester.Request,
|
||||
appLinksProvider: AppLinksProvider
|
||||
): ViewModel {
|
||||
return PushMultisigSettingsViewModel(
|
||||
router = router,
|
||||
pushMultisigSettingsResponder = pushMultisigSettingsCommunicator,
|
||||
request = request,
|
||||
appLinksProvider = appLinksProvider
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory
|
||||
): PushMultisigSettingsViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(PushMultisigSettingsViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigsWarning
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import io.novafoundation.nova.common.R
|
||||
import io.novafoundation.nova.common.utils.WithContextExtensions
|
||||
import io.novafoundation.nova.common.view.bottomSheet.BaseBottomSheet
|
||||
import io.novafoundation.nova.feature_push_notifications.databinding.FragmentEnableMultisigPushesWarningBinding
|
||||
|
||||
open class MultisigPushNotificationsAlertBottomSheet(
|
||||
context: Context,
|
||||
private val onEnableClicked: () -> Unit,
|
||||
) : BaseBottomSheet<FragmentEnableMultisigPushesWarningBinding>(context, R.style.BottomSheetDialog), WithContextExtensions by WithContextExtensions(context) {
|
||||
|
||||
override val binder = FragmentEnableMultisigPushesWarningBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binder.enableMultisigPushesNotNow.setOnClickListener { dismiss() }
|
||||
binder.enableMultisigPushesEnable.setOnClickListener {
|
||||
onEnableClicked()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigsWarning
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.launchUnit
|
||||
import io.novafoundation.nova.common.utils.sendEvent
|
||||
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
|
||||
import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed
|
||||
import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.MultisigPushAlertInteractor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class MultisigPushNotificationsAlertMixinFactory(
|
||||
private val automaticInteractionGate: AutomaticInteractionGate,
|
||||
private val interactor: MultisigPushAlertInteractor,
|
||||
private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry,
|
||||
private val router: PushNotificationsRouter
|
||||
) {
|
||||
fun create(coroutineScope: CoroutineScope): MultisigPushNotificationsAlertMixin {
|
||||
return RealMultisigPushNotificationsAlertMixin(
|
||||
automaticInteractionGate,
|
||||
interactor,
|
||||
metaAccountsUpdatesRegistry,
|
||||
router,
|
||||
coroutineScope
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface MultisigPushNotificationsAlertMixin {
|
||||
|
||||
val showAlertEvent: LiveData<Event<Unit>>
|
||||
|
||||
fun subscribeToShowAlert()
|
||||
|
||||
fun showPushSettings()
|
||||
}
|
||||
|
||||
class RealMultisigPushNotificationsAlertMixin(
|
||||
private val automaticInteractionGate: AutomaticInteractionGate,
|
||||
private val interactor: MultisigPushAlertInteractor,
|
||||
private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry,
|
||||
private val router: PushNotificationsRouter,
|
||||
private val coroutineScope: CoroutineScope
|
||||
) : MultisigPushNotificationsAlertMixin {
|
||||
|
||||
override val showAlertEvent = MutableLiveData<Event<Unit>>()
|
||||
|
||||
override fun subscribeToShowAlert() = coroutineScope.launchUnit {
|
||||
if (interactor.isAlertAlreadyShown()) return@launchUnit
|
||||
|
||||
// We should get this state before multisigs will be discovered so we call this method before interaction gate
|
||||
val allowedToShowAlertAtStart = interactor.allowedToShowAlertAtStart()
|
||||
|
||||
automaticInteractionGate.awaitInteractionAllowed()
|
||||
|
||||
if (allowedToShowAlertAtStart) {
|
||||
showAlert()
|
||||
return@launchUnit
|
||||
}
|
||||
|
||||
// We have to show alert after user saw new multisigs in account list so we subscribed to its update states
|
||||
// And show alert when at least one multisig update was consumed
|
||||
metaAccountsUpdatesRegistry.observeLastConsumedUpdatesMetaIds()
|
||||
.onEach { consumedMetaIdsUpdates ->
|
||||
if (interactor.isAlertAlreadyShown()) return@onEach
|
||||
|
||||
if (interactor.hasMultisigWallets(consumedMetaIdsUpdates.toList())) {
|
||||
// We need to check interaction again since app may went to background before consuming updates
|
||||
automaticInteractionGate.awaitInteractionAllowed()
|
||||
showAlert()
|
||||
}
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
private fun showAlert() {
|
||||
interactor.setAlertWasAlreadyShown()
|
||||
showAlertEvent.sendEvent()
|
||||
}
|
||||
|
||||
override fun showPushSettings() {
|
||||
router.openPushSettingsWithAccounts()
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.multisigsWarning
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseScreenMixin
|
||||
|
||||
fun BaseScreenMixin<*>.observeEnableMultisigPushesAlert(mixin: MultisigPushNotificationsAlertMixin) {
|
||||
mixin.showAlertEvent.observeEvent {
|
||||
MultisigPushNotificationsAlertBottomSheet(providedContext, onEnableClicked = { mixin.showPushSettings() }).show()
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.settings
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.mixin.actionAwaitable.setupConfirmationDialog
|
||||
import io.novafoundation.nova.common.utils.FragmentPayloadCreator
|
||||
import io.novafoundation.nova.common.utils.PayloadCreator
|
||||
import io.novafoundation.nova.common.utils.payload
|
||||
import io.novafoundation.nova.common.utils.permissions.setupPermissionAsker
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.databinding.FragmentPushSettingsBinding
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureComponent
|
||||
|
||||
class PushSettingsFragment : BaseFragment<PushSettingsViewModel, FragmentPushSettingsBinding>() {
|
||||
|
||||
companion object : PayloadCreator<PushSettingsPayload> by FragmentPayloadCreator()
|
||||
|
||||
override fun createBinding() = FragmentPushSettingsBinding.inflate(layoutInflater)
|
||||
|
||||
override fun initViews() {
|
||||
onBackPressed { viewModel.backClicked() }
|
||||
|
||||
binder.pushSettingsToolbar.setRightActionClickListener { viewModel.saveClicked() }
|
||||
binder.pushSettingsToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
|
||||
binder.pushSettingsEnable.setOnClickListener { viewModel.enableSwitcherClicked() }
|
||||
binder.pushSettingsWallets.setOnClickListener { viewModel.walletsClicked() }
|
||||
binder.pushSettingsAnnouncements.setOnClickListener { viewModel.announementsClicked() }
|
||||
binder.pushSettingsSentTokens.setOnClickListener { viewModel.sentTokensClicked() }
|
||||
binder.pushSettingsMultisigs.setOnClickListener { viewModel.multisigOperationsClicked() }
|
||||
binder.pushSettingsReceivedTokens.setOnClickListener { viewModel.receivedTokensClicked() }
|
||||
binder.pushSettingsGovernance.setOnClickListener { viewModel.governanceClicked() }
|
||||
binder.pushSettingsStakingRewards.setOnClickListener { viewModel.stakingRewardsClicked() }
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<PushNotificationsFeatureComponent>(requireContext(), PushNotificationsFeatureApi::class.java)
|
||||
.pushSettingsComponentFactory()
|
||||
.create(this, payload())
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: PushSettingsViewModel) {
|
||||
setupConfirmationDialog(R.style.AccentNegativeAlertDialogTheme_Reversed, viewModel.closeConfirmationAction)
|
||||
setupPermissionAsker(viewModel)
|
||||
|
||||
viewModel.pushSettingsWasChangedState.observe { binder.pushSettingsToolbar.setRightActionEnabled(it) }
|
||||
viewModel.savingInProgress.observe { binder.pushSettingsToolbar.showProgress(it) }
|
||||
|
||||
viewModel.pushEnabledState.observe { enabled ->
|
||||
binder.pushSettingsEnable.setChecked(enabled)
|
||||
binder.pushSettingsAnnouncements.setEnabled(enabled)
|
||||
binder.pushSettingsSentTokens.setEnabled(enabled)
|
||||
binder.pushSettingsReceivedTokens.setEnabled(enabled)
|
||||
binder.pushSettingsMultisigs.setEnabled(enabled)
|
||||
binder.pushSettingsGovernance.setEnabled(enabled)
|
||||
binder.pushSettingsStakingRewards.setEnabled(enabled)
|
||||
}
|
||||
|
||||
viewModel.pushWalletsQuantity.observe { binder.pushSettingsWallets.setValue(it) }
|
||||
viewModel.showNoSelectedWalletsTip.observe { binder.pushSettingsNoSelectedWallets.isVisible = it }
|
||||
viewModel.pushAnnouncements.observe { binder.pushSettingsAnnouncements.setChecked(it) }
|
||||
viewModel.pushSentTokens.observe { binder.pushSettingsSentTokens.setChecked(it) }
|
||||
viewModel.pushReceivedTokens.observe { binder.pushSettingsReceivedTokens.setChecked(it) }
|
||||
viewModel.pushMultisigsState.observe { binder.pushSettingsMultisigs.setValue(it) }
|
||||
viewModel.pushGovernanceState.observe { binder.pushSettingsGovernance.setValue(it) }
|
||||
viewModel.pushStakingRewardsState.observe { binder.pushSettingsStakingRewards.setValue(it) }
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.settings
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class PushSettingsPayload(
|
||||
val enableSwitcherOnStart: Boolean,
|
||||
val navigation: InstantNavigation
|
||||
) : Parcelable {
|
||||
companion object;
|
||||
|
||||
sealed interface InstantNavigation : Parcelable {
|
||||
@Parcelize
|
||||
data object Nothing : InstantNavigation
|
||||
|
||||
@Parcelize
|
||||
data object WithWalletSelection : InstantNavigation
|
||||
}
|
||||
}
|
||||
|
||||
fun PushSettingsPayload.Companion.default(enableSwitcherOnStart: Boolean = false) =
|
||||
PushSettingsPayload(enableSwitcherOnStart, PushSettingsPayload.InstantNavigation.Nothing)
|
||||
|
||||
fun PushSettingsPayload.Companion.withWalletSelection(enableSwitcherOnStart: Boolean = false) =
|
||||
PushSettingsPayload(enableSwitcherOnStart, PushSettingsPayload.InstantNavigation.WithWalletSelection)
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.settings
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
|
||||
import io.novafoundation.nova.common.mixin.actionAwaitable.ConfirmationDialogInfo
|
||||
import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingAction
|
||||
import io.novafoundation.nova.common.mixin.actionAwaitable.fromRes
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.resources.formatBooleanToState
|
||||
import io.novafoundation.nova.common.utils.flowOf
|
||||
import io.novafoundation.nova.common.utils.formatting.format
|
||||
import io.novafoundation.nova.common.utils.launchUnit
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.common.utils.toggle
|
||||
import io.novafoundation.nova.common.utils.updateValue
|
||||
import io.novafoundation.nova.feature_account_api.domain.model.isMultisig
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsRequester
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.fromTrackIds
|
||||
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.toTrackIds
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.PushSettings
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.isGovEnabled
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.model.isNotEmpty
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsPayload
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsRequester
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsResponder
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsRequester
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.toDomain
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.toModel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsPayload
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsRequester
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val MIN_WALLETS = 1
|
||||
private const val MAX_WALLETS = 10
|
||||
|
||||
class PushSettingsViewModel(
|
||||
private val router: PushNotificationsRouter,
|
||||
private val pushNotificationsInteractor: PushNotificationsInteractor,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val walletRequester: SelectMultipleWalletsRequester,
|
||||
private val pushGovernanceSettingsRequester: PushGovernanceSettingsRequester,
|
||||
private val pushStakingSettingsRequester: PushStakingSettingsRequester,
|
||||
private val pushMultisigSettingsRequester: PushMultisigSettingsRequester,
|
||||
private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory,
|
||||
private val permissionsAsker: PermissionsAsker.Presentation,
|
||||
private val payload: PushSettingsPayload
|
||||
) : BaseViewModel(), PermissionsAsker by permissionsAsker {
|
||||
|
||||
val closeConfirmationAction = actionAwaitableMixinFactory.confirmingAction<ConfirmationDialogInfo>()
|
||||
|
||||
private val oldPushSettingsState = flowOf { pushNotificationsInteractor.getPushSettings() }
|
||||
.shareInBackground()
|
||||
|
||||
val pushEnabledState = MutableStateFlow(pushNotificationsInteractor.isPushNotificationsEnabled())
|
||||
private val pushSettingsState = MutableStateFlow<PushSettings?>(null)
|
||||
|
||||
val pushSettingsWasChangedState = combine(pushEnabledState, pushSettingsState, oldPushSettingsState) { pushEnabled, newState, oldState ->
|
||||
pushEnabled != pushNotificationsInteractor.isPushNotificationsEnabled() ||
|
||||
newState != oldState
|
||||
}
|
||||
|
||||
val pushWalletsQuantity = pushSettingsState
|
||||
.mapNotNull { it?.subscribedMetaAccounts?.size?.format() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val pushAnnouncements = pushSettingsState.mapNotNull { it?.announcementsEnabled }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val pushSentTokens = pushSettingsState.mapNotNull { it?.sentTokensEnabled }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val pushReceivedTokens = pushSettingsState.mapNotNull { it?.receivedTokensEnabled }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val pushGovernanceState = pushSettingsState.mapNotNull { it }
|
||||
.map { resourceManager.formatBooleanToState(it.isGovEnabled()) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val pushMultisigsState = pushSettingsState.mapNotNull { it }
|
||||
.map { resourceManager.formatBooleanToState(it.multisigs.isEnabled) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val pushStakingRewardsState = pushSettingsState.mapNotNull { it }
|
||||
.map { resourceManager.formatBooleanToState(it.stakingReward.isNotEmpty()) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val showNoSelectedWalletsTip = pushSettingsState
|
||||
.mapNotNull { it?.subscribedMetaAccounts?.isEmpty() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
private val _savingInProgress = MutableStateFlow(false)
|
||||
val savingInProgress: Flow<Boolean> = _savingInProgress
|
||||
|
||||
init {
|
||||
initFirstState()
|
||||
|
||||
subscribeOnSelectWallets()
|
||||
subscribeOnGovernanceSettings()
|
||||
subscribeOnStakingSettings()
|
||||
subscribeMultisigSettings()
|
||||
disableNotificationsIfPushSettingsEmpty()
|
||||
|
||||
enableSwitcherOnStartIfRequested()
|
||||
}
|
||||
|
||||
private fun initFirstState() {
|
||||
launch {
|
||||
val settings = oldPushSettingsState.first()
|
||||
pushSettingsState.value = pushNotificationsInteractor.filterAvailableMetaIdsAndGetNewState(settings)
|
||||
|
||||
openWalletSelectionIfRequested()
|
||||
}
|
||||
}
|
||||
|
||||
fun backClicked() {
|
||||
launch {
|
||||
if (pushSettingsWasChangedState.first()) {
|
||||
closeConfirmationAction.awaitAction(
|
||||
ConfirmationDialogInfo.fromRes(
|
||||
resourceManager,
|
||||
R.string.common_confirmation_title,
|
||||
R.string.common_close_confirmation_message,
|
||||
R.string.common_close,
|
||||
R.string.common_cancel,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveClicked() {
|
||||
launch {
|
||||
_savingInProgress.value = true
|
||||
val pushSettings = pushSettingsState.value ?: return@launch
|
||||
pushNotificationsInteractor.updatePushSettings(pushEnabledState.value, pushSettings)
|
||||
.onSuccess {
|
||||
enableMultisigWalletIfAtLeastOneSelected(pushSettings)
|
||||
router.back()
|
||||
}
|
||||
.onFailure { showError(it) }
|
||||
|
||||
_savingInProgress.value = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableMultisigWalletIfAtLeastOneSelected(pushSettings: PushSettings) {
|
||||
if (pushSettings.multisigs.isEnabled && isMultisigsStillWasNotEnabled()) {
|
||||
pushNotificationsInteractor.setMultisigsWasEnabledFirstTime()
|
||||
}
|
||||
}
|
||||
|
||||
fun enableSwitcherClicked() {
|
||||
launch {
|
||||
if (!pushEnabledState.value && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val isPermissionsGranted = permissionsAsker.requirePermissions(Manifest.permission.POST_NOTIFICATIONS)
|
||||
|
||||
if (!isPermissionsGranted) {
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
if (!pushEnabledState.value) {
|
||||
setDefaultPushSettingsIfEmpty()
|
||||
}
|
||||
|
||||
pushEnabledState.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
fun walletsClicked() {
|
||||
selectWallets()
|
||||
}
|
||||
|
||||
fun announementsClicked() {
|
||||
pushSettingsState.updateValue { it?.copy(announcementsEnabled = !it.announcementsEnabled) }
|
||||
}
|
||||
|
||||
fun sentTokensClicked() {
|
||||
pushSettingsState.updateValue { it?.copy(sentTokensEnabled = !it.sentTokensEnabled) }
|
||||
}
|
||||
|
||||
fun receivedTokensClicked() {
|
||||
pushSettingsState.updateValue { it?.copy(receivedTokensEnabled = !it.receivedTokensEnabled) }
|
||||
}
|
||||
|
||||
fun multisigOperationsClicked() = launchUnit {
|
||||
val settings = pushSettingsState.value ?: return@launchUnit
|
||||
val isAtLeastOneAccountMultisig = settings.subscribedMetaAccounts.atLeastOneMultisigWalletEnabled()
|
||||
pushMultisigSettingsRequester.openRequest(
|
||||
PushMultisigSettingsRequester.Request(isAtLeastOneAccountMultisig, settings.multisigs.toModel())
|
||||
)
|
||||
}
|
||||
|
||||
fun governanceClicked() {
|
||||
val settings = pushSettingsState.value ?: return
|
||||
pushGovernanceSettingsRequester.openRequest(PushGovernanceSettingsRequester.Request(mapGovSettingsToPayload(settings)))
|
||||
}
|
||||
|
||||
fun stakingRewardsClicked() {
|
||||
val stakingRewards = pushSettingsState.value?.stakingReward ?: return
|
||||
val settings = when (stakingRewards) {
|
||||
is PushSettings.ChainFeature.All -> PushStakingSettingsPayload.AllChains
|
||||
is PushSettings.ChainFeature.Concrete -> PushStakingSettingsPayload.SpecifiedChains(stakingRewards.chainIds.toSet())
|
||||
}
|
||||
val request = PushStakingSettingsRequester.Request(settings)
|
||||
pushStakingSettingsRequester.openRequest(request)
|
||||
}
|
||||
|
||||
private fun subscribeOnSelectWallets() {
|
||||
walletRequester.responseFlow
|
||||
.onEach { response ->
|
||||
val currentState = pushSettingsState.value ?: return@onEach
|
||||
val newPushSettingsState = pushNotificationsInteractor.getNewStateForChangedMetaAccounts(currentState, response.selectedMetaIds)
|
||||
|
||||
pushSettingsState.value = newPushSettingsState
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun subscribeOnGovernanceSettings() {
|
||||
pushGovernanceSettingsRequester.responseFlow
|
||||
.onEach { response ->
|
||||
pushSettingsState.updateValue { settings ->
|
||||
settings?.copy(governance = mapGovSettingsResponseToModel(response))
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun subscribeOnStakingSettings() {
|
||||
pushStakingSettingsRequester.responseFlow
|
||||
.onEach { response ->
|
||||
val stakingSettings = when (response.settings) {
|
||||
is PushStakingSettingsPayload.AllChains -> PushSettings.ChainFeature.All
|
||||
is PushStakingSettingsPayload.SpecifiedChains -> PushSettings.ChainFeature.Concrete(response.settings.enabledChainIds.toList())
|
||||
}
|
||||
|
||||
pushSettingsState.updateValue { settings ->
|
||||
settings?.copy(stakingReward = stakingSettings)
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun subscribeMultisigSettings() {
|
||||
pushMultisigSettingsRequester.responseFlow
|
||||
.onEach { response ->
|
||||
pushSettingsState.updateValue { settings ->
|
||||
settings?.copy(multisigs = response.settings.toDomain())
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun mapGovSettingsToPayload(pushSettings: PushSettings): List<PushGovernanceSettingsPayload> {
|
||||
return pushSettings.governance.map { (chainId, govState) ->
|
||||
PushGovernanceSettingsPayload(
|
||||
chainId = chainId,
|
||||
governance = Chain.Governance.V2,
|
||||
newReferenda = govState.newReferendaEnabled,
|
||||
referendaUpdates = govState.referendumUpdateEnabled,
|
||||
delegateVotes = govState.govMyDelegateVotedEnabled,
|
||||
tracksIds = govState.tracks.fromTrackIds()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapGovSettingsResponseToModel(response: PushGovernanceSettingsResponder.Response): Map<ChainId, PushSettings.GovernanceState> {
|
||||
return response.enabledGovernanceSettings
|
||||
.associateBy { it.chainId }
|
||||
.mapValues { (_, govState) ->
|
||||
PushSettings.GovernanceState(
|
||||
newReferendaEnabled = govState.newReferenda,
|
||||
referendumUpdateEnabled = govState.referendaUpdates,
|
||||
govMyDelegateVotedEnabled = govState.delegateVotes,
|
||||
tracks = govState.tracksIds.toTrackIds()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableNotificationsIfPushSettingsEmpty() {
|
||||
pushSettingsState
|
||||
.filterNotNull()
|
||||
.onEach { pushSettings ->
|
||||
if (pushSettings.settingsIsEmpty()) {
|
||||
pushEnabledState.value = false
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private suspend fun setDefaultPushSettingsIfEmpty() {
|
||||
if (pushSettingsState.value?.settingsIsEmpty() == true) {
|
||||
pushSettingsState.value = pushNotificationsInteractor.getPushSettings()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Collection<Long>.atLeastOneMultisigWalletEnabled(): Boolean {
|
||||
return pushNotificationsInteractor.getMetaAccounts(this.toList())
|
||||
.any { it.isMultisig() }
|
||||
}
|
||||
|
||||
private fun isMultisigsStillWasNotEnabled() = !pushNotificationsInteractor.isMultisigsWasEnabledFirstTime()
|
||||
|
||||
private fun enableSwitcherOnStartIfRequested() {
|
||||
if (payload.enableSwitcherOnStart) {
|
||||
pushEnabledState.value = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun openWalletSelectionIfRequested() {
|
||||
if (payload.navigation is PushSettingsPayload.InstantNavigation.WithWalletSelection) {
|
||||
selectWallets()
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectWallets() {
|
||||
walletRequester.openRequest(
|
||||
SelectMultipleWalletsRequester.Request(
|
||||
titleText = resourceManager.getString(R.string.push_wallets_title, MAX_WALLETS),
|
||||
currentlySelectedMetaIds = pushSettingsState.value?.subscribedMetaAccounts?.toSet().orEmpty(),
|
||||
min = MIN_WALLETS,
|
||||
max = MAX_WALLETS
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.settings.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.settings.PushSettingsFragment
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.settings.PushSettingsPayload
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
PushSettingsModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface PushSettingsComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance payload: PushSettingsPayload
|
||||
): PushSettingsComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: PushSettingsFragment)
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.settings.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.settings.PushSettingsPayload
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.settings.PushSettingsViewModel
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsCommunicator
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class PushSettingsModule {
|
||||
|
||||
@Provides
|
||||
fun providePermissionAsker(
|
||||
permissionsAskerFactory: PermissionsAskerFactory,
|
||||
fragment: Fragment,
|
||||
router: PushNotificationsRouter
|
||||
) = permissionsAskerFactory.createReturnable(fragment, router)
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(PushSettingsViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: PushNotificationsRouter,
|
||||
interactor: PushNotificationsInteractor,
|
||||
resourceManager: ResourceManager,
|
||||
selectMultipleWalletsCommunicator: SelectMultipleWalletsCommunicator,
|
||||
pushGovernanceSettingsCommunicator: PushGovernanceSettingsCommunicator,
|
||||
pushStakingSettingsRequester: PushStakingSettingsCommunicator,
|
||||
permissionsAsker: PermissionsAsker.Presentation,
|
||||
actionAwaitableMixinFactory: ActionAwaitableMixin.Factory,
|
||||
pushMultisigSettingsCommunicator: PushMultisigSettingsCommunicator,
|
||||
payload: PushSettingsPayload
|
||||
): ViewModel {
|
||||
return PushSettingsViewModel(
|
||||
router,
|
||||
interactor,
|
||||
resourceManager,
|
||||
selectMultipleWalletsCommunicator,
|
||||
pushGovernanceSettingsCommunicator,
|
||||
pushStakingSettingsRequester,
|
||||
pushMultisigSettingsCommunicator,
|
||||
actionAwaitableMixinFactory,
|
||||
permissionsAsker,
|
||||
payload
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory
|
||||
): PushSettingsViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(PushSettingsViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.staking
|
||||
|
||||
import android.os.Parcelable
|
||||
import io.novafoundation.nova.common.navigation.InterScreenRequester
|
||||
import io.novafoundation.nova.common.navigation.InterScreenResponder
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
interface PushStakingSettingsRequester : InterScreenRequester<PushStakingSettingsRequester.Request, PushStakingSettingsResponder.Response> {
|
||||
|
||||
@Parcelize
|
||||
class Request(val settings: PushStakingSettingsPayload) : Parcelable
|
||||
}
|
||||
|
||||
interface PushStakingSettingsResponder : InterScreenResponder<PushStakingSettingsRequester.Request, PushStakingSettingsResponder.Response> {
|
||||
|
||||
@Parcelize
|
||||
class Response(val settings: PushStakingSettingsPayload) : Parcelable
|
||||
}
|
||||
|
||||
interface PushStakingSettingsCommunicator : PushStakingSettingsRequester, PushStakingSettingsResponder
|
||||
|
||||
sealed class PushStakingSettingsPayload : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
object AllChains : PushStakingSettingsPayload()
|
||||
|
||||
@Parcelize
|
||||
class SpecifiedChains(val enabledChainIds: Set<ChainId>) : PushStakingSettingsPayload()
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.staking
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.view.isVisible
|
||||
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.domain.ExtendedLoadingState
|
||||
import io.novafoundation.nova.feature_push_notifications.databinding.FragmentPushStakingSettingsBinding
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureComponent
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.adapter.PushStakingRVItem
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.adapter.PushStakingSettingsAdapter
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushStakingSettingsFragment : BaseFragment<PushStakingSettingsViewModel, FragmentPushStakingSettingsBinding>(), PushStakingSettingsAdapter.ItemHandler {
|
||||
|
||||
companion object {
|
||||
private const val KEY_REQUEST = "KEY_REQUEST"
|
||||
|
||||
fun getBundle(request: PushStakingSettingsRequester.Request): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelable(KEY_REQUEST, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createBinding() = FragmentPushStakingSettingsBinding.inflate(layoutInflater)
|
||||
|
||||
@Inject
|
||||
lateinit var imageLoader: ImageLoader
|
||||
|
||||
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
PushStakingSettingsAdapter(imageLoader, this)
|
||||
}
|
||||
|
||||
override fun initViews() {
|
||||
binder.pushStakingToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
binder.pushStakingToolbar.setRightActionClickListener { viewModel.clearClicked() }
|
||||
onBackPressed { viewModel.backClicked() }
|
||||
|
||||
binder.pushStakingList.adapter = adapter
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<PushNotificationsFeatureComponent>(requireContext(), PushNotificationsFeatureApi::class.java)
|
||||
.pushStakingSettings()
|
||||
.create(this, argument(KEY_REQUEST))
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: PushStakingSettingsViewModel) {
|
||||
viewModel.clearButtonEnabledFlow.observe {
|
||||
binder.pushStakingToolbar.setRightActionEnabled(it)
|
||||
}
|
||||
|
||||
viewModel.stakingSettingsList.observe {
|
||||
binder.pushStakingList.isVisible = it is ExtendedLoadingState.Loaded
|
||||
binder.pushStakingProgress.isVisible = it is ExtendedLoadingState.Loading
|
||||
|
||||
if (it is ExtendedLoadingState.Loaded) {
|
||||
adapter.submitList(it.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun itemClicked(item: PushStakingRVItem) {
|
||||
viewModel.itemClicked(item)
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.staking
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.mapToSet
|
||||
import io.novafoundation.nova.common.utils.toggle
|
||||
import io.novafoundation.nova.common.utils.updateValue
|
||||
import io.novafoundation.nova.common.utils.withSafeLoading
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.StakingPushSettingsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.adapter.PushStakingRVItem
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PushStakingSettingsViewModel(
|
||||
private val router: PushNotificationsRouter,
|
||||
private val pushStakingSettingsResponder: PushStakingSettingsCommunicator,
|
||||
private val chainRegistry: ChainRegistry,
|
||||
private val request: PushStakingSettingsRequester.Request,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val stakingPushSettingsInteractor: StakingPushSettingsInteractor
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val chainsFlow = stakingPushSettingsInteractor.stakingChainsFlow()
|
||||
|
||||
val _enabledStakingSettingsList: MutableStateFlow<Set<ChainId>> = MutableStateFlow(emptySet())
|
||||
|
||||
val stakingSettingsList = combine(chainsFlow, _enabledStakingSettingsList) { chains, enabledChains ->
|
||||
chains.map { chain ->
|
||||
PushStakingRVItem(
|
||||
chain.id,
|
||||
chain.name,
|
||||
chain.icon,
|
||||
enabledChains.contains(chain.id)
|
||||
)
|
||||
}
|
||||
}.withSafeLoading()
|
||||
.shareInBackground()
|
||||
|
||||
val clearButtonEnabledFlow = _enabledStakingSettingsList.map {
|
||||
it.isNotEmpty()
|
||||
}.shareInBackground()
|
||||
|
||||
init {
|
||||
launch {
|
||||
_enabledStakingSettingsList.value = when (request.settings) {
|
||||
PushStakingSettingsPayload.AllChains -> chainsFlow.first().mapToSet { it.id }
|
||||
|
||||
is PushStakingSettingsPayload.SpecifiedChains -> request.settings.enabledChainIds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun backClicked() {
|
||||
launch {
|
||||
val allChainsIds = chainsFlow.first().mapToSet { it.id }
|
||||
val enabledChains = _enabledStakingSettingsList.value
|
||||
|
||||
val settings = if (enabledChains == allChainsIds) {
|
||||
PushStakingSettingsPayload.AllChains
|
||||
} else {
|
||||
PushStakingSettingsPayload.SpecifiedChains(enabledChains)
|
||||
}
|
||||
|
||||
val response = PushStakingSettingsResponder.Response(settings)
|
||||
pushStakingSettingsResponder.respond(response)
|
||||
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
fun clearClicked() {
|
||||
_enabledStakingSettingsList.value = emptySet()
|
||||
}
|
||||
|
||||
fun itemClicked(item: PushStakingRVItem) {
|
||||
_enabledStakingSettingsList.updateValue {
|
||||
it.toggle(item.chainId)
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.staking.adapter
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
data class PushStakingRVItem(
|
||||
val chainId: ChainId,
|
||||
val chainName: String,
|
||||
val chainIconUrl: String?,
|
||||
val isEnabled: Boolean,
|
||||
)
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.staking.adapter
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.list.PayloadGenerator
|
||||
import io.novafoundation.nova.common.list.resolvePayload
|
||||
import io.novafoundation.nova.common.utils.inflater
|
||||
import io.novafoundation.nova.feature_account_api.presenatation.chain.loadChainIconToTarget
|
||||
import io.novafoundation.nova.feature_push_notifications.databinding.ItemPushStakingSettingsBinding
|
||||
|
||||
class PushStakingSettingsAdapter(
|
||||
private val imageLoader: ImageLoader,
|
||||
private val itemHandler: ItemHandler
|
||||
) : ListAdapter<PushStakingRVItem, PushStakingItemViewHolder>(PushStakingItemCallback()) {
|
||||
|
||||
interface ItemHandler {
|
||||
fun itemClicked(item: PushStakingRVItem)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PushStakingItemViewHolder {
|
||||
return PushStakingItemViewHolder(ItemPushStakingSettingsBinding.inflate(parent.inflater(), parent, false), imageLoader, itemHandler)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PushStakingItemViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PushStakingItemViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
resolvePayload(holder, position, payloads) {
|
||||
val item = getItem(position)
|
||||
|
||||
when (it) {
|
||||
PushStakingRVItem::isEnabled -> holder.setEnabled(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PushStakingItemCallback() : DiffUtil.ItemCallback<PushStakingRVItem>() {
|
||||
override fun areItemsTheSame(oldItem: PushStakingRVItem, newItem: PushStakingRVItem): Boolean {
|
||||
return oldItem.chainId == newItem.chainId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: PushStakingRVItem, newItem: PushStakingRVItem): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: PushStakingRVItem, newItem: PushStakingRVItem): Any? {
|
||||
return PushStakingPayloadGenerator.diff(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
|
||||
class PushStakingItemViewHolder(
|
||||
private val binder: ItemPushStakingSettingsBinding,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val itemHandler: PushStakingSettingsAdapter.ItemHandler
|
||||
) : ViewHolder(binder.root) {
|
||||
|
||||
init {
|
||||
binder.pushStakingItem.setIconTintColor(null)
|
||||
}
|
||||
|
||||
fun bind(item: PushStakingRVItem) {
|
||||
with(binder) {
|
||||
pushStakingItem.setOnClickListener {
|
||||
itemHandler.itemClicked(item)
|
||||
}
|
||||
|
||||
pushStakingItem.setTitle(item.chainName)
|
||||
imageLoader.loadChainIconToTarget(item.chainIconUrl, binder.root.context) {
|
||||
pushStakingItem.setIcon(it)
|
||||
}
|
||||
|
||||
setEnabled(item)
|
||||
}
|
||||
}
|
||||
|
||||
fun setEnabled(item: PushStakingRVItem) {
|
||||
binder.pushStakingItem.setChecked(item.isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private object PushStakingPayloadGenerator : PayloadGenerator<PushStakingRVItem>(
|
||||
PushStakingRVItem::isEnabled
|
||||
)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.staking.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsFragment
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsRequester
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
PushStakingSettingsModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface PushStakingSettingsComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
@BindsInstance fragment: Fragment,
|
||||
@BindsInstance request: PushStakingSettingsRequester.Request
|
||||
): PushStakingSettingsComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: PushStakingSettingsFragment)
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.staking.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.StakingPushSettingsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsCommunicator
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsRequester
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsViewModel
|
||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class PushStakingSettingsModule {
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(PushStakingSettingsViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: PushNotificationsRouter,
|
||||
pushStakingSettingsCommunicator: PushStakingSettingsCommunicator,
|
||||
chainRegistry: ChainRegistry,
|
||||
request: PushStakingSettingsRequester.Request,
|
||||
resourceManager: ResourceManager,
|
||||
stakingPushSettingsInteractor: StakingPushSettingsInteractor
|
||||
): ViewModel {
|
||||
return PushStakingSettingsViewModel(
|
||||
router = router,
|
||||
pushStakingSettingsResponder = pushStakingSettingsCommunicator,
|
||||
chainRegistry = chainRegistry,
|
||||
request = request,
|
||||
resourceManager = resourceManager,
|
||||
stakingPushSettingsInteractor = stakingPushSettingsInteractor
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory
|
||||
): PushStakingSettingsViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(PushStakingSettingsViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.welcome
|
||||
|
||||
import io.novafoundation.nova.common.base.BaseFragment
|
||||
import io.novafoundation.nova.common.di.FeatureUtils
|
||||
import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents
|
||||
import io.novafoundation.nova.common.mixin.impl.observeRetries
|
||||
import io.novafoundation.nova.common.utils.formatting.applyTermsAndPrivacyPolicy
|
||||
import io.novafoundation.nova.common.utils.permissions.setupPermissionAsker
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.databinding.FragmentPushWelcomeBinding
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
|
||||
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureComponent
|
||||
|
||||
class PushWelcomeFragment : BaseFragment<PushWelcomeViewModel, FragmentPushWelcomeBinding>() {
|
||||
|
||||
override fun createBinding() = FragmentPushWelcomeBinding.inflate(layoutInflater)
|
||||
|
||||
override fun initViews() {
|
||||
binder.pushWelcomeEnableButton.prepareForProgress(this)
|
||||
binder.pushWelcomeCancelButton.prepareForProgress(this)
|
||||
binder.pushWelcomeToolbar.setHomeButtonListener { viewModel.backClicked() }
|
||||
binder.pushWelcomeEnableButton.setOnClickListener { viewModel.askPermissionAndEnablePushNotifications() }
|
||||
binder.pushWelcomeCancelButton.setOnClickListener { viewModel.backClicked() }
|
||||
|
||||
configureTermsAndPrivacy()
|
||||
}
|
||||
|
||||
private fun configureTermsAndPrivacy() {
|
||||
binder.pushWelcomeTermsAndConditions.applyTermsAndPrivacyPolicy(
|
||||
R.string.push_welcome_terms_and_conditions,
|
||||
R.string.common_terms_and_conditions_formatting,
|
||||
R.string.common_privacy_policy_formatting,
|
||||
viewModel::termsClicked,
|
||||
viewModel::privacyClicked
|
||||
)
|
||||
}
|
||||
|
||||
override fun inject() {
|
||||
FeatureUtils.getFeature<PushNotificationsFeatureComponent>(requireContext(), PushNotificationsFeatureApi::class.java)
|
||||
.pushWelcomeComponentFactory()
|
||||
.create(this)
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun subscribe(viewModel: PushWelcomeViewModel) {
|
||||
observeBrowserEvents(viewModel)
|
||||
observeRetries(viewModel)
|
||||
setupPermissionAsker(viewModel)
|
||||
|
||||
viewModel.buttonState.observe { state ->
|
||||
binder.pushWelcomeEnableButton.setState(state)
|
||||
binder.pushWelcomeCancelButton.setState(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.welcome
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.novafoundation.nova.common.base.BaseViewModel
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.mixin.api.Browserable
|
||||
import io.novafoundation.nova.common.mixin.api.Retriable
|
||||
import io.novafoundation.nova.common.mixin.api.RetryPayload
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.Event
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.common.view.ButtonState
|
||||
import io.novafoundation.nova.feature_push_notifications.R
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.WelcomePushNotificationsInteractor
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PushWelcomeViewModel(
|
||||
private val router: PushNotificationsRouter,
|
||||
private val pushNotificationsInteractor: PushNotificationsInteractor,
|
||||
private val welcomePushNotificationsInteractor: WelcomePushNotificationsInteractor,
|
||||
private val permissionsAsker: PermissionsAsker.Presentation,
|
||||
private val resourceManager: ResourceManager,
|
||||
private val appLinksProvider: AppLinksProvider
|
||||
) : BaseViewModel(), PermissionsAsker by permissionsAsker, Retriable, Browserable {
|
||||
|
||||
private val _enablingInProgress = MutableStateFlow(false)
|
||||
|
||||
override val retryEvent: MutableLiveData<Event<RetryPayload>> = MutableLiveData()
|
||||
|
||||
override val openBrowserEvent = MutableLiveData<Event<String>>()
|
||||
|
||||
val buttonState = _enablingInProgress.map { inProgress ->
|
||||
when (inProgress) {
|
||||
true -> ButtonState.PROGRESS
|
||||
false -> ButtonState.NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
fun backClicked() {
|
||||
welcomePushNotificationsInteractor.setWelcomeScreenShown()
|
||||
router.back()
|
||||
}
|
||||
|
||||
fun termsClicked() {
|
||||
openBrowserEvent.value = Event(appLinksProvider.termsUrl)
|
||||
}
|
||||
|
||||
fun privacyClicked() {
|
||||
openBrowserEvent.value = Event(appLinksProvider.privacyUrl)
|
||||
}
|
||||
|
||||
fun askPermissionAndEnablePushNotifications() {
|
||||
launch {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val isPermissionsGranted = permissionsAsker.requirePermissions(Manifest.permission.POST_NOTIFICATIONS)
|
||||
|
||||
if (!isPermissionsGranted) {
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
_enablingInProgress.value = true
|
||||
pushNotificationsInteractor.initPushSettings()
|
||||
.onSuccess {
|
||||
welcomePushNotificationsInteractor.setWelcomeScreenShown()
|
||||
router.back()
|
||||
}
|
||||
.onFailure {
|
||||
when (it) {
|
||||
is TimeoutCancellationException -> showError(
|
||||
resourceManager.getString(R.string.common_something_went_wrong_title),
|
||||
resourceManager.getString(R.string.push_welcome_timeout_error_message)
|
||||
)
|
||||
|
||||
else -> retryDialog()
|
||||
}
|
||||
}
|
||||
|
||||
_enablingInProgress.value = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun retryDialog() {
|
||||
retryEvent.value = Event(
|
||||
RetryPayload(
|
||||
title = resourceManager.getString(R.string.common_error_general_title),
|
||||
message = resourceManager.getString(R.string.common_retry_message),
|
||||
onRetry = { askPermissionAndEnablePushNotifications() }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.welcome.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.welcome.PushWelcomeFragment
|
||||
|
||||
@Subcomponent(
|
||||
modules = [
|
||||
PushWelcomeModule::class
|
||||
]
|
||||
)
|
||||
@ScreenScope
|
||||
interface PushWelcomeComponent {
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(@BindsInstance fragment: Fragment): PushWelcomeComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: PushWelcomeFragment)
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package io.novafoundation.nova.feature_push_notifications.presentation.welcome.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.novafoundation.nova.common.data.network.AppLinksProvider
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||
import io.novafoundation.nova.common.resources.ResourceManager
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAsker
|
||||
import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory
|
||||
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.domain.interactor.WelcomePushNotificationsInteractor
|
||||
import io.novafoundation.nova.feature_push_notifications.presentation.welcome.PushWelcomeViewModel
|
||||
|
||||
@Module(includes = [ViewModelModule::class])
|
||||
class PushWelcomeModule {
|
||||
|
||||
@Provides
|
||||
fun providePermissionAsker(
|
||||
permissionsAskerFactory: PermissionsAskerFactory,
|
||||
fragment: Fragment,
|
||||
router: PushNotificationsRouter
|
||||
) = permissionsAskerFactory.createReturnable(fragment, router)
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@ViewModelKey(PushWelcomeViewModel::class)
|
||||
fun provideViewModel(
|
||||
router: PushNotificationsRouter,
|
||||
interactor: PushNotificationsInteractor,
|
||||
permissionsAsker: PermissionsAsker.Presentation,
|
||||
resourceManager: ResourceManager,
|
||||
welcomePushNotificationsInteractor: WelcomePushNotificationsInteractor,
|
||||
appLinksProvider: AppLinksProvider
|
||||
): ViewModel {
|
||||
return PushWelcomeViewModel(
|
||||
router,
|
||||
interactor,
|
||||
welcomePushNotificationsInteractor,
|
||||
permissionsAsker,
|
||||
resourceManager,
|
||||
appLinksProvider
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideViewModelCreator(
|
||||
fragment: Fragment,
|
||||
viewModelFactory: ViewModelProvider.Factory
|
||||
): PushWelcomeViewModel {
|
||||
return ViewModelProvider(fragment, viewModelFactory).get(PushWelcomeViewModel::class.java)
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<View
|
||||
style="@style/Widget.Nova.Puller"
|
||||
android:layout_marginTop="6dp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/actionNotAllowedImage"
|
||||
android:layout_width="88dp"
|
||||
android:layout_height="88dp"
|
||||
android:layout_marginTop="20dp"
|
||||
app:srcCompat="@drawable/ic_bell" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actionNotAllowedTitle"
|
||||
style="@style/TextAppearance.NovaFoundation.SemiBold.Title3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/mutisigs_push_notifications_warning_title"
|
||||
android:textColor="@color/text_primary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actionNotAllowedSubtitle"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/mutisigs_push_notifications_warning_message"
|
||||
android:textColor="@color/text_secondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="16dp">
|
||||
|
||||
<io.novafoundation.nova.common.view.PrimaryButtonV2
|
||||
android:id="@+id/enableMultisigPushesNotNow"
|
||||
style="@style/Widget.Nova.MaterialButton.Secondary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/common_not_now" />
|
||||
|
||||
<io.novafoundation.nova.common.view.PrimaryButtonV2
|
||||
android:id="@+id/enableMultisigPushesEnable"
|
||||
style="@style/Widget.Nova.MaterialButton.Primary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/common_enable" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<io.novafoundation.nova.common.view.Toolbar
|
||||
android:id="@+id/pushGovernanceToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerVisible="false"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:textRight="@string/common_clear"
|
||||
app:titleText="@string/common_governance" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/pushGovernanceList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="24dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pushGovernanceToolbar" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pushGovernanceProgress"
|
||||
style="@style/Widget.Nova.ProgressBar.Indeterminate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pushGovernanceToolbar" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<io.novafoundation.nova.common.view.Toolbar
|
||||
android:id="@+id/pushMultisigsToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerVisible="false"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:titleText="@string/push_settings_multisig_operations" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushMultisigSettingsSwitcher"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/push_settings_enable" />
|
||||
|
||||
</io.novafoundation.nova.common.view.settings.SettingsGroupView>
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="24dp">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushMultisigInitiatingSwitcher"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/multisig_push_settings_initial_switcher" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushMultisigApprovalSwitcher"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/multisig_push_settings_approval_switcher" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushMultisigExecutedSwitcher"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/multisig_push_settings_executed_switcher" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushMultisigRejectedSwitcher"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/multisig_push_settings_rejected_switcher" />
|
||||
</io.novafoundation.nova.common.view.settings.SettingsGroupView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<io.novafoundation.nova.common.view.Toolbar
|
||||
android:id="@+id/pushSettingsToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerVisible="false"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:textRight="@string/common_save"
|
||||
app:titleText="@string/common_push_notifications" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="24dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pushSettingsToolbar">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushSettingsEnable"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_notifications_outline"
|
||||
app:title="@string/push_settings_enable" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsItemView
|
||||
android:id="@+id/pushSettingsWallets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_wallet_outline"
|
||||
app:title="@string/push_settings_wallets"
|
||||
tools:settingValue="0" />
|
||||
|
||||
</io.novafoundation.nova.common.view.settings.SettingsGroupView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushSettingsNoSelectedWallets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/push_settings_no_selected_wallets"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupHeaderView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_general" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushSettingsAnnouncements"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/push_settings_announcements" />
|
||||
|
||||
</io.novafoundation.nova.common.view.settings.SettingsGroupView>
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupHeaderView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/push_settings_balances" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushSettingsSentTokens"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/push_settings_sent_tokens" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushSettingsReceivedTokens"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/push_settings_received_tokens" />
|
||||
|
||||
</io.novafoundation.nova.common.view.settings.SettingsGroupView>
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupHeaderView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/push_settings_others" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsItemView
|
||||
android:id="@+id/pushSettingsMultisigs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/push_settings_multisig_operations"
|
||||
tools:settingValue="On" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsItemView
|
||||
android:id="@+id/pushSettingsGovernance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/common_governance"
|
||||
tools:settingValue="On" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsItemView
|
||||
android:id="@+id/pushSettingsStakingRewards"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/push_settings_staking_rewards"
|
||||
tools:settingValue="On" />
|
||||
|
||||
</io.novafoundation.nova.common.view.settings.SettingsGroupView>
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="Powered by"
|
||||
android:textColor="@color/text_secondary" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:src="@drawable/ic_web3_alert_icon" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<io.novafoundation.nova.common.view.Toolbar
|
||||
android:id="@+id/pushStakingToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerVisible="false"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:textRight="@string/common_clear"
|
||||
app:titleText="@string/push_settings_staking_rewards" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/pushStakingList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="24dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pushStakingToolbar" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pushStakingProgress"
|
||||
style="@style/Widget.Nova.ProgressBar.Indeterminate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pushStakingToolbar" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<io.novafoundation.nova.common.view.Toolbar
|
||||
android:id="@+id/pushWelcomeToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerVisible="false"
|
||||
app:homeButtonIcon="@drawable/ic_close"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pushWelcomeImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:src="@drawable/ic_bell"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pushWelcomeToolbar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushWelcomeTitle"
|
||||
style="@style/TextAppearance.NovaFoundation.Bold.Title2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:text="@string/push_welcome_title"
|
||||
android:textColor="@color/text_primary"
|
||||
app:layout_constraintTop_toBottomOf="@id/pushWelcomeImage" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushWelcomeMessage"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:text="@string/push_welcome_message"
|
||||
android:textColor="@color/text_secondary"
|
||||
app:layout_constraintTop_toBottomOf="@id/pushWelcomeTitle" />
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/pushWelcomeMessage">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginHorizontal="48dp"
|
||||
app:cardBackgroundColor="@color/notification_preview_3_layer_background"
|
||||
app:cardCornerRadius="18dp"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/notification_preview_2_layer_background"
|
||||
app:cardCornerRadius="18dp"
|
||||
app:cardElevation="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/pushWelcomeTemplate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:cardBackgroundColor="@color/notification_preview_1_layer_background"
|
||||
app:cardCornerRadius="18dp"
|
||||
app:cardElevation="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pushWelcomeLogo"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:src="@drawable/ic_pezkuwi_logo"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Caption2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/push_template_app_name_and_time"
|
||||
android:textColor="@color/chip_text"
|
||||
app:layout_constraintBottom_toBottomOf="@id/pushWelcomeLogo"
|
||||
app:layout_constraintStart_toEndOf="@id/pushWelcomeLogo"
|
||||
app:layout_constraintTop_toTopOf="@id/pushWelcomeLogo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushWelcomeTemplateTitle"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/push_staking_reward_single_account_title"
|
||||
android:textColor="@color/text_primary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pushWelcomeLogo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushWelcomeTemplateMessage"
|
||||
style="@style/TextAppearance.NovaFoundation.Regular.Caption1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="@string/push_template_message"
|
||||
android:textColor="@color/chip_text"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pushWelcomeTemplateTitle" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<io.novafoundation.nova.common.view.PrimaryButton
|
||||
android:id="@+id/pushWelcomeEnableButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/push_welcome_enable_button"
|
||||
app:appearance="primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/pushWelcomeCancelButton" />
|
||||
|
||||
<io.novafoundation.nova.common.view.PrimaryButton
|
||||
android:id="@+id/pushWelcomeCancelButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/common_not_now"
|
||||
app:appearance="secondary"
|
||||
app:layout_constraintBottom_toTopOf="@id/pushWelcomeTermsAndConditions" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pushWelcomeTermsAndConditions"
|
||||
style="@style/TextAppearance.NovaFoundation.Body1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/x2"
|
||||
android:layout_marginEnd="@dimen/x2"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/push_welcome_terms_and_conditions"
|
||||
android:textColor="@color/text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushGovernanceItemState"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:icon="@drawable/ic_pezkuwi"
|
||||
tools:title="Polkdaot" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushGovernanceItemNewReferenda"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/push_governance_settings_new_referendum" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushGovernanceItemReferendumUpdate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/push_governance_settings_referendum_update" />
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsItemView
|
||||
android:id="@+id/pushGovernanceItemTracks"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/delegation_tracks" />
|
||||
|
||||
</io.novafoundation.nova.common.view.settings.SettingsGroupView>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<io.novafoundation.nova.common.view.settings.SettingsGroupView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<io.novafoundation.nova.common.view.settings.SettingsSwitcherView
|
||||
android:id="@+id/pushStakingItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:icon="@drawable/ic_pezkuwi"
|
||||
tools:title="Polkdaot" />
|
||||
|
||||
</io.novafoundation.nova.common.view.settings.SettingsGroupView>
|
||||
Reference in New Issue
Block a user