diff --git a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersFragment.java b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersFragment.java index eb7f7e7c2..d231f6575 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/blocked/BlockedUsersFragment.java @@ -1,9 +1,6 @@ package org.thoughtcrime.securesms.blocked; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; -import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -15,6 +12,7 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProviders; import androidx.recyclerview.widget.RecyclerView; +import org.thoughtcrime.securesms.BlockUnblockDialog; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.recipients.Recipient; @@ -74,24 +72,9 @@ public class BlockedUsersFragment extends Fragment { } private void handleRecipientClicked(@NonNull Recipient recipient) { - AlertDialog confirmationDialog = new AlertDialog.Builder(requireContext()) - .setTitle(R.string.BlockedUsersActivity__unblock_user) - .setMessage(getString(R.string.BlockedUsersActivity__do_you_want_to_unblock_s, recipient.getDisplayName(requireContext()))) - .setPositiveButton(R.string.BlockedUsersActivity__unblock, (dialog, which) -> { - viewModel.unblock(recipient.getId()); - dialog.dismiss(); - }) - .setNegativeButton(android.R.string.cancel, (dialog, which) -> { - dialog.dismiss(); - }) - .setCancelable(true) - .create(); - - confirmationDialog.setOnShowListener(dialog -> { - confirmationDialog.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(Color.RED); + BlockUnblockDialog.showUnblockFor(requireContext(), getViewLifecycleOwner().getLifecycle(), recipient, () -> { + viewModel.unblock(recipient.getId()); }); - - confirmationDialog.show(); } interface Listener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/BioTextPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/BioTextPreference.kt index 0c952dceb..b93662729 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/BioTextPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/BioTextPreference.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.conversation.preferences import android.content.ClipData import android.content.Context +import android.text.SpannableStringBuilder import android.view.View import android.widget.TextView import android.widget.Toast @@ -9,7 +10,9 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.PreferenceModel import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.util.ContextUtil import org.thoughtcrime.securesms.util.ServiceUtil +import org.thoughtcrime.securesms.util.SpanUtil import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder @@ -25,17 +28,25 @@ object BioTextPreference { } abstract class BioTextPreferenceModel> : PreferenceModel() { - abstract fun getHeadlineText(context: Context): String + abstract fun getHeadlineText(context: Context): CharSequence abstract fun getSubhead1Text(context: Context): String? abstract fun getSubhead2Text(): String? - abstract fun getCompoundDrawable(): Int } class RecipientModel( private val recipient: Recipient, ) : BioTextPreferenceModel() { - override fun getHeadlineText(context: Context): String = recipient.getDisplayNameOrUsername(context) + override fun getHeadlineText(context: Context): CharSequence { + val name = recipient.getDisplayNameOrUsername(context) + return if (recipient.isReleaseNotes) { + SpannableStringBuilder(name).apply { + SpanUtil.appendCenteredImageSpan(this, ContextUtil.requireDrawable(context, R.drawable.ic_official_28), 28, 28) + } + } else { + name + } + } override fun getSubhead1Text(context: Context): String? { return if (recipient.isReleaseNotes) { @@ -47,10 +58,6 @@ object BioTextPreference { override fun getSubhead2Text(): String? = recipient.e164.transform(PhoneNumberFormatter::prettyPrint).orNull() - override fun getCompoundDrawable(): Int { - return if (recipient.isReleaseNotes) R.drawable.ic_official_28 else 0 - } - override fun areContentsTheSame(newItem: RecipientModel): Boolean { return super.areContentsTheSame(newItem) && newItem.recipient.hasSameContent(recipient) } @@ -64,14 +71,12 @@ object BioTextPreference { val groupTitle: String, val groupMembershipDescription: String? ) : BioTextPreferenceModel() { - override fun getHeadlineText(context: Context): String = groupTitle + override fun getHeadlineText(context: Context): CharSequence = groupTitle override fun getSubhead1Text(context: Context): String? = groupMembershipDescription override fun getSubhead2Text(): String? = null - override fun getCompoundDrawable(): Int = 0 - override fun areContentsTheSame(newItem: GroupModel): Boolean { return super.areContentsTheSame(newItem) && groupTitle == newItem.groupTitle && @@ -91,7 +96,6 @@ object BioTextPreference { override fun bind(model: T) { headline.text = model.getHeadlineText(context) - headline.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, model.getCompoundDrawable(), 0) model.getSubhead1Text(context).let { subhead1.text = it diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt index a980dd33a..1dbb1d018 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.kt @@ -6,17 +6,21 @@ import android.view.MotionEvent import android.view.View import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar +import io.reactivex.rxjava3.subjects.PublishSubject +import io.reactivex.rxjava3.subjects.Subject import org.thoughtcrime.securesms.PassphraseRequiredActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.HidingLinearLayout import org.thoughtcrime.securesms.components.reminder.ReminderView +import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent +import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentRepository import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme import org.thoughtcrime.securesms.util.DynamicTheme import org.thoughtcrime.securesms.util.concurrent.ListenableFuture import org.thoughtcrime.securesms.util.views.Stub -open class ConversationActivity : PassphraseRequiredActivity(), ConversationParentFragment.Callback { +open class ConversationActivity : PassphraseRequiredActivity(), ConversationParentFragment.Callback, DonationPaymentComponent { private lateinit var fragment: ConversationParentFragment @@ -73,4 +77,7 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare fun getReminderView(): Stub { return fragment.reminderView } + + override val donationPaymentRepository: DonationPaymentRepository by lazy { DonationPaymentRepository(this) } + override val googlePayResultPublisher: Subject = PublishSubject.create() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 673cc820b..e4b720431 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -59,6 +59,7 @@ import androidx.core.view.ViewKt; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.OnScrollListener; @@ -1857,7 +1858,12 @@ public class ConversationFragment extends LoggingFragment implements Multiselect @Override public void onDonateClicked() { if (SignalStore.donationsValues().isLikelyASustainer()) { - startActivity(AppSettingsActivity.boost(requireContext())); + NavHostFragment navHostFragment = NavHostFragment.create(R.navigation.boosts); + + requireActivity().getSupportFragmentManager() + .beginTransaction() + .add(navHostFragment, "boost_nav") + .commitNow(); } else { startActivity(AppSettingsActivity.subscriptions(requireContext())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java index 72a4dd4e0..e41a5913d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java @@ -716,7 +716,7 @@ public final class ConversationReactionOverlay extends FrameLayout { items.add(new ActionItem(R.drawable.ic_select_24_tinted, getResources().getString(R.string.conversation_selection__menu_multi_select), () -> handleActionItemClicked(Action.MULTISELECT))); - if (menuState.shouldShowInfoAction()) { + if (menuState.shouldShowDetailsAction()) { items.add(new ActionItem(R.drawable.ic_info_tinted_24, getResources().getString(R.string.conversation_selection__menu_message_details), () -> handleActionItemClicked(Action.VIEW_INFO))); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/MenuState.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/MenuState.java index a9f756b6a..6083aae01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/MenuState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/MenuState.java @@ -22,7 +22,6 @@ final class MenuState { private final boolean resend; private final boolean copy; private final boolean delete; - private final boolean info; private final boolean reactions; private MenuState(@NonNull Builder builder) { @@ -33,7 +32,6 @@ final class MenuState { resend = builder.resend; copy = builder.copy; delete = builder.delete; - info = builder.info; reactions = builder.reactions; } @@ -65,10 +63,6 @@ final class MenuState { return delete; } - boolean shouldShowInfoAction() { - return info; - } - boolean shouldShowReactions() { return reactions; } @@ -154,13 +148,13 @@ final class MenuState { ((MediaMmsMessageRecord)messageRecord).containsMediaSlide() && ((MediaMmsMessageRecord)messageRecord).getSlideDeck().getStickerSlide() == null) .shouldShowForwardAction(shouldShowForwardAction) - .shouldShowDetailsAction(!actionMessage) + .shouldShowDetailsAction(!actionMessage && !conversationRecipient.isReleaseNotes()) .shouldShowReplyAction(canReplyToMessage(conversationRecipient, actionMessage, messageRecord, shouldShowMessageRequest, isNonAdminInAnnouncementGroup)); } return builder.shouldShowCopyAction(!actionMessage && !remoteDelete && hasText) .shouldShowDeleteAction(!hasInMemory && onlyContainsCompleteMessages(selectedParts)) - .shouldShowInfoAction(!conversationRecipient.isReleaseNotes()) + .shouldShowDetailsAction(!conversationRecipient.isReleaseNotes()) .shouldShowReactions(!conversationRecipient.isReleaseNotes()) .build(); } @@ -216,7 +210,6 @@ final class MenuState { private boolean resend; private boolean copy; private boolean delete; - private boolean info; private boolean reactions; @NonNull Builder shouldShowForwardAction(boolean forward) { @@ -254,11 +247,6 @@ final class MenuState { return this; } - @NonNull Builder shouldShowInfoAction(boolean info) { - this.info = info; - return this; - } - @NonNull Builder shouldShowReactions(boolean reactions) { this.reactions = reactions; return this; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveReleaseChannelJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveReleaseChannelJob.kt index 5d5d485be..921d76536 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveReleaseChannelJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveReleaseChannelJob.kt @@ -137,11 +137,13 @@ class RetrieveReleaseChannelJob private constructor(private val force: Boolean, val allReleaseNotes: ReleaseNotes? = S3.getAndVerifyObject(MANIFEST, ReleaseNotes::class.java, manifestMd5).result.orNull() if (allReleaseNotes != null) { - val resolvedNotes: List = allReleaseNotes.announcements - .filter { it.androidMinVersion.toIntOrNull()?.let { minVersion: Int -> minVersion > values.highestVersionNoteReceived && minVersion <= BuildConfig.CANONICAL_VERSION_CODE } ?: false } + val resolvedNotes: List = allReleaseNotes.announcements.asSequence() + .filter { it.androidMinVersion != null } + .filter { it.androidMinVersion!!.toIntOrNull()?.let { minVersion: Int -> minVersion > values.highestVersionNoteReceived && minVersion <= BuildConfig.CANONICAL_VERSION_CODE } ?: false } .filter { it.countries == null || LocaleFeatureFlags.shouldShowReleaseNote(it.uuid, it.countries) } - .sortedBy { it.androidMinVersion.toInt() } + .sortedBy { it.androidMinVersion!!.toInt() } .map { resolveReleaseNote(it) } + .toList() if (resolvedNotes.any { it == null }) { Log.w(TAG, "Some release notes did not resolve, aborting.") @@ -174,7 +176,9 @@ class RetrieveReleaseChannelJob private constructor(private val force: Boolean, body = body, threadId = threadId, messageRanges = bodyRangeList.build(), - image = note.translation.image + image = note.translation.image, + imageWidth = note.translation.imageWidth?.toIntOrNull() ?: 0, + imageHeight = note.translation.imageHeight?.toIntOrNull() ?: 0 ) SignalDatabase.sms.insertBoostRequestMessage(values.releaseChannelRecipientId!!, threadId) @@ -186,7 +190,7 @@ class RetrieveReleaseChannelJob private constructor(private val force: Boolean, ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.threadId) TrimThreadJob.enqueueAsync(insertResult.threadId) - highestVersion = max(highestVersion, note.releaseNote.androidMinVersion.toInt()) + highestVersion = max(highestVersion, note.releaseNote.androidMinVersion!!.toInt()) } } @@ -242,7 +246,7 @@ class RetrieveReleaseChannelJob private constructor(private val force: Boolean, data class ReleaseNote( @JsonProperty val uuid: String, @JsonProperty val countries: String?, - @JsonProperty val androidMinVersion: String, + @JsonProperty val androidMinVersion: String?, @JsonProperty val link: String?, @JsonProperty val ctaId: String? ) @@ -250,6 +254,8 @@ class RetrieveReleaseChannelJob private constructor(private val force: Boolean, data class TranslatedReleaseNote( @JsonProperty val uuid: String, @JsonProperty val image: String?, + @JsonProperty val imageWidth: String?, + @JsonProperty val imageHeight: String?, @JsonProperty val linkText: String?, @JsonProperty val title: String, @JsonProperty val body: String, diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java index eb284f556..00a5db0f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java @@ -4,7 +4,10 @@ import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -41,7 +44,9 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.BottomSheetUtil; import org.thoughtcrime.securesms.util.ContextUtil; +import org.thoughtcrime.securesms.util.DrawableUtil; import org.thoughtcrime.securesms.util.ServiceUtil; +import org.thoughtcrime.securesms.util.SpanUtil; import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; @@ -170,16 +175,16 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF String name = recipient.isSelf() ? requireContext().getString(R.string.note_to_self) : recipient.getDisplayName(requireContext()); - fullName.setText(name); fullName.setVisibility(TextUtils.isEmpty(name) ? View.GONE : View.VISIBLE); + SpannableStringBuilder nameBuilder = new SpannableStringBuilder(name); if (recipient.isSystemContact() && !recipient.isSelf()) { - fullName.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_profile_circle_outline_16, 0); - fullName.setCompoundDrawablePadding(ViewUtil.dpToPx(4)); - TextViewCompat.setCompoundDrawableTintList(fullName, ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.signal_text_primary))); + Drawable systemContact = DrawableUtil.tint(ContextUtil.requireDrawable(requireContext(), R.drawable.ic_profile_circle_outline_16), + ContextCompat.getColor(requireContext(), R.color.signal_text_primary)); + SpanUtil.appendCenteredImageSpan(nameBuilder, systemContact, 16, 16); } else if (recipient.isReleaseNotes()) { - fullName.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_official_28, 0); - fullName.setCompoundDrawablePadding(ViewUtil.dpToPx(4)); + SpanUtil.appendCenteredImageSpan(nameBuilder, ContextUtil.requireDrawable(requireContext(), R.drawable.ic_official_28), 28, 28); } + fullName.setText(nameBuilder); String aboutText = recipient.getCombinedAboutAndEmoji(); if (recipient.isReleaseNotes()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt b/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt index 68d8f12d5..64c88b021 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/releasechannel/ReleaseChannel.kt @@ -24,6 +24,8 @@ object ReleaseChannel { body: String, threadId: Long, image: String? = null, + imageWidth: Int = 0, + imageHeight: Int = 0, serverUuid: String? = UUID.randomUUID().toString(), messageRanges: BodyRangeList? = null ): MessageDatabase.InsertResult? { @@ -36,8 +38,8 @@ object ReleaseChannel { null, Optional.absent(), Optional.absent(), - 0, - 0, + imageWidth, + imageHeight, Optional.absent(), Optional.of(image), false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java index ad0ce1796..2b2f53f06 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SpanUtil.java @@ -127,6 +127,11 @@ public final class SpanUtil { return imageSpan; } + public static void appendCenteredImageSpan(@NonNull SpannableStringBuilder builder, @NonNull Drawable drawable, int width, int height) { + drawable.setBounds(0, 0, ViewUtil.dpToPx(width), ViewUtil.dpToPx(height)); + builder.append(" ").append(SpanUtil.buildCenteredImageSpan(drawable)); + } + public static CharSequence learnMore(@NonNull Context context, @ColorInt int color, @NonNull View.OnClickListener onLearnMoreClicked) diff --git a/app/src/main/res/layout/conversation_item_update.xml b/app/src/main/res/layout/conversation_item_update.xml index 53bba5d17..66d6b47f9 100644 --- a/app/src/main/res/layout/conversation_item_update.xml +++ b/app/src/main/res/layout/conversation_item_update.xml @@ -48,6 +48,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="12dp" + android:layout_marginBottom="4dp" android:layout="@layout/conversation_item_update_donate" /> diff --git a/app/src/main/res/layout/conversation_settings_bio_preference_item.xml b/app/src/main/res/layout/conversation_settings_bio_preference_item.xml index 6a71353d4..fa0de6a67 100644 --- a/app/src/main/res/layout/conversation_settings_bio_preference_item.xml +++ b/app/src/main/res/layout/conversation_settings_bio_preference_item.xml @@ -7,15 +7,13 @@ + app:popExitAnim="@anim/fragment_close_exit" + app:popUpTo="@id/app_settings" + app:popUpToInclusive="true" />