mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-21 02:08:40 +00:00
Fix a bunch UX bugs for donor badges.
This commit is contained in:
@@ -44,8 +44,15 @@ class BadgeImageView @JvmOverloads constructor(
|
||||
fun setBadgeFromRecipient(recipient: Recipient?, glideRequests: GlideRequests) {
|
||||
if (recipient == null || recipient.badges.isEmpty()) {
|
||||
setBadge(null, glideRequests)
|
||||
} else if (recipient.isSelf) {
|
||||
val badge = recipient.featuredBadge
|
||||
if (badge == null || !badge.visible || badge.isExpired()) {
|
||||
setBadge(null, glideRequests)
|
||||
} else {
|
||||
setBadge(badge, glideRequests)
|
||||
}
|
||||
} else {
|
||||
setBadge(recipient.badges[0], glideRequests)
|
||||
setBadge(recipient.featuredBadge, glideRequests)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,10 +68,11 @@ class BadgesOverviewFragment : DSLSettingsFragment(
|
||||
fadedBadgeId = state.fadedBadgeId
|
||||
)
|
||||
|
||||
switchPref(
|
||||
asyncSwitchPref(
|
||||
title = DSLSettingsText.from(R.string.BadgesOverviewFragment__display_badges_on_profile),
|
||||
isChecked = state.displayBadgesOnProfile,
|
||||
isEnabled = state.stage == BadgesOverviewState.Stage.READY && state.hasUnexpiredBadges,
|
||||
isProcessing = state.stage == BadgesOverviewState.Stage.UPDATING_BADGE_DISPLAY_STATE,
|
||||
onClick = {
|
||||
viewModel.setDisplayBadgesOnProfile(!state.displayBadgesOnProfile)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,6 @@ data class BadgesOverviewState(
|
||||
enum class Stage {
|
||||
INIT,
|
||||
READY,
|
||||
UPDATING
|
||||
UPDATING_BADGE_DISPLAY_STATE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ class BadgesOverviewViewModel(
|
||||
}
|
||||
|
||||
fun setDisplayBadgesOnProfile(displayBadgesOnProfile: Boolean) {
|
||||
store.update { it.copy(stage = BadgesOverviewState.Stage.UPDATING_BADGE_DISPLAY_STATE) }
|
||||
disposables += badgeRepository.setVisibilityForAllBadges(displayBadgesOnProfile)
|
||||
.subscribe(
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
@@ -26,6 +27,9 @@ public class ConversationTypingView extends ConstraintLayout {
|
||||
private AvatarImageView avatar1;
|
||||
private AvatarImageView avatar2;
|
||||
private AvatarImageView avatar3;
|
||||
private BadgeImageView badge1;
|
||||
private BadgeImageView badge2;
|
||||
private BadgeImageView badge3;
|
||||
private View bubble;
|
||||
private TypingIndicatorView indicator;
|
||||
private TextView typistCount;
|
||||
@@ -41,6 +45,9 @@ public class ConversationTypingView extends ConstraintLayout {
|
||||
avatar1 = findViewById(R.id.typing_avatar_1);
|
||||
avatar2 = findViewById(R.id.typing_avatar_2);
|
||||
avatar3 = findViewById(R.id.typing_avatar_3);
|
||||
badge1 = findViewById(R.id.typing_badge_1);
|
||||
badge2 = findViewById(R.id.typing_badge_2);
|
||||
badge3 = findViewById(R.id.typing_badge_3);
|
||||
typistCount = findViewById(R.id.typing_count);
|
||||
bubble = findViewById(R.id.typing_bubble);
|
||||
indicator = findViewById(R.id.typing_indicator);
|
||||
@@ -55,6 +62,9 @@ public class ConversationTypingView extends ConstraintLayout {
|
||||
avatar1.setVisibility(GONE);
|
||||
avatar2.setVisibility(GONE);
|
||||
avatar3.setVisibility(GONE);
|
||||
badge1.setVisibility(GONE);
|
||||
badge2.setVisibility(GONE);
|
||||
badge3.setVisibility(GONE);
|
||||
typistCount.setVisibility(GONE);
|
||||
|
||||
if (isGroupThread) {
|
||||
@@ -75,15 +85,21 @@ public class ConversationTypingView extends ConstraintLayout {
|
||||
private void presentGroupThreadAvatars(@NonNull GlideRequests glideRequests, @NonNull List<Recipient> typists) {
|
||||
avatar1.setAvatar(glideRequests, typists.get(0), typists.size() == 1);
|
||||
avatar1.setVisibility(VISIBLE);
|
||||
badge1.setBadgeFromRecipient(typists.get(0), glideRequests);
|
||||
badge1.setVisibility(VISIBLE);
|
||||
|
||||
if (typists.size() > 1) {
|
||||
avatar2.setAvatar(glideRequests, typists.get(1), false);
|
||||
avatar2.setVisibility(VISIBLE);
|
||||
badge2.setBadgeFromRecipient(typists.get(1), glideRequests);
|
||||
badge2.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
if (typists.size() == 3) {
|
||||
avatar3.setAvatar(glideRequests, typists.get(2), false);
|
||||
avatar3.setVisibility(VISIBLE);
|
||||
badge3.setBadgeFromRecipient(typists.get(2), glideRequests);
|
||||
badge3.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
if (typists.size() > 3) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.models.AsyncSwitch
|
||||
import org.thoughtcrime.securesms.components.settings.models.Button
|
||||
import org.thoughtcrime.securesms.components.settings.models.Space
|
||||
import org.thoughtcrime.securesms.components.settings.models.Text
|
||||
@@ -37,6 +38,7 @@ class DSLSettingsAdapter : MappingAdapter() {
|
||||
Text.register(this)
|
||||
Space.register(this)
|
||||
Button.register(this)
|
||||
AsyncSwitch.register(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -415,6 +415,6 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||
}
|
||||
|
||||
private fun enqueueSubscriptionRedemption() {
|
||||
SubscriptionReceiptRequestResponseJob.enqueueSubscriptionContinuation()
|
||||
SubscriptionReceiptRequestResponseJob.createSubscriptionContinuationJobChain().enqueue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,11 +132,10 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
return Completable.create {
|
||||
stripeApi.confirmPaymentIntent(GooglePayPaymentSource(paymentData), paymentIntent).blockingSubscribe()
|
||||
|
||||
val jobId = BoostReceiptRequestResponseJob.enqueueChain(paymentIntent)
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
|
||||
var finalJobState: JobTracker.JobState? = null
|
||||
ApplicationDependencies.getJobManager().addListener(jobId) { _, jobState ->
|
||||
|
||||
BoostReceiptRequestResponseJob.createJobChain(paymentIntent).enqueue { _, jobState ->
|
||||
if (jobState.isComplete) {
|
||||
finalJobState = jobState
|
||||
countDownLatch.countDown()
|
||||
@@ -200,11 +199,10 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
}
|
||||
}.andThen {
|
||||
Log.d(TAG, "Enqueuing request response job chain.", true)
|
||||
val jobId = SubscriptionReceiptRequestResponseJob.enqueueSubscriptionContinuation()
|
||||
val countDownLatch = CountDownLatch(1)
|
||||
|
||||
var finalJobState: JobTracker.JobState? = null
|
||||
ApplicationDependencies.getJobManager().addListener(jobId) { _, jobState ->
|
||||
|
||||
SubscriptionReceiptRequestResponseJob.createSubscriptionContinuationJobChain().enqueue { _, jobState ->
|
||||
if (jobState.isComplete) {
|
||||
finalJobState = jobState
|
||||
countDownLatch.countDown()
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
@@ -25,6 +26,7 @@ import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import java.lang.Integer.min
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
@@ -137,7 +139,10 @@ data class Boost(
|
||||
button.text = FiatMoneyUtil.format(
|
||||
context.resources,
|
||||
boost.price,
|
||||
FiatMoneyUtil.formatOptions().trimZerosAfterDecimal()
|
||||
FiatMoneyUtil
|
||||
.formatOptions()
|
||||
.numberOnly()
|
||||
.trimZerosAfterDecimal()
|
||||
)
|
||||
button.setOnClickListener {
|
||||
model.onBoostClick(it, boost)
|
||||
@@ -181,11 +186,12 @@ data class Boost(
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
class MoneyFilter(val currency: Currency, private val onCustomAmountChanged: (String) -> Unit = {}) : DigitsKeyListener(), TextWatcher {
|
||||
class MoneyFilter(val currency: Currency, private val onCustomAmountChanged: (String) -> Unit = {}) : DigitsKeyListener(false, true), TextWatcher {
|
||||
|
||||
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
|
||||
val separatorCount = min(1, currency.defaultFractionDigits)
|
||||
val prefix: String = currency.getSymbol(Locale.getDefault())
|
||||
val pattern: Pattern = "[0-9]*([.,]){0,$separatorCount}[0-9]{0,${currency.defaultFractionDigits}}".toPattern()
|
||||
val pattern: Pattern = "[0-9]*($separator){0,$separatorCount}[0-9]{0,${currency.defaultFractionDigits}}".toPattern()
|
||||
|
||||
override fun filter(
|
||||
source: CharSequence,
|
||||
@@ -198,6 +204,11 @@ data class Boost(
|
||||
|
||||
val result = dest.subSequence(0, dstart).toString() + source.toString() + dest.subSequence(dend, dest.length)
|
||||
val resultWithoutCurrencyPrefix = result.removePrefix(prefix)
|
||||
|
||||
if (result.length == 1 && !result.isDigitsOnly() && result != separator.toString()) {
|
||||
return dest.subSequence(dstart, dend)
|
||||
}
|
||||
|
||||
val matcher = pattern.matcher(resultWithoutCurrencyPrefix)
|
||||
|
||||
if (!matcher.matches()) {
|
||||
|
||||
@@ -132,6 +132,11 @@ class BoostFragment : DSLSettingsBottomSheetFragment(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
processingDonationPaymentDialog.hide()
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: BoostState): DSLConfiguration {
|
||||
if (state.stage == BoostState.Stage.PAYMENT_PIPELINE) {
|
||||
processingDonationPaymentDialog.show()
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import java.math.BigDecimal
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.Currency
|
||||
|
||||
class BoostViewModel(
|
||||
@@ -190,7 +191,7 @@ class BoostViewModel(
|
||||
}
|
||||
|
||||
fun setCustomAmount(amount: String) {
|
||||
val bigDecimalAmount = if (amount.isEmpty()) {
|
||||
val bigDecimalAmount = if (amount.isEmpty() || amount == DecimalFormatSymbols.getInstance().decimalSeparator.toString()) {
|
||||
BigDecimal.ZERO
|
||||
} else {
|
||||
BigDecimal(amount)
|
||||
|
||||
@@ -109,6 +109,11 @@ class SubscribeFragment : DSLSettingsFragment(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
processingDonationPaymentDialog.hide()
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: SubscribeState): DSLConfiguration {
|
||||
if (state.hasInProgressSubscriptionTransaction || state.stage == SubscribeState.Stage.PAYMENT_PIPELINE) {
|
||||
processingDonationPaymentDialog.show()
|
||||
|
||||
@@ -108,6 +108,7 @@ class ThanksForYourSupportBottomSheetDialogFragment : FixedRoundedCornerBottomSh
|
||||
|
||||
if (args.isBoost) {
|
||||
presentBoostCopy()
|
||||
badgeView.visibility = View.INVISIBLE
|
||||
lottie.visible = true
|
||||
lottie.playAnimation()
|
||||
lottie.addAnimatorListener(object : AnimationCompleteListener() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.settings
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.Px
|
||||
import androidx.annotation.StringRes
|
||||
import org.thoughtcrime.securesms.components.settings.models.AsyncSwitch
|
||||
import org.thoughtcrime.securesms.components.settings.models.Button
|
||||
import org.thoughtcrime.securesms.components.settings.models.Space
|
||||
import org.thoughtcrime.securesms.components.settings.models.Text
|
||||
@@ -56,6 +57,17 @@ class DSLConfiguration {
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun asyncSwitchPref(
|
||||
title: DSLSettingsText,
|
||||
isEnabled: Boolean = true,
|
||||
isChecked: Boolean,
|
||||
isProcessing: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val preference = AsyncSwitch.Model(title, isEnabled, isChecked, isProcessing, onClick)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
fun switchPref(
|
||||
title: DSLSettingsText,
|
||||
summary: DSLSettingsText? = null,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.thoughtcrime.securesms.components.settings.models
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ViewSwitcher
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
|
||||
/**
|
||||
* Switch that will perform a long-running async operation (normally network) that requires a
|
||||
* progress spinner to replace the switch after a press.
|
||||
*/
|
||||
object AsyncSwitch {
|
||||
|
||||
fun register(adapter: MappingAdapter) {
|
||||
adapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory(AsyncSwitch::ViewHolder, R.layout.dsl_async_switch_preference_item))
|
||||
}
|
||||
|
||||
class Model(
|
||||
override val title: DSLSettingsText,
|
||||
override val isEnabled: Boolean,
|
||||
val isChecked: Boolean,
|
||||
val isProcessing: Boolean,
|
||||
val onClick: () -> Unit
|
||||
) : PreferenceModel<Model>() {
|
||||
override fun areContentsTheSame(newItem: Model): Boolean {
|
||||
return super.areContentsTheSame(newItem) && isChecked == newItem.isChecked && isProcessing == newItem.isProcessing
|
||||
}
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View) : PreferenceViewHolder<Model>(itemView) {
|
||||
private val switchWidget: SwitchMaterial = itemView.findViewById(R.id.switch_widget)
|
||||
private val switcher: ViewSwitcher = itemView.findViewById(R.id.switcher)
|
||||
|
||||
override fun bind(model: Model) {
|
||||
super.bind(model)
|
||||
switchWidget.isEnabled = model.isEnabled
|
||||
switchWidget.isChecked = model.isChecked
|
||||
itemView.isEnabled = !model.isProcessing
|
||||
switcher.displayedChild = if (model.isProcessing) 1 else 0
|
||||
|
||||
itemView.setOnClickListener {
|
||||
if (!model.isProcessing) {
|
||||
itemView.isEnabled = false
|
||||
switcher.displayedChild = 1
|
||||
model.onClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||
@@ -55,9 +56,11 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
|
||||
private AppCompatImageView backgroundAvatar;
|
||||
private AvatarImageView avatar;
|
||||
private BadgeImageView badge;
|
||||
private View rendererFrame;
|
||||
private TextureViewRenderer renderer;
|
||||
private ImageView pipAvatar;
|
||||
private BadgeImageView pipBadge;
|
||||
private ContactPhoto contactPhoto;
|
||||
private View audioMuted;
|
||||
private View infoOverlay;
|
||||
@@ -92,6 +95,8 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
infoIcon = findViewById(R.id.call_participant_info_icon);
|
||||
infoMessage = findViewById(R.id.call_participant_info_message);
|
||||
infoMoreInfo = findViewById(R.id.call_participant_info_more_info);
|
||||
badge = findViewById(R.id.call_participant_item_badge);
|
||||
pipBadge = findViewById(R.id.call_participant_item_pip_badge);
|
||||
|
||||
avatar.setFallbackPhotoProvider(FALLBACK_PHOTO_PROVIDER);
|
||||
useLargeAvatar();
|
||||
@@ -120,7 +125,9 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
renderer.attachBroadcastVideoSink(null);
|
||||
audioMuted.setVisibility(View.GONE);
|
||||
avatar.setVisibility(View.GONE);
|
||||
badge.setVisibility(View.GONE);
|
||||
pipAvatar.setVisibility(View.GONE);
|
||||
pipBadge.setVisibility(View.GONE);
|
||||
|
||||
infoOverlay.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -157,8 +164,10 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
|
||||
if (participantChanged || !Objects.equals(contactPhoto, participant.getRecipient().getContactPhoto())) {
|
||||
avatar.setAvatarUsingProfile(participant.getRecipient());
|
||||
badge.setBadgeFromRecipient(participant.getRecipient());
|
||||
AvatarUtil.loadBlurredIconIntoImageView(participant.getRecipient(), backgroundAvatar);
|
||||
setPipAvatar(participant.getRecipient());
|
||||
pipBadge.setBadgeFromRecipient(participant.getRecipient());
|
||||
contactPhoto = participant.getRecipient().getContactPhoto();
|
||||
}
|
||||
}
|
||||
@@ -193,15 +202,19 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
}
|
||||
|
||||
avatar.setVisibility(shouldRenderInPip ? View.GONE : View.VISIBLE);
|
||||
badge.setVisibility(shouldRenderInPip ? View.GONE : View.VISIBLE);
|
||||
pipAvatar.setVisibility(shouldRenderInPip ? View.VISIBLE : View.GONE);
|
||||
pipBadge.setVisibility(shouldRenderInPip ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
void hideAvatar() {
|
||||
avatar.setAlpha(0f);
|
||||
badge.setAlpha(0f);
|
||||
}
|
||||
|
||||
void showAvatar() {
|
||||
avatar.setAlpha(1f);
|
||||
badge.setAlpha(1f);
|
||||
}
|
||||
|
||||
void useLargeAvatar() {
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
@@ -29,6 +30,7 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||
|
||||
private final ViewGroup parent;
|
||||
private final AvatarImageView avatarImageView;
|
||||
private final BadgeImageView badgeImageView;
|
||||
private final TextView descriptionTextView;
|
||||
|
||||
private final Set<CallParticipantListUpdate.Wrapper> pendingAdditions = new HashSet<>();
|
||||
@@ -43,6 +45,7 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||
|
||||
this.parent = parent;
|
||||
this.avatarImageView = getContentView().findViewById(R.id.avatar);
|
||||
this.badgeImageView = getContentView().findViewById(R.id.badge);
|
||||
this.descriptionTextView = getContentView().findViewById(R.id.description);
|
||||
|
||||
setOnDismissListener(this::showPending);
|
||||
@@ -109,6 +112,7 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||
|
||||
private void setAvatar(@Nullable Recipient recipient) {
|
||||
avatarImageView.setAvatarUsingProfile(recipient);
|
||||
badgeImageView.setBadgeFromRecipient(recipient);
|
||||
avatarImageView.setVisibility(recipient == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
|
||||
@@ -322,6 +322,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), Recipient::self, this::initializeProfileIcon);
|
||||
|
||||
initializeSettingsTouchTarget();
|
||||
|
||||
if ((!searchToolbar.resolved() || !searchToolbar.get().isVisible()) && list.getAdapter() != defaultAdapter) {
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
setAdapter(defaultAdapter);
|
||||
@@ -527,7 +529,11 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
imageView.setBadgeFromRecipient(recipient);
|
||||
|
||||
AvatarUtil.loadIconIntoImageView(recipient, icon, getResources().getDimensionPixelSize(R.dimen.toolbar_avatar_size));
|
||||
icon.setOnClickListener(v -> getNavigator().goToAppSettings());
|
||||
}
|
||||
|
||||
private void initializeSettingsTouchTarget() {
|
||||
View touchArea = requireView().findViewById(R.id.toolbar_settings_touch_area);
|
||||
touchArea.setOnClickListener(v -> getNavigator().goToAppSettings());
|
||||
}
|
||||
|
||||
private void initializeSearchListener() {
|
||||
|
||||
@@ -226,6 +226,8 @@ public final class ConversationListItem extends ConstraintLayout
|
||||
private void setBadgeFromRecipient(Recipient recipient) {
|
||||
if (!recipient.isSelf()) {
|
||||
badge.setBadgeFromRecipient(recipient);
|
||||
} else {
|
||||
badge.setBadge(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -462,6 +462,14 @@ public class JobManager implements ConstraintObserver.Notifier {
|
||||
jobManager.enqueueChain(this);
|
||||
}
|
||||
|
||||
public void enqueue(@NonNull JobTracker.JobListener listener) {
|
||||
List<Job> lastChain = jobs.get(jobs.size() - 1);
|
||||
Job lastJobInLastChain = lastChain.get(lastChain.size() - 1);
|
||||
|
||||
jobManager.addListener(lastJobInLastChain.getId(), listener);
|
||||
enqueue();
|
||||
}
|
||||
|
||||
private List<List<Job>> getJobListChain() {
|
||||
return jobs;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.signal.zkgroup.receipts.ReceiptSerial;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.subscription.SubscriptionNotification;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
@@ -56,18 +57,15 @@ public class BoostReceiptRequestResponseJob extends BaseJob {
|
||||
);
|
||||
}
|
||||
|
||||
public static String enqueueChain(StripeApi.PaymentIntent paymentIntent) {
|
||||
public static JobManager.Chain createJobChain(StripeApi.PaymentIntent paymentIntent) {
|
||||
BoostReceiptRequestResponseJob requestReceiptJob = createJob(paymentIntent);
|
||||
DonationReceiptRedemptionJob redeemReceiptJob = DonationReceiptRedemptionJob.createJobForBoost();
|
||||
RefreshOwnProfileJob refreshOwnProfileJob = new RefreshOwnProfileJob();
|
||||
|
||||
ApplicationDependencies.getJobManager()
|
||||
.startChain(requestReceiptJob)
|
||||
.then(redeemReceiptJob)
|
||||
.then(refreshOwnProfileJob)
|
||||
.enqueue();
|
||||
|
||||
return refreshOwnProfileJob.getId();
|
||||
return ApplicationDependencies.getJobManager()
|
||||
.startChain(requestReceiptJob)
|
||||
.then(redeemReceiptJob)
|
||||
.then(refreshOwnProfileJob);
|
||||
}
|
||||
|
||||
private BoostReceiptRequestResponseJob(@NonNull Parameters parameters,
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.subscription.SubscriptionNotification;
|
||||
import org.whispersystems.signalservice.internal.EmptyResponse;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
|
||||
@@ -63,6 +64,7 @@ public class DonationReceiptRedemptionJob extends BaseJob {
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
SubscriptionNotification.RedemptionFailed.INSTANCE.show(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,8 +72,8 @@ public class DonationReceiptRedemptionJob extends BaseJob {
|
||||
Data inputData = getInputData();
|
||||
|
||||
if (inputData == null) {
|
||||
Log.w(TAG, "No input data. Failing.", null, true);
|
||||
throw new IllegalStateException("Expected a presentation object in input data.");
|
||||
Log.w(TAG, "No input data. Exiting.", null, true);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] presentationBytes = inputData.getStringAsBlob(INPUT_RECEIPT_CREDENTIAL_PRESENTATION);
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.subscription.Subscriber;
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription;
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId;
|
||||
import org.whispersystems.signalservice.internal.EmptyResponse;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
|
||||
@@ -97,7 +96,7 @@ public class SubscriptionKeepAliveJob extends BaseJob {
|
||||
|
||||
if (activeSubscription.getActiveSubscription().getEndOfCurrentPeriod() > SignalStore.donationsValues().getLastEndOfPeriod()) {
|
||||
Log.i(TAG, "Last end of period change. Requesting receipt refresh.");
|
||||
SubscriptionReceiptRequestResponseJob.enqueueSubscriptionContinuation();
|
||||
SubscriptionReceiptRequestResponseJob.createSubscriptionContinuationJobChain().enqueue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.signal.zkgroup.receipts.ReceiptSerial;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.subscription.Subscriber;
|
||||
@@ -62,19 +63,16 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
||||
);
|
||||
}
|
||||
|
||||
public static String enqueueSubscriptionContinuation() {
|
||||
public static JobManager.Chain createSubscriptionContinuationJobChain() {
|
||||
Subscriber subscriber = SignalStore.donationsValues().requireSubscriber();
|
||||
SubscriptionReceiptRequestResponseJob requestReceiptJob = createJob(subscriber.getSubscriberId());
|
||||
DonationReceiptRedemptionJob redeemReceiptJob = DonationReceiptRedemptionJob.createJobForSubscription();
|
||||
RefreshOwnProfileJob refreshOwnProfileJob = new RefreshOwnProfileJob();
|
||||
|
||||
ApplicationDependencies.getJobManager()
|
||||
.startChain(requestReceiptJob)
|
||||
.then(redeemReceiptJob)
|
||||
.then(refreshOwnProfileJob)
|
||||
.enqueue();
|
||||
|
||||
return refreshOwnProfileJob.getId();
|
||||
return ApplicationDependencies.getJobManager()
|
||||
.startChain(requestReceiptJob)
|
||||
.then(redeemReceiptJob)
|
||||
.then(refreshOwnProfileJob);
|
||||
}
|
||||
|
||||
private SubscriptionReceiptRequestResponseJob(@NonNull Parameters parameters,
|
||||
|
||||
@@ -226,7 +226,11 @@ public class ManageProfileFragment extends LoggingFragment {
|
||||
}
|
||||
|
||||
private void presentBadge(@NonNull Optional<Badge> badge) {
|
||||
badgeView.setBadge(badge.orNull());
|
||||
if (badge.isPresent() && badge.get().getVisible() && !badge.get().isExpired()) {
|
||||
badgeView.setBadge(badge.orNull());
|
||||
} else {
|
||||
badgeView.setBadge(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void presentEvent(@NonNull ManageProfileViewModel.Event event) {
|
||||
|
||||
@@ -38,5 +38,31 @@ sealed class SubscriptionNotification {
|
||||
}
|
||||
}
|
||||
|
||||
object RedemptionFailed : SubscriptionNotification() {
|
||||
override fun show(context: Context) {
|
||||
val notification = NotificationCompat.Builder(context, NotificationChannels.FAILURES)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(context.getString(R.string.Subscription__redemption_failed))
|
||||
.setContentText(context.getString(R.string.Subscription__please_contact_support_for_more_information))
|
||||
.addAction(
|
||||
NotificationCompat.Action.Builder(
|
||||
null,
|
||||
context.getString(R.string.Subscription__contact_support),
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
AppSettingsActivity.help(context, HelpFragment.DONATION_INDEX),
|
||||
if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_ONE_SHOT else 0
|
||||
)
|
||||
).build()
|
||||
)
|
||||
.build()
|
||||
|
||||
NotificationManagerCompat
|
||||
.from(context)
|
||||
.notify(NotificationIds.SUBSCRIPTION_VERIFY_FAILED, notification)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun show(context: Context)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter;
|
||||
@@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.util.MappingViewHolder;
|
||||
public class RecipientViewHolder<T extends RecipientMappingModel<T>> extends MappingViewHolder<T> {
|
||||
|
||||
protected final @Nullable AvatarImageView avatar;
|
||||
protected final @Nullable BadgeImageView badge;
|
||||
protected final @Nullable TextView name;
|
||||
protected final @Nullable EventListener<T> eventListener;
|
||||
private final boolean quickContactEnabled;
|
||||
@@ -30,6 +32,7 @@ public class RecipientViewHolder<T extends RecipientMappingModel<T>> extends Map
|
||||
this.quickContactEnabled = quickContactEnabled;
|
||||
|
||||
avatar = findViewById(R.id.recipient_view_avatar);
|
||||
badge = findViewById(R.id.recipient_view_badge);
|
||||
name = findViewById(R.id.recipient_view_name);
|
||||
}
|
||||
|
||||
@@ -39,6 +42,10 @@ public class RecipientViewHolder<T extends RecipientMappingModel<T>> extends Map
|
||||
avatar.setRecipient(model.getRecipient(), quickContactEnabled);
|
||||
}
|
||||
|
||||
if (badge != null) {
|
||||
badge.setBadgeFromRecipient(model.getRecipient());
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
name.setText(model.getName(context));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user