Fix a bunch UX bugs for donor badges.

fork-5.53.8
Alex Hart 2021-11-11 13:46:38 -04:00 zatwierdzone przez GitHub
rodzic 5047fc54f2
commit ca24682366
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
37 zmienionych plików z 450 dodań i 47 usunięć

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -15,6 +15,6 @@ data class BadgesOverviewState(
enum class Stage {
INIT,
READY,
UPDATING
UPDATING_BADGE_DISPLAY_STATE
}
}

Wyświetl plik

@ -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(
{

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -415,6 +415,6 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
}
private fun enqueueSubscriptionRedemption() {
SubscriptionReceiptRequestResponseJob.enqueueSubscriptionContinuation()
SubscriptionReceiptRequestResponseJob.createSubscriptionContinuationJobChain().enqueue()
}
}

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()) {

Wyświetl plik

@ -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()

Wyświetl plik

@ -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)

Wyświetl plik

@ -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()

Wyświetl plik

@ -108,6 +108,7 @@ class ThanksForYourSupportBottomSheetDialogFragment : FixedRoundedCornerBottomSh
if (args.isBoost) {
presentBoostCopy()
badgeView.visibility = View.INVISIBLE
lottie.visible = true
lottie.playAnimation()
lottie.addAnimatorListener(object : AnimationCompleteListener() {

Wyświetl plik

@ -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,

Wyświetl plik

@ -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()
}
}
}
}
}

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -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);

Wyświetl plik

@ -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();
}
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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));
}

Wyświetl plik

@ -28,6 +28,21 @@
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<View
android:id="@+id/call_participant_badge_offseter"
android:layout_width="36dp"
android:layout_height="32dp"
app:layout_constraintBottom_toBottomOf="@id/call_participant_item_avatar"
app:layout_constraintEnd_toEndOf="@id/call_participant_item_avatar" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/call_participant_item_badge"
android:layout_width="36dp"
android:layout_height="36dp"
app:badge_size="large"
app:layout_constraintEnd_toEndOf="@id/call_participant_badge_offseter"
app:layout_constraintTop_toTopOf="@id/call_participant_badge_offseter" />
<ImageView
android:id="@+id/call_participant_item_pip_avatar"
android:layout_width="200dp"
@ -39,6 +54,15 @@
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/call_participant_item_pip_badge"
android:layout_width="36dp"
android:layout_height="36dp"
android:visibility="gone"
app:badge_size="large"
app:layout_constraintBottom_toBottomOf="@id/call_participant_item_pip_avatar"
app:layout_constraintEnd_toEndOf="@id/call_participant_item_pip_avatar" />
<FrameLayout
android:id="@+id/call_participant_renderer_frame"
android:layout_width="match_parent"

Wyświetl plik

@ -26,6 +26,18 @@
tools:src="@drawable/ic_profile_80"
tools:visibility="visible" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/badge"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="20dp"
android:layout_marginTop="22dp"
android:contentDescription="@string/ImageView__badge"
app:badge_size="medium"
app:layout_constraintStart_toStartOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/avatar"
tools:visibility="visible" />
<TextView
android:id="@+id/description"
android:layout_width="0dp"

Wyświetl plik

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -16,13 +16,25 @@
android:layout_width="36dp"
android:layout_height="36dp"
app:fallbackImageSize="small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/recipient_view_badge"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="20dp"
android:layout_marginTop="23dp"
app:badge_size="small"
app:layout_constraintStart_toStartOf="@id/recipient_view_avatar"
app:layout_constraintTop_toTopOf="@id/recipient_view_avatar" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/recipient_view_name"
style="@style/TextAppearance.Signal.Body1"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
@ -30,29 +42,45 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/Signal.Text.Preview"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/call_participant_video_muted"
app:layout_constraintStart_toEndOf="@id/recipient_view_avatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/call_participant_video_muted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/call_participant_audio_muted"
app:layout_constraintStart_toEndOf="@id/recipient_view_name"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_video_off_solid_white_18"
app:tint="@color/core_white"/>
app:tint="@color/core_white" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/call_participant_audio_muted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/call_participant_screen_sharing"
app:layout_constraintStart_toEndOf="@id/call_participant_video_muted"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_mic_off_solid_18"
app:tint="@color/core_white"/>
app:tint="@color/core_white" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/call_participant_screen_sharing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/call_participant_audio_muted"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_share_screen_20"
app:tint="@color/core_white"/>
app:tint="@color/core_white" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -54,6 +54,15 @@
app:layout_constraintStart_toStartOf="@id/toolbar_icon"
app:layout_constraintTop_toTopOf="@id/toolbar_icon" />
<View
android:id="@+id/toolbar_settings_touch_area"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/toolbar_icon"
app:layout_constraintStart_toStartOf="@id/toolbar_icon"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/unread_payments_indicator"
android:layout_width="13dp"

Wyświetl plik

@ -24,6 +24,18 @@
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/typing_badge_1"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="14dp"
android:layout_marginTop="16dp"
android:visibility="gone"
app:badge_size="small"
app:layout_constraintStart_toStartOf="@id/typing_avatar_1"
app:layout_constraintTop_toTopOf="@id/typing_avatar_1"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/typing_avatar_2"
android:layout_width="@dimen/conversation_item_avatar_size"
@ -39,6 +51,18 @@
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/typing_badge_2"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="14dp"
android:layout_marginTop="16dp"
android:visibility="gone"
app:badge_size="small"
app:layout_constraintStart_toStartOf="@id/typing_avatar_2"
app:layout_constraintTop_toTopOf="@id/typing_avatar_2"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/typing_avatar_3"
android:layout_width="@dimen/conversation_item_avatar_size"
@ -53,6 +77,17 @@
app:layout_constraintStart_toStartOf="@id/typing_avatar_2"
app:layout_constraintTop_toTopOf="parent" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/typing_badge_3"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="14dp"
android:layout_marginTop="16dp"
android:visibility="gone"
app:badge_size="small"
app:layout_constraintStart_toStartOf="@id/typing_avatar_3"
app:layout_constraintTop_toTopOf="@id/typing_avatar_3" />
<TextView
android:id="@+id/typing_count"
android:layout_width="wrap_content"

Wyświetl plik

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/dsl_preference_item_background"
android:minHeight="56dp">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="@dimen/dsl_settings_gutter"
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_advanced_24" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:textAppearance="@style/Signal.Text.Body"
app:layout_constraintBottom_toTopOf="@id/summary"
app:layout_constraintEnd_toStartOf="@id/switcher"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginBottom="16dp"
app:layout_goneMarginStart="@dimen/dsl_settings_gutter"
tools:text="Message font size" />
<TextView
android:id="@+id/summary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
android:layout_marginBottom="16dp"
android:lineSpacingExtra="4sp"
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/text_color_secondary_enabled_selector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/switcher"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_goneMarginStart="@dimen/dsl_settings_gutter"
app:layout_goneMarginTop="16dp"
tools:text="Some random text to get stuff onto more than one line but not absurdly long like lorem/random"
tools:visibility="visible" />
<ViewSwitcher
android:id="@+id/switcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dsl_settings_gutter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false" />
<ProgressBar
android:id="@+id/switch_progress"
style="@style/Widget.MaterialComponents.CircularProgressIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
</ViewSwitcher>
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -16,11 +16,25 @@
android:layout_width="36dp"
android:layout_height="36dp"
app:fallbackImageSize="small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/recipient_view_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/recipient_view_badge"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="20dp"
android:layout_marginTop="23dp"
app:badge_size="small"
app:layout_constraintStart_toStartOf="@id/recipient_view_avatar"
app:layout_constraintTop_toTopOf="@id/recipient_view_avatar" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/recipient_view_name"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
@ -28,6 +42,10 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/Signal.Text.Preview"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/recipient_view_avatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -19,6 +19,18 @@
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/recipient_view_badge"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="20dp"
android:layout_marginTop="22dp"
android:contentDescription="@string/ImageView__badge"
app:badge_size="medium"
app:layout_constraintStart_toStartOf="@id/recipient_view_avatar"
app:layout_constraintTop_toTopOf="@id/recipient_view_avatar"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/payments_home_payment_item_avatar_progress_overlay"
android:layout_width="0dp"

Wyświetl plik

@ -8,8 +8,8 @@
<org.thoughtcrime.securesms.badges.BadgeImageView
android:id="@+id/badge"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_gravity="center_horizontal"
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
app:badge_size="xlarge"

Wyświetl plik

@ -3999,6 +3999,7 @@
<string name="ExpiredBadgeBottomSheetDialogFragment__renew_subscription">Renew subscription</string>
<string name="Subscription__verification_failed">Subscription Verification Failed</string>
<string name="Subscription__redemption_failed">Badge Redemption Failed</string>
<string name="Subscription__please_contact_support_for_more_information">Please contact support for more information.</string>
<string name="Subscription__contact_support">Contact Support</string>
<string name="Subscription__earn_a_s_badge">Earn a %1$s badge</string>