Add more polish to Badges.
* Better network error handling * Marking user cancellations so we don't annoy them * Manage Profile screen treatment.
This commit is contained in:
parent
17517cfc88
commit
1af15842cc
19 changed files with 207 additions and 70 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
|||
subscriber.currencyCode,
|
||||
levelUpdateOperation.idempotencyKey.serialize()
|
||||
).flatMap(ServiceResponse<EmptyResponse>::flattenResult).ignoreElement().andThen {
|
||||
SignalStore.donationsValues().clearUserManuallyCancelled()
|
||||
SignalStore.donationsValues().clearLevelOperation(levelUpdateOperation)
|
||||
it.onComplete()
|
||||
}.andThen {
|
||||
|
|
|
@ -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<List<Subscription>> = donationsService.subscriptionLevels.map { response ->
|
||||
response.result.transform { subscriptionLevels ->
|
||||
fun getSubscriptions(currency: Currency): Single<List<Subscription>> = donationsService.subscriptionLevels
|
||||
.flatMap(ServiceResponse<SubscriptionLevels>::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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,8 @@ class BoostFragment : DSLSettingsBottomSheetFragment(
|
|||
}
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
viewModel.refresh()
|
||||
|
||||
CurrencySelection.register(adapter)
|
||||
BadgePreview.register(adapter)
|
||||
Boost.register(adapter)
|
||||
|
|
|
@ -21,5 +21,6 @@ data class BoostState(
|
|||
READY,
|
||||
TOKEN_REQUEST,
|
||||
PAYMENT_PIPELINE,
|
||||
FAILURE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,8 @@ class SubscribeFragment : DSLSettingsFragment(
|
|||
}
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
viewModel.refresh()
|
||||
|
||||
BadgePreview.register(adapter)
|
||||
CurrencySelection.register(adapter)
|
||||
Subscription.register(adapter)
|
||||
|
|
|
@ -18,6 +18,7 @@ data class SubscribeState(
|
|||
READY,
|
||||
TOKEN_REQUEST,
|
||||
PAYMENT_PIPELINE,
|
||||
CANCELLING
|
||||
CANCELLING,
|
||||
FAILURE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Currency> = SignalStore.donationsValues().observableSubscriptionCurrency
|
||||
val allSubscriptions: Observable<List<Subscription>> = 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>): 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Event> events;
|
||||
private final RecipientForeverObserver observer;
|
||||
private final ManageProfileRepository repository;
|
||||
private final MutableLiveData<Optional<Badge>> 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<Optional<Badge>> getBadge() {
|
||||
return badge;
|
||||
}
|
||||
|
||||
public @NonNull LiveData<Event> 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
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/manage_profile_avatar_background"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:src="@drawable/circle_tintable"
|
||||
android:tint="@color/core_grey_05"
|
||||
|
@ -71,32 +71,45 @@
|
|||
app:layout_constraintStart_toStartOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintTop_toTopOf="@+id/manage_profile_avatar_background" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manage_profile_camera_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:layout_marginTop="56dp"
|
||||
android:background="@drawable/circle_tintable_padded"
|
||||
android:cropToPadding="false"
|
||||
android:elevation="4dp"
|
||||
android:padding="14dp"
|
||||
app:backgroundTint="@color/camera_icon_background_tint"
|
||||
app:layout_constraintStart_toStartOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintTop_toTopOf="@+id/manage_profile_avatar_background"
|
||||
app:srcCompat="@drawable/ic_camera_24" />
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/manage_profile_badge"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginStart="44dp"
|
||||
android:layout_marginTop="52dp"
|
||||
app:badge_size="large"
|
||||
app:layout_constraintStart_toStartOf="@+id/manage_profile_avatar_background"
|
||||
app:layout_constraintTop_toTopOf="@+id/manage_profile_avatar_background" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/manage_profile_name_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingStart="@dimen/dsl_settings_gutter"
|
||||
android:paddingEnd="@dimen/dsl_settings_gutter"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?selectableItemBackground"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_avatar">
|
||||
<TextView
|
||||
android:id="@+id/manage_profile_edit_photo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dsl_settings_gutter"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="@dimen/dsl_settings_gutter"
|
||||
android:background="@drawable/rounded_rectangle_tertiary"
|
||||
android:paddingStart="21dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingEnd="21dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:text="@string/ManageProfileFragment__edit_photo"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body2.Bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_badge" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/manage_profile_name_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="?selectableItemBackground"
|
||||
android:paddingStart="@dimen/dsl_settings_gutter"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="@dimen/dsl_settings_gutter"
|
||||
android:paddingBottom="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/manage_profile_edit_photo">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/manage_profile_name_icon"
|
||||
|
|
|
@ -815,6 +815,7 @@
|
|||
<string name="ManageProfileFragment_your_username">Your username</string>
|
||||
<string name="ManageProfileFragment_failed_to_set_avatar">Failed to set avatar</string>
|
||||
<string name="ManageProfileFragment_badges">Badges</string>
|
||||
<string name="ManageProfileFragment__edit_photo">Edit photo</string>
|
||||
|
||||
<!-- ManageRecipientActivity -->
|
||||
<string name="ManageRecipientActivity_no_groups_in_common">No groups in common</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue