Skip to content

Commit

Permalink
Merge pull request #130 from muun/51-release-branch
Browse files Browse the repository at this point in the history
Apollo: Release source code for 51
  • Loading branch information
acrespo authored Jul 31, 2023
2 parents 595d0c0 + 222bee0 commit 69bacfb
Show file tree
Hide file tree
Showing 66 changed files with 1,791 additions and 658 deletions.
10 changes: 10 additions & 0 deletions android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ follow [https://changelog.md/](https://changelog.md/) guidelines.

## [Unreleased]

## [51] - 2023-07-28

### ADDED

- Support for requesting Push Notification Runtime Permission. Option to skip to receive on-chain
but required to receive LN Payments (to avoid payment failures).

### CHANGED
- Upgraded compiledSdkVersion and targetSdkVersion to 33

## [50.16] - 2023-07-13

### FIXED
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.muun.apollo.data.preferences

import android.content.Context
import io.muun.apollo.data.preferences.rx.Preference
import javax.inject.Inject

class NotificationPermissionDeniedRepository @Inject constructor(
context: Context,
repositoryRegistry: RepositoryRegistry,
) : BaseRepository(context, repositoryRegistry) {

companion object {
private const val NOTIFICATION_PERMISSION_DENIED = "notification_permission_denied"
}

private val permissionDeniedPref: Preference<Boolean> = rxSharedPreferences
.getBoolean(NOTIFICATION_PERMISSION_DENIED)

override fun getFileName() =
"notification_permission_denied"

fun setHasPreviouslyDeniedNotificationPermission() {
permissionDeniedPref.set(true)
}

fun hasPreviouslyDeniedNotificationPermission(): Boolean =
permissionDeniedPref.get()!!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.muun.apollo.data.preferences

import android.content.Context
import io.muun.apollo.data.preferences.rx.Preference
import javax.inject.Inject

class NotificationPermissionSkippedRepository @Inject constructor(
context: Context,
repositoryRegistry: RepositoryRegistry,
) : BaseRepository(context, repositoryRegistry) {

companion object {
private const val NOTIFICATION_PERMISSION_SKIPPED = "notification_permission_skipped"
}

private val permissionSkippedPref: Preference<Boolean> = rxSharedPreferences
.getBoolean(NOTIFICATION_PERMISSION_SKIPPED)

override fun getFileName() =
"notification_permission_skipped"

fun store(permissionSkipped: Boolean) {
permissionSkippedPref.set(permissionSkipped)
}

fun get(): Boolean =
permissionSkippedPref.get()!!
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.muun.apollo.data.preferences

import io.muun.apollo.data.preferences.permission.NotificationPermissionStateRepository

/**
* Welcome to RepositoryRegistry! This is class is meant to be a centralized, singleton registry
* for all preferences repositories. Why do we need it? The idea is to invert the way of doing this
Expand Down Expand Up @@ -41,20 +43,27 @@ class RepositoryRegistry {
FeaturesRepository::class.java,
AppVersionRepository::class.java,
PlayIntegrityNonceRepository::class.java,
NotificationPermissionStateRepository::class.java,
NotificationPermissionDeniedRepository::class.java,
NotificationPermissionSkippedRepository::class.java
)

// Notable exceptions:
// - FirebaseInstallationIdRepository
// - NightModeRepository
// - SchemaVersionRepository
// - NotificationPermissionDeniedRepository
// - NotificationPermissionSkippedRepository
// They get special treatment and are not wiped on logout to avoid problems.
// Update: technically SchemaVersionRepository could be wiped with no issues, but we feel
// its more clear and clean to keep it and avoid wiping it (there's no privacy or security
// issues).
private val logoutSurvivorRepositories: Set<Class<out BaseRepository>> = setOf(
FirebaseInstallationIdRepository::class.java,
NightModeRepository::class.java,
SchemaVersionRepository::class.java
SchemaVersionRepository::class.java,
NotificationPermissionDeniedRepository::class.java,
NotificationPermissionSkippedRepository::class.java
)

private val loadedRepositories: MutableSet<BaseRepository> = mutableSetOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import io.muun.apollo.data.preferences.stored.StoredEkVerificationCodes;
import io.muun.apollo.data.serialization.SerializationUtils;
import io.muun.apollo.domain.model.BitcoinUnit;
import io.muun.apollo.domain.model.ContactsPermissionState;
import io.muun.apollo.domain.model.EmergencyKitExport;
import io.muun.apollo.domain.model.PermissionState;
import io.muun.apollo.domain.model.user.EmergencyKit;
import io.muun.apollo.domain.model.user.User;
import io.muun.apollo.domain.model.user.UserPhoneNumber;
Expand Down Expand Up @@ -64,7 +64,7 @@ public class UserRepository extends BaseRepository {

private final Preference<String> passwordChangeAuthorizedUuidPreference;

private final Preference<ContactsPermissionState> conctactsPermissionStatePreference;
private final Preference<PermissionState> conctactsPermissionStatePreference;

private final Preference<Boolean> initialSyncCompletedPreference;

Expand Down Expand Up @@ -103,8 +103,8 @@ public UserRepository(Context context, RepositoryRegistry repositoryRegistry) {

conctactsPermissionStatePreference = rxSharedPreferences.getEnum(
CONTACTS_PERMISSION_STATE_KEY,
ContactsPermissionState.DENIED,
ContactsPermissionState.class
PermissionState.DENIED,
PermissionState.class
);

initialSyncCompletedPreference = rxSharedPreferences.getBoolean(
Expand Down Expand Up @@ -308,18 +308,18 @@ public void authorizePasswordChange(String uuid) {
/**
* Save contacts permission state.
*/
public void storeContactsPermissionState(ContactsPermissionState state) {
public void storeContactsPermissionState(PermissionState state) {
conctactsPermissionStatePreference.set(state);
}

public ContactsPermissionState getContactsPermissionState() {
public PermissionState getContactsPermissionState() {
return conctactsPermissionStatePreference.get();
}

/**
* Get an Observable to observe changes to the contacts permission preference.
*/
public Observable<ContactsPermissionState> watchContactsPermissionState() {
public Observable<PermissionState> watchContactsPermissionState() {
return conctactsPermissionStatePreference.asObservable();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.muun.apollo.data.preferences.permission

import android.content.Context
import io.muun.apollo.data.preferences.BaseRepository
import io.muun.apollo.data.preferences.RepositoryRegistry
import io.muun.apollo.data.preferences.rx.Preference
import io.muun.apollo.domain.model.PermissionState
import javax.inject.Inject

class NotificationPermissionStateRepository @Inject constructor(
context: Context,
repositoryRegistry: RepositoryRegistry,
) : BaseRepository(context, repositoryRegistry) {

companion object {
private const val NOTIFICATION_PERMISSION_STATE = "notification_permission_state"
}

private val permissionStatePref: Preference<PermissionState> = rxSharedPreferences.getEnum(
NOTIFICATION_PERMISSION_STATE,
PermissionState.NOT_DETERMINED,
PermissionState::class.java
)

override fun getFileName() =
"notification_permission_state"

fun store(permissionState: PermissionState) {
permissionStatePref.set(permissionState)
}

fun get(): PermissionState =
permissionStatePref.get()!!
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import io.muun.apollo.domain.action.base.AsyncAction2;
import io.muun.apollo.domain.action.base.AsyncActionStore;
import io.muun.apollo.domain.action.user.UpdateProfilePictureAction;
import io.muun.apollo.domain.model.ContactsPermissionState;
import io.muun.apollo.domain.model.FeedbackCategory;
import io.muun.apollo.domain.model.PermissionState;
import io.muun.apollo.domain.model.user.User;
import io.muun.apollo.domain.model.user.UserPhoneNumber;
import io.muun.apollo.domain.model.user.UserProfile;
Expand Down Expand Up @@ -70,26 +70,26 @@ public UserActions(AsyncActionStore asyncActionStore,
this.updateProfilePictureAction = updateProfilePictureAction;

this.createPhoneAction = asyncActionStore
.get("user/createPhone", this::createPhone);
.get("user/createPhone", this::createPhone);

this.resendVerificationCodeAction = asyncActionStore
.get("user/resendCode", houstonClient::resendVerificationCode);
.get("user/resendCode", houstonClient::resendVerificationCode);

this.confirmPhoneAction = asyncActionStore
.get("user/confirm-phone", this::confirmPhone);
.get("user/confirm-phone", this::confirmPhone);

this.createProfileAction = asyncActionStore
.get("user/createProfile", this::createProfile);
.get("user/createProfile", this::createProfile);


this.updateUsernameAction = asyncActionStore
.get("user/editUsername", this::updateUsername);
.get("user/editUsername", this::updateUsername);

this.updatePrimaryCurrencyAction = asyncActionStore
.get("user/editPrimaryCurrency", this::updatePrimaryCurrency);
.get("user/editPrimaryCurrency", this::updatePrimaryCurrency);

this.submitFeedbackAction = asyncActionStore
.get("user/submitFeedbackAction", this::submitFeedback);
.get("user/submitFeedbackAction", this::submitFeedback);

this.notifyLogoutAction = asyncActionStore.get(NOTIFY_LOGOUT_ACTION, this::notifyLogout);
}
Expand Down Expand Up @@ -175,40 +175,7 @@ public void resetPhoneNumber() {
* Save user's choice to never be asked again for Contacts permission.
*/
public void reportContactsPermissionNeverAskAgain() {
userRepository.storeContactsPermissionState(ContactsPermissionState.PERMANENTLY_DENIED);
}

/**
* Update value of user preference tracking Contacts permission state.
* This receives as param the result of asking if permission is granted, that's why it is a
* boolean: true for GRANTED, false for DENIED.
*
* <p>Helpful: table of values
*
* <p>if current_state is GRANTED && new_value is GRANTED => GRANTED
* if current_state is GRANTED && new_value is DENIED => DENIED
* if current_state is DENIED && new_value is GRANTED => GRANTED
* if current_state is DENIED && new_value is DENIED => DENIED
* if current_state is PERMANENTLY_DENIED && new_value is GRANTED => GRANTED
* if current_state is PERMANENTLY_DENIED && new_value is DENIED => PERMANENTLY_DENIED
*/
public void updateContactsPermissionState(boolean granted) {

final ContactsPermissionState prevState = userRepository.getContactsPermissionState();

if (granted) {
// if we detect a permission grant (via android settings) => trigger sync phone contacts
if (isLoggedIn() && prevState != ContactsPermissionState.GRANTED) {
contactActions.initialSyncPhoneContactsAction.run();
}

userRepository.storeContactsPermissionState(ContactsPermissionState.GRANTED);
return;
}

if (prevState != ContactsPermissionState.PERMANENTLY_DENIED) {
userRepository.storeContactsPermissionState(ContactsPermissionState.DENIED);
}
userRepository.storeContactsPermissionState(PermissionState.PERMANENTLY_DENIED);
}

private Observable<Void> notifyLogout(String jwtToken) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import io.muun.apollo.domain.action.operation.ResolveLnInvoiceAction;
import io.muun.apollo.domain.action.operation.ResolveOperationUriAction;
import io.muun.apollo.domain.action.operation.SubmitPaymentAction;
import io.muun.apollo.domain.action.permission.UpdateContactsPermissionStateAction;
import io.muun.apollo.domain.action.realtime.FetchRealTimeDataAction;
import io.muun.apollo.domain.action.session.CreateLoginSessionAction;
import io.muun.apollo.domain.action.session.LogInAction;
Expand Down Expand Up @@ -120,4 +121,6 @@ public interface ActionComponent {
AddEmergencyKitMetadataAction addEmergencyKitMetadata();

StartRecoveryCodeSetupAction startRecoveryCodeSetupAction();

UpdateContactsPermissionStateAction updateContactsPermissionStateAction();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.muun.apollo.domain.action.notification

import io.muun.apollo.data.preferences.NotificationPermissionDeniedRepository
import javax.inject.Inject

class SetNotificationPermissionDeniedAction @Inject constructor(
private val notificationPermissionDeniedRepository: NotificationPermissionDeniedRepository,
) {

fun run() {
return notificationPermissionDeniedRepository.setHasPreviouslyDeniedNotificationPermission()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.muun.apollo.domain.action.notification

import io.muun.apollo.data.preferences.NotificationPermissionSkippedRepository
import javax.inject.Inject

class SetNotificationPermissionSkippedAction @Inject constructor(
private val notificationPermissionSkippedRepository: NotificationPermissionSkippedRepository,
) {

fun run() {
return notificationPermissionSkippedRepository.store(true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.muun.apollo.domain.action.permission

import io.muun.apollo.data.preferences.permission.NotificationPermissionStateRepository
import io.muun.apollo.domain.model.PermissionState
import javax.inject.Inject

class SetNotificationPermissionNeverAskAgainAction @Inject constructor(
private val notificationPermissionStateRepository: NotificationPermissionStateRepository,
) {

fun run() {
return notificationPermissionStateRepository.store(PermissionState.PERMANENTLY_DENIED)
}
}
Loading

0 comments on commit 69bacfb

Please sign in to comment.