diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt index a16d8a4eb9..fbc051557f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/self/overview/BadgesOverviewViewModel.kt @@ -51,9 +51,14 @@ class BadgesOverviewViewModel( } else { Optional.absent() } - }.subscribeBy { badgeId -> - store.update { it.copy(fadedBadgeId = badgeId.orNull()) } - } + }.subscribeBy( + onSuccess = { badgeId -> + store.update { it.copy(fadedBadgeId = badgeId.orNull()) } + }, + onError = { throwable -> + Log.w(TAG, "Could not retrieve data from server", throwable) + } + ) } fun setDisplayBadgesOnProfile(displayBadgesOnProfile: Boolean) { @@ -82,4 +87,8 @@ class BadgesOverviewViewModel( return requireNotNull(modelClass.cast(BadgesOverviewViewModel(badgeRepository, subscriptionsRepository))) } } + + companion object { + private val TAG = Log.tag(BadgesOverviewViewModel::class.java) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt index 0e1688fbaa..111d04f409 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.reactivex.rxjava3.kotlin.subscribeBy +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.components.settings.app.subscription.SubscriptionsRepository import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData import org.thoughtcrime.securesms.keyvalue.SignalStore @@ -37,6 +38,9 @@ class AppSettingsViewModel(private val subscriptionsRepository: SubscriptionsRep subscriptionsRepository.getActiveSubscription().subscribeBy( onSuccess = { subscription -> store.update { it.copy(hasActiveSubscription = subscription.isActive) } }, + onError = { throwable -> + Log.w(TAG, "Could not load active subscription", throwable) + } ) } @@ -45,4 +49,8 @@ class AppSettingsViewModel(private val subscriptionsRepository: SubscriptionsRep return modelClass.cast(AppSettingsViewModel(subscriptionsRepository)) as T } } + + companion object { + private val TAG = Log.tag(AppSettingsViewModel::class.java) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPaymentRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPaymentRepository.kt index feaa5338a8..ea71ce8418 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPaymentRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationPaymentRepository.kt @@ -141,6 +141,7 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet subscriber.currencyCode, levelUpdateOperation.idempotencyKey.serialize() ).flatMap(ServiceResponse::flattenResult).ignoreElement().andThen { + SignalStore.donationsValues().clearUserManuallyCancelled() SignalStore.donationsValues().clearLevelOperation(levelUpdateOperation) it.onComplete() }.andThen { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/SubscriptionsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/SubscriptionsRepository.kt index 17b743434e..9e34dc624e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/SubscriptionsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/SubscriptionsRepository.kt @@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.subscription.Subscription import org.whispersystems.signalservice.api.services.DonationsService import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription +import org.whispersystems.signalservice.api.subscriptions.SubscriptionLevels import org.whispersystems.signalservice.internal.ServiceResponse import java.util.Currency @@ -26,8 +27,9 @@ class SubscriptionsRepository(private val donationsService: DonationsService) { } } - fun getSubscriptions(currency: Currency): Single> = donationsService.subscriptionLevels.map { response -> - response.result.transform { subscriptionLevels -> + fun getSubscriptions(currency: Currency): Single> = donationsService.subscriptionLevels + .flatMap(ServiceResponse::flattenResult) + .map { subscriptionLevels -> subscriptionLevels.levels.map { (code, level) -> Subscription( id = code, @@ -38,6 +40,5 @@ class SubscriptionsRepository(private val donationsService: DonationsService) { }.sortedBy { it.level } - }.or(emptyList()) - } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostFragment.kt index 339935b061..fa3929d06f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostFragment.kt @@ -58,6 +58,8 @@ class BoostFragment : DSLSettingsBottomSheetFragment( } override fun bindAdapter(adapter: DSLSettingsAdapter) { + viewModel.refresh() + CurrencySelection.register(adapter) BadgePreview.register(adapter) Boost.register(adapter) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostState.kt index 1e592acd55..c84462154b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostState.kt @@ -21,5 +21,6 @@ data class BoostState( READY, TOKEN_REQUEST, PAYMENT_PIPELINE, + FAILURE } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostViewModel.kt index cd17f9e47c..955faf53b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostViewModel.kt @@ -11,6 +11,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.subjects.PublishSubject +import org.signal.core.util.logging.Log import org.signal.core.util.money.FiatMoney import org.signal.donations.GooglePayApi import org.thoughtcrime.securesms.badges.models.Badge @@ -40,24 +41,33 @@ class BoostViewModel( disposables.clear() } - init { + fun refresh() { + disposables.clear() + val currencyObservable = SignalStore.donationsValues().observableBoostCurrency val boosts = currencyObservable.flatMapSingle { boostRepository.getBoosts(it) } val boostBadge = boostRepository.getBoostBadge() - disposables += Observable.combineLatest(boosts, boostBadge.toObservable()) { - boostList, badge -> + disposables += Observable.combineLatest(boosts, boostBadge.toObservable()) { boostList, badge -> BoostInfo(boostList, boostList[2], badge) - }.subscribe { info -> - store.update { - it.copy( - boosts = info.boosts, - selectedBoost = if (it.selectedBoost in info.boosts) it.selectedBoost else info.defaultBoost, - boostBadge = it.boostBadge ?: info.boostBadge, - stage = if (it.stage == BoostState.Stage.INIT) BoostState.Stage.READY else it.stage - ) + }.subscribeBy( + onNext = { info -> + store.update { + it.copy( + boosts = info.boosts, + selectedBoost = if (it.selectedBoost in info.boosts) it.selectedBoost else info.defaultBoost, + boostBadge = it.boostBadge ?: info.boostBadge, + stage = if (it.stage == BoostState.Stage.INIT || it.stage == BoostState.Stage.FAILURE) BoostState.Stage.READY else it.stage + ) + } + }, + onError = { throwable -> + Log.w(TAG, "Could not load boost information", throwable) + store.update { + it.copy(stage = BoostState.Stage.FAILURE) + } } - } + ) disposables += donationPaymentRepository.isGooglePayAvailable().subscribeBy( onComplete = { store.update { it.copy(isGooglePayAvailable = true) } }, @@ -170,4 +180,8 @@ class BoostViewModel( return modelClass.cast(BoostViewModel(boostRepository, donationPaymentRepository, fetchTokenRequestCode))!! } } + + companion object { + private val TAG = Log.tag(BoostViewModel::class.java) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt index 64e81e5e6d..66146c4fa1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/manage/ManageDonationsViewModel.kt @@ -10,6 +10,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.subjects.PublishSubject +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.components.settings.app.subscription.SubscriptionsRepository import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient @@ -66,9 +67,14 @@ class ManageDonationsViewModel( } ) - disposables += subscriptionsRepository.getSubscriptions(SignalStore.donationsValues().getSubscriptionCurrency()).subscribeBy { subs -> - store.update { it.copy(availableSubscriptions = subs) } - } + disposables += subscriptionsRepository.getSubscriptions(SignalStore.donationsValues().getSubscriptionCurrency()).subscribeBy( + onSuccess = { subs -> + store.update { it.copy(availableSubscriptions = subs) } + }, + onError = { + Log.w(TAG, "Error retrieving subscriptions data", it) + } + ) } class Factory( @@ -78,4 +84,8 @@ class ManageDonationsViewModel( return modelClass.cast(ManageDonationsViewModel(subscriptionsRepository))!! } } + + companion object { + private val TAG = Log.tag(ManageDonationsViewModel::class.java) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/CurrencySelection.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/CurrencySelection.kt index 95f1be1ff8..8ead1a2593 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/CurrencySelection.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/models/CurrencySelection.kt @@ -39,9 +39,10 @@ data class CurrencySelection( override fun bind(model: Model) { spinner.text = model.currencySelection.selectedCurrencyCode - if (model.isEnabled) { - itemView.setOnClickListener { model.onClick() } - } + itemView.setOnClickListener { model.onClick() } + + itemView.isEnabled = model.isEnabled + itemView.isClickable = model.isEnabled } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeFragment.kt index d25493bad5..74196d6179 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeFragment.kt @@ -60,6 +60,8 @@ class SubscribeFragment : DSLSettingsFragment( } override fun bindAdapter(adapter: DSLSettingsAdapter) { + viewModel.refresh() + BadgePreview.register(adapter) CurrencySelection.register(adapter) Subscription.register(adapter) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeState.kt index b03dd4d434..6ee78fffdc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeState.kt @@ -18,6 +18,7 @@ data class SubscribeState( READY, TOKEN_REQUEST, PAYMENT_PIPELINE, - CANCELLING + CANCELLING, + FAILURE } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt index 8b62f59152..bc794b2992 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/subscribe/SubscribeViewModel.kt @@ -12,6 +12,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.subjects.PublishSubject +import org.signal.core.util.logging.Log import org.signal.donations.GooglePayApi import org.thoughtcrime.securesms.components.settings.app.subscription.DonationEvent import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentRepository @@ -43,7 +44,9 @@ class SubscribeViewModel( disposables.clear() } - init { + fun refresh() { + disposables.clear() + val currency: Observable = SignalStore.donationsValues().observableSubscriptionCurrency val allSubscriptions: Observable> = currency.switchMapSingle { subscriptionsRepository.getSubscriptions(it) } refreshActiveSubscription() @@ -56,16 +59,19 @@ class SubscribeViewModel( } } - disposables += Observable.combineLatest(allSubscriptions, activeSubscriptionSubject, ::Pair).subscribe { (subs, active) -> - store.update { - it.copy( - subscriptions = subs, - selectedSubscription = it.selectedSubscription ?: resolveSelectedSubscription(active, subs), - activeSubscription = active, - stage = if (it.stage == SubscribeState.Stage.INIT) SubscribeState.Stage.READY else it.stage, - ) - } - } + disposables += Observable.combineLatest(allSubscriptions, activeSubscriptionSubject, ::Pair).subscribeBy( + onNext = { (subs, active) -> + store.update { + it.copy( + subscriptions = subs, + selectedSubscription = it.selectedSubscription ?: resolveSelectedSubscription(active, subs), + activeSubscription = active, + stage = if (it.stage == SubscribeState.Stage.INIT || it.stage == SubscribeState.Stage.FAILURE) SubscribeState.Stage.READY else it.stage, + ) + } + }, + onError = this::handleSubscriptionDataLoadFailure + ) disposables += donationPaymentRepository.isGooglePayAvailable().subscribeBy( onComplete = { store.update { it.copy(isGooglePayAvailable = true) } }, @@ -77,10 +83,20 @@ class SubscribeViewModel( } } + private fun handleSubscriptionDataLoadFailure(throwable: Throwable) { + Log.w(TAG, "Could not load subscription data", throwable) + store.update { + it.copy(stage = SubscribeState.Stage.FAILURE) + } + } + fun refreshActiveSubscription() { subscriptionsRepository .getActiveSubscription() - .subscribeBy { activeSubscriptionSubject.onNext(it) } + .subscribeBy( + onSuccess = { activeSubscriptionSubject.onNext(it) }, + onError = { activeSubscriptionSubject.onNext(ActiveSubscription(null)) } + ) } private fun resolveSelectedSubscription(activeSubscription: ActiveSubscription, subscriptions: List): Subscription? { @@ -97,6 +113,7 @@ class SubscribeViewModel( onComplete = { eventPublisher.onNext(DonationEvent.SubscriptionCancelled) SignalStore.donationsValues().setLastEndOfPeriod(0L) + SignalStore.donationsValues().markUserManuallyCancelled() refreshActiveSubscription() store.update { it.copy(stage = SubscribeState.Stage.READY) } }, @@ -196,4 +213,8 @@ class SubscribeViewModel( return modelClass.cast(SubscribeViewModel(subscriptionsRepository, donationPaymentRepository, fetchTokenRequestCode))!! } } + + companion object { + private val TAG = Log.tag(SubscribeViewModel::class.java) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/thanks/ThanksForYourSupportBottomSheetDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/thanks/ThanksForYourSupportBottomSheetDialogFragment.kt index 51e01c99d7..dfec331018 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/thanks/ThanksForYourSupportBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/thanks/ThanksForYourSupportBottomSheetDialogFragment.kt @@ -14,6 +14,8 @@ import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieDrawable import com.google.android.material.button.MaterialButton import com.google.android.material.switchmaterial.SwitchMaterial +import io.reactivex.rxjava3.kotlin.subscribeBy +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.animation.AnimationCompleteListener import org.thoughtcrime.securesms.badges.BadgeImageView @@ -131,9 +133,17 @@ class ThanksForYourSupportBottomSheetDialogFragment : FixedRoundedCornerBottomSh val args = ThanksForYourSupportBottomSheetDialogFragmentArgs.fromBundle(requireArguments()) if (controlState == ControlState.DISPLAY) { - badgeRepository.setVisibilityForAllBadges(controlChecked).subscribe() + badgeRepository.setVisibilityForAllBadges(controlChecked).subscribeBy( + onError = { + Log.w(TAG, "Failure while updating badge visibility", it) + } + ) } else if (controlChecked) { - badgeRepository.setFeaturedBadge(args.badge).subscribe() + badgeRepository.setFeaturedBadge(args.badge).subscribeBy( + onError = { + Log.w(TAG, "Failure while updating featured badge", it) + } + ) } if (args.isBoost) { @@ -151,4 +161,8 @@ class ThanksForYourSupportBottomSheetDialogFragment : FixedRoundedCornerBottomSh private fun presentSubscriptionCopy() { heading.setText(R.string.SubscribeThanksForYourSupportBottomSheetDialogFragment__thanks_for_your_support) } + + companion object { + private val TAG = Log.tag(ThanksForYourSupportBottomSheetDialogFragment::class.java) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 2948bd80aa..a0f9652395 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -328,7 +328,10 @@ public class ConversationListFragment extends MainFragment implements ActionMode Badge expiredBadge = SignalStore.donationsValues().getExpiredBadge(); if (expiredBadge != null) { SignalStore.donationsValues().setExpiredBadge(null); - ExpiredBadgeBottomSheetDialogFragment.show(expiredBadge, getParentFragmentManager()); + + if (expiredBadge.isBoost() || !SignalStore.donationsValues().isUserManuallyCancelled()) { + ExpiredBadgeBottomSheetDialogFragment.show(expiredBadge, getParentFragmentManager()); + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt index 57487c6e5c..c3fd93f2eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/DonationsValues.kt @@ -29,6 +29,7 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign private const val KEY_LAST_KEEP_ALIVE_LAUNCH = "donation.last.successful.ping" private const val KEY_LAST_END_OF_PERIOD = "donation.last.end.of.period" private const val EXPIRED_BADGE = "donation.expired.badge" + private const val USER_MANUALLY_CANCELLED = "donation.user.manually.cancelled" } override fun onFirstEverAppLaunch() = Unit @@ -188,6 +189,18 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign putLong(KEY_LAST_END_OF_PERIOD, timestamp) } + fun isUserManuallyCancelled(): Boolean { + return getBoolean(USER_MANUALLY_CANCELLED, false) + } + + fun markUserManuallyCancelled() { + putBoolean(USER_MANUALLY_CANCELLED, true) + } + + fun clearUserManuallyCancelled() { + remove(USER_MANUALLY_CANCELLED) + } + private fun dispatchLevelOperation() { levelUpdateOperationPublisher.onNext(Optional.fromNullable(getLevelOperation())) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileFragment.java index 8477a99456..538b0c477f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileFragment.java @@ -27,6 +27,8 @@ import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.avatar.Avatars; import org.thoughtcrime.securesms.avatar.picker.AvatarPickerFragment; +import org.thoughtcrime.securesms.badges.BadgeImageView; +import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.components.emoji.EmojiUtil; import org.thoughtcrime.securesms.mediasend.Media; import org.thoughtcrime.securesms.profiles.ProfileName; @@ -34,6 +36,7 @@ import org.thoughtcrime.securesms.profiles.manage.ManageProfileViewModel.AvatarS import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.NameUtil; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; +import org.whispersystems.libsignal.util.guava.Optional; public class ManageProfileFragment extends LoggingFragment { @@ -53,6 +56,7 @@ public class ManageProfileFragment extends LoggingFragment { private TextView avatarInitials; private ImageView avatarBackground; private View badgesContainer; + private BadgeImageView badgeView; private ManageProfileViewModel viewModel; @@ -76,11 +80,14 @@ public class ManageProfileFragment extends LoggingFragment { this.avatarInitials = view.findViewById(R.id.manage_profile_avatar_initials); this.avatarBackground = view.findViewById(R.id.manage_profile_avatar_background); this.badgesContainer = view.findViewById(R.id.manage_profile_badges_container); + this.badgeView = view.findViewById(R.id.manage_profile_badge); initializeViewModel(); this.toolbar.setNavigationOnClickListener(v -> requireActivity().finish()); - this.avatarView.setOnClickListener(v -> onAvatarClicked()); + + View editAvatar = view.findViewById(R.id.manage_profile_edit_photo); + editAvatar.setOnClickListener(v -> onEditAvatarClicked()); this.profileNameContainer.setOnClickListener(v -> { Navigation.findNavController(v).navigate(ManageProfileFragmentDirections.actionManageProfileName()); @@ -126,6 +133,7 @@ public class ManageProfileFragment extends LoggingFragment { viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvent); viewModel.getAbout().observe(getViewLifecycleOwner(), this::presentAbout); viewModel.getAboutEmoji().observe(getViewLifecycleOwner(), this::presentAboutEmoji); + viewModel.getBadge().observe(getViewLifecycleOwner(), this::presentBadge); if (viewModel.shouldShowUsername()) { viewModel.getUsername().observe(getViewLifecycleOwner(), this::presentUsername); @@ -217,6 +225,10 @@ public class ManageProfileFragment extends LoggingFragment { } } + private void presentBadge(@NonNull Optional badge) { + badgeView.setBadge(badge.orNull()); + } + private void presentEvent(@NonNull ManageProfileViewModel.Event event) { switch (event) { case AVATAR_DISK_FAILURE: @@ -228,7 +240,7 @@ public class ManageProfileFragment extends LoggingFragment { } } - private void onAvatarClicked() { + private void onEditAvatarClicked() { Navigation.findNavController(requireView()).navigate(ManageProfileFragmentDirections.actionManageProfileFragmentToAvatarPicker(null, null)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileViewModel.java index 8c984009a0..c9e6f01e86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/ManageProfileViewModel.java @@ -12,6 +12,7 @@ import androidx.lifecycle.ViewModelProvider; import org.signal.core.util.StreamUtil; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.badges.models.Badge; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.mediasend.Media; @@ -20,9 +21,11 @@ import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; +import org.thoughtcrime.securesms.util.DefaultValueLiveData; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.SingleLiveEvent; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; +import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.util.StreamDetails; import java.io.IOException; @@ -42,6 +45,7 @@ class ManageProfileViewModel extends ViewModel { private final SingleLiveEvent events; private final RecipientForeverObserver observer; private final ManageProfileRepository repository; + private final MutableLiveData> badge; private byte[] previousAvatar; @@ -53,6 +57,7 @@ class ManageProfileViewModel extends ViewModel { this.aboutEmoji = new MutableLiveData<>(); this.events = new SingleLiveEvent<>(); this.repository = new ManageProfileRepository(); + this.badge = new DefaultValueLiveData<>(Optional.absent()); this.observer = this::onRecipientChanged; this.avatarState = LiveDataUtil.combineLatest(Recipient.self().live().getLiveData(), internalAvatarState, (self, state) -> new AvatarState(state, self)); @@ -97,6 +102,10 @@ class ManageProfileViewModel extends ViewModel { return aboutEmoji; } + public @NonNull LiveData> getBadge() { + return badge; + } + public @NonNull LiveData getEvents() { return events; } @@ -159,6 +168,7 @@ class ManageProfileViewModel extends ViewModel { username.postValue(recipient.getUsername().orNull()); about.postValue(recipient.getAbout()); aboutEmoji.postValue(recipient.getAboutEmoji()); + badge.postValue(Optional.fromNullable(recipient.getFeaturedBadge())); } @Override diff --git a/app/src/main/res/layout/manage_profile_fragment.xml b/app/src/main/res/layout/manage_profile_fragment.xml index ad4e53a973..ceaf6c1bfb 100644 --- a/app/src/main/res/layout/manage_profile_fragment.xml +++ b/app/src/main/res/layout/manage_profile_fragment.xml @@ -23,8 +23,8 @@ - + - + + + Your username Failed to set avatar Badges + Edit photo No groups in common