diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardBottomSheet.kt index fe3f5b9737..ecf32c5799 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardBottomSheet.kt @@ -44,7 +44,7 @@ class MultiselectForwardBottomSheet : FixedRoundedCornerBottomSheetDialogFragmen } override fun setResult(bundle: Bundle) { - setFragmentResult(MultiselectForwardFragment.RESULT_SELECTION, bundle) + setFragmentResult(MultiselectForwardFragment.RESULT_KEY, bundle) } override fun onDismiss(dialog: DialogInterface) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt index 276a891aa9..d5c43d5b78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt @@ -44,9 +44,24 @@ import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.fragments.findListener +import org.thoughtcrime.securesms.util.fragments.requireListener import org.thoughtcrime.securesms.util.views.SimpleProgressDialog import org.thoughtcrime.securesms.util.visible +/** + * Allows selection and optional sending to one or more users. + * + * This fragment is designed to be displayed in a Dialog fragment, and thus has two available constructors to display as a bottom sheet or full screen dialog. + * + * To customize the available recipients, a parent must implement `SearchConfigurationProvider` + * + * This fragment will emit one of two possible result values at the same key, `RESULT_KEY`: + * + * - If the arguments contain a non-empty list of MultiShareArgs, then messages will be sent when the selection is confirmed. This will result in `RESULT_SENT` being true. + * - If the arguments contain an empty list of MultiShareArgs, then the selection will be returned on confirmation. This will result in `RESULT_SELECTION` being set. + * + * It is up to the user of this fragment to handle the result accordingly utilizing a fragment result listener. + */ class MultiselectForwardFragment : Fragment(R.layout.multiselect_forward_fragment), SafetyNumberChangeDialog.Callback, @@ -98,8 +113,8 @@ class MultiselectForwardFragment : contactSearchMediator.onFilterChanged(it) } - val title: TextView? = view.findViewById(R.id.title) val container = callback.getContainer() + val title: TextView? = container.findViewById(R.id.title) val bottomBar = LayoutInflater.from(requireContext()).inflate(R.layout.multiselect_forward_fragment_bottom_bar, container, false) val shareSelectionRecycler: RecyclerView = bottomBar.findViewById(R.id.selected_list) val shareSelectionAdapter = ShareSelectionAdapter() @@ -162,9 +177,9 @@ class MultiselectForwardFragment : dismissibleDialog?.dismiss() dismissibleDialog = SimpleProgressDialog.showDelayed(requireContext()) } - MultiselectForwardState.Stage.SomeFailed -> dismissAndShowToast(R.plurals.MultiselectForwardFragment_messages_sent) + MultiselectForwardState.Stage.SomeFailed -> dismissWithSuccess(R.plurals.MultiselectForwardFragment_messages_sent) MultiselectForwardState.Stage.AllFailed -> dismissAndShowToast(R.plurals.MultiselectForwardFragment_messages_failed_to_send) - MultiselectForwardState.Stage.Success -> dismissAndShowToast(R.plurals.MultiselectForwardFragment_messages_sent) + MultiselectForwardState.Stage.Success -> dismissWithSuccess(R.plurals.MultiselectForwardFragment_messages_sent) is MultiselectForwardState.Stage.SelectionConfirmed -> dismissWithSelection(it.stage.selectedContacts) } @@ -242,6 +257,16 @@ class MultiselectForwardFragment : SafetyNumberChangeDialog.show(childFragmentManager, identityRecords) } + private fun dismissWithSuccess(@PluralsRes toastTextResId: Int) { + requireListener().setResult( + Bundle().apply { + putBoolean(RESULT_SENT, true) + } + ) + + dismissAndShowToast(toastTextResId) + } + private fun dismissAndShowToast(@PluralsRes toastTextResId: Int) { val argCount = getMessageCount() @@ -265,7 +290,7 @@ class MultiselectForwardFragment : dismissibleDialog?.dismiss() val resultsBundle = Bundle().apply { - putParcelableArrayList(RESULT_SELECTION_RECIPIENTS, ArrayList(selectedContacts.map { it.requireParcelable() })) + putParcelableArrayList(RESULT_SELECTION, ArrayList(selectedContacts.map { it.requireParcelable() })) } callback.setResult(resultsBundle) @@ -367,8 +392,9 @@ class MultiselectForwardFragment : const val ARG_MULTISHARE_ARGS = "multiselect.forward.fragment.arg.multishare.args" const val ARG_CAN_SEND_TO_NON_PUSH = "multiselect.forward.fragment.arg.can.send.to.non.push" const val ARG_TITLE = "multiselect.forward.fragment.title" - const val RESULT_SELECTION = "result_selection" - const val RESULT_SELECTION_RECIPIENTS = "result_selection_recipients" + const val RESULT_KEY = "result_key" + const val RESULT_SELECTION = "result_selection_recipients" + const val RESULT_SENT = "result_sent" @JvmStatic fun showBottomSheet(supportFragmentManager: FragmentManager, multiselectForwardFragmentArgs: MultiselectForwardFragmentArgs) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFullScreenDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFullScreenDialogFragment.kt index c9f0a6eda7..4d4364e26c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFullScreenDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFullScreenDialogFragment.kt @@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.components.FullScreenDialogFragment import org.thoughtcrime.securesms.util.fragments.findListener class MultiselectForwardFullScreenDialogFragment : FullScreenDialogFragment(), MultiselectForwardFragment.Callback { - override fun getTitle(): Int = R.string.MediaReviewFragment__send_to + override fun getTitle(): Int = requireArguments().getInt(MultiselectForwardFragment.ARG_TITLE) override fun getDialogLayoutResource(): Int = R.layout.fragment_container @@ -38,7 +38,7 @@ class MultiselectForwardFullScreenDialogFragment : FullScreenDialogFragment(), M } override fun setResult(bundle: Bundle) { - setFragmentResult(MultiselectForwardFragment.RESULT_SELECTION, bundle) + setFragmentResult(MultiselectForwardFragment.RESULT_KEY, bundle) } override fun exitFlow() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt index d704dba341..8dec2fc136 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt @@ -139,8 +139,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) { sharedViewModel.sendCommand(HudCommand.SaveMedia) } - setFragmentResultListener(MultiselectForwardFragment.RESULT_SELECTION) { _, bundle -> - val parcelizedKeys: List = bundle.getParcelableArrayList(MultiselectForwardFragment.RESULT_SELECTION_RECIPIENTS)!! + setFragmentResultListener(MultiselectForwardFragment.RESULT_KEY) { _, bundle -> + val parcelizedKeys: List = bundle.getParcelableArrayList(MultiselectForwardFragment.RESULT_SELECTION)!! val contactSearchKeys = parcelizedKeys.map { it.asContactSearchKey() } performSend(contactSearchKeys) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/GroupLinkBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/GroupLinkBottomSheetDialogFragment.java index bb82061b47..051f81332c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/GroupLinkBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/sharablegrouplink/GroupLinkBottomSheetDialogFragment.java @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.recipients.ui.sharablegrouplink; import android.content.Context; -import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -18,14 +17,17 @@ import androidx.fragment.app.FragmentManager; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment; +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.LiveGroup; import org.thoughtcrime.securesms.recipients.ui.sharablegrouplink.qr.GroupLinkShareQrDialogFragment; -import org.thoughtcrime.securesms.sharing.ShareActivity; +import org.thoughtcrime.securesms.sharing.MultiShareArgs; import org.thoughtcrime.securesms.util.BottomSheetUtil; import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; +import java.util.Collections; import java.util.Objects; public final class GroupLinkBottomSheetDialogFragment extends BottomSheetDialogFragment { @@ -77,10 +79,16 @@ public final class GroupLinkBottomSheetDialogFragment extends BottomSheetDialogF hint.setVisibility(View.VISIBLE); shareViaSignalButton.setOnClickListener(v -> { - Context context = requireContext(); - Intent intent = new Intent(context, ShareActivity.class); - intent.putExtra(Intent.EXTRA_TEXT, groupLink.getUrl()); - context.startActivity(intent); + MultiselectForwardFragment.showBottomSheet( + getParentFragmentManager(), + new MultiselectForwardFragmentArgs( + true, + Collections.singletonList(new MultiShareArgs.Builder(Collections.emptySet()) + .withDraftText(groupLink.getUrl()) + .build()), + R.string.MultiselectForwardFragment__share_with + ) + ); dismiss(); }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/shakereport/ShakeToReport.java b/app/src/main/java/org/thoughtcrime/securesms/shakereport/ShakeToReport.java index 55b390b356..7d1acc3e72 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/shakereport/ShakeToReport.java +++ b/app/src/main/java/org/thoughtcrime/securesms/shakereport/ShakeToReport.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.shakereport; -import android.app.Activity; import android.app.Application; import android.widget.TextView; import android.widget.Toast; @@ -16,14 +15,17 @@ import org.signal.core.util.ShakeDetector; import org.signal.core.util.ThreadUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment; +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogRepository; -import org.thoughtcrime.securesms.sharing.ShareIntents; +import org.thoughtcrime.securesms.sharing.MultiShareArgs; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import java.lang.ref.WeakReference; +import java.util.Collections; /** * A class that will detect a shake and then prompts the user to submit a debuglog. Basically a @@ -90,7 +92,7 @@ public final class ShakeToReport implements ShakeDetector.Listener { } } - private void submitLog(@NonNull Activity activity) { + private void submitLog(@NonNull AppCompatActivity activity) { AlertDialog spinner = SimpleProgressDialog.show(activity); SubmitDebugLogRepository repo = new SubmitDebugLogRepository(); @@ -112,7 +114,7 @@ public final class ShakeToReport implements ShakeDetector.Listener { }); } - private void showPostSubmitDialog(@NonNull Activity activity, @NonNull String url) { + private void showPostSubmitDialog(@NonNull AppCompatActivity activity, @NonNull String url) { AlertDialog dialog = new MaterialAlertDialogBuilder(activity) .setTitle(R.string.ShakeToReport_success) .setMessage(url) @@ -124,9 +126,16 @@ public final class ShakeToReport implements ShakeDetector.Listener { d.dismiss(); enableIfVisible(); - activity.startActivity(new ShareIntents.Builder(activity) - .setText(url) - .build()); + MultiselectForwardFragment.showFullScreen( + activity.getSupportFragmentManager(), + new MultiselectForwardFragmentArgs( + true, + Collections.singletonList(new MultiShareArgs.Builder(Collections.emptySet()) + .withDraftText(url) + .build()), + R.string.MultiselectForwardFragment__share_with + ) + ); }) .show(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareIntents.java b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareIntents.java index 828da0856e..59e6fc0460 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareIntents.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sharing/ShareIntents.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.sharing; -import android.content.Context; import android.content.Intent; import android.text.TextUtils; @@ -8,14 +7,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.mediasend.Media; -import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.stickers.StickerLocator; import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -public final class ShareIntents { +final class ShareIntents { private static final String EXTRA_MEDIA = "extra_media"; private static final String EXTRA_BORDERLESS = "extra_borderless"; @@ -71,58 +67,4 @@ public final class ShareIntents { TextUtils.isEmpty(extraText); } } - - public static final class Builder { - - private final Context context; - - private String extraText; - private List extraMedia; - private Slide slide; - - public Builder(@NonNull Context context) { - this.context = context; - } - - public @NonNull Builder setText(@NonNull CharSequence extraText) { - this.extraText = extraText.toString(); - return this; - } - - public @NonNull Builder setMedia(@NonNull Collection extraMedia) { - this.extraMedia = new ArrayList<>(extraMedia); - return this; - } - - public @NonNull Builder setSlide(@NonNull Slide slide) { - this.slide = slide; - return this; - } - - public @NonNull Intent build() { - if (slide != null && extraMedia != null) { - throw new IllegalStateException("Cannot create intent with both Slide and [Media]"); - } - - Intent intent = new Intent(context, ShareActivity.class); - - intent.putExtra(Intent.EXTRA_TEXT, extraText); - - if (extraMedia != null) { - intent.putParcelableArrayListExtra(EXTRA_MEDIA, new ArrayList<>(extraMedia)); - } else if (slide != null) { - intent.putExtra(Intent.EXTRA_STREAM, slide.getUri()); - intent.putExtra(EXTRA_BORDERLESS, slide.isBorderless()); - - if (slide.hasSticker()) { - intent.putExtra(EXTRA_STICKER, slide.asAttachment().getSticker()); - intent.setType(slide.asAttachment().getContentType()); - } else { - intent.setType(slide.getContentType()); - } - } - - return intent; - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.java b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.java index 3694ec8984..19cccd26e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivity.java @@ -13,11 +13,15 @@ import androidx.recyclerview.widget.RecyclerView; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment; +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs; import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.sharing.ShareActivity; +import org.thoughtcrime.securesms.sharing.MultiShareArgs; import org.thoughtcrime.securesms.util.DeviceProperties; import org.thoughtcrime.securesms.util.DynamicTheme; +import java.util.Collections; + /** * Allows the user to view and manage (install, uninstall, etc) their stickers. */ @@ -46,6 +50,12 @@ public final class StickerManagementActivity extends PassphraseRequiredActivity initView(); initToolbar(); initViewModel(); + + getSupportFragmentManager().setFragmentResultListener(MultiselectForwardFragment.RESULT_KEY, this, (requestKey, result) -> { + if (result.getBoolean(MultiselectForwardFragment.RESULT_SENT, false)) { + finish(); + } + }); } @Override @@ -86,10 +96,16 @@ public final class StickerManagementActivity extends PassphraseRequiredActivity @Override public void onStickerPackShareClicked(@NonNull String packId, @NonNull String packKey) { - Intent composeIntent = new Intent(this, ShareActivity.class); - composeIntent.putExtra(Intent.EXTRA_TEXT, StickerUrl.createShareLink(packId, packKey)); - startActivity(composeIntent); - finish(); + MultiselectForwardFragment.showBottomSheet( + getSupportFragmentManager(), + new MultiselectForwardFragmentArgs( + true, + Collections.singletonList(new MultiShareArgs.Builder(Collections.emptySet()) + .withDraftText(StickerUrl.createShareLink(packId, packKey)) + .build()), + R.string.MultiselectForwardFragment__share_with + ) + ); } private void initView() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackPreviewActivity.java index f4d900becc..a570f06c6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerPackPreviewActivity.java @@ -22,16 +22,19 @@ import org.signal.core.util.logging.Log; import org.signal.libsignal.protocol.util.Pair; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment; +import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs; import org.thoughtcrime.securesms.glide.cache.ApngOptions; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.sharing.ShareActivity; +import org.thoughtcrime.securesms.sharing.MultiShareArgs; import org.thoughtcrime.securesms.stickers.StickerManifest.Sticker; import org.thoughtcrime.securesms.util.DeviceProperties; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; import org.whispersystems.signalservice.api.util.OptionalUtil; +import java.util.Collections; import java.util.Optional; @@ -40,9 +43,9 @@ import java.util.Optional; * (if installed). This is also the handler for sticker pack deep links. */ public final class StickerPackPreviewActivity extends PassphraseRequiredActivity - implements StickerRolloverTouchListener.RolloverEventListener, - StickerRolloverTouchListener.RolloverStickerRetriever, - StickerPackPreviewAdapter.EventListener + implements StickerRolloverTouchListener.RolloverEventListener, + StickerRolloverTouchListener.RolloverStickerRetriever, + StickerPackPreviewAdapter.EventListener { private static final String TAG = Log.tag(StickerPackPreviewActivity.class); @@ -95,6 +98,12 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActivity initToolbar(); initView(); initViewModel(packId, packKey); + + getSupportFragmentManager().setFragmentResultListener(MultiselectForwardFragment.RESULT_KEY, this, (requestKey, result) -> { + if (result.getBoolean(MultiselectForwardFragment.RESULT_SENT, false)) { + finish(); + } + }); } @Override @@ -193,12 +202,12 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActivity Sticker cover = OptionalUtil.or(manifest.getCover(), Optional.ofNullable(first)).orElse(null); if (cover != null) { - Object model = cover.getUri().isPresent() ? new DecryptableStreamUriLoader.DecryptableUri(cover.getUri().get()) - : new StickerRemoteUri(cover.getPackId(), cover.getPackKey(), cover.getId()); + Object model = cover.getUri().isPresent() ? new DecryptableStreamUriLoader.DecryptableUri(cover.getUri().get()) + : new StickerRemoteUri(cover.getPackId(), cover.getPackKey(), cover.getId()); GlideApp.with(this).load(model) - .transition(DrawableTransitionOptions.withCrossFade()) - .set(ApngOptions.ANIMATE, DeviceProperties.shouldAllowApngStickerAnimation(this)) - .into(coverImage); + .transition(DrawableTransitionOptions.withCrossFade()) + .set(ApngOptions.ANIMATE, DeviceProperties.shouldAllowApngStickerAnimation(this)) + .into(coverImage); } else { coverImage.setImageDrawable(null); } @@ -229,10 +238,16 @@ public final class StickerPackPreviewActivity extends PassphraseRequiredActivity shareButton.setVisibility(View.VISIBLE); shareButtonImage.setVisibility(View.VISIBLE); shareButton.setOnClickListener(v -> { - Intent composeIntent = new Intent(this, ShareActivity.class); - composeIntent.putExtra(Intent.EXTRA_TEXT, StickerUrl.createShareLink(packId, packKey)); - startActivity(composeIntent); - finish(); + MultiselectForwardFragment.showBottomSheet( + getSupportFragmentManager(), + new MultiselectForwardFragmentArgs( + true, + Collections.singletonList(new MultiShareArgs.Builder(Collections.emptySet()) + .withDraftText(StickerUrl.createShareLink(packId, packKey)) + .build()), + R.string.MultiselectForwardFragment__share_with + ) + ); }); } else { shareButton.setVisibility(View.GONE); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 308ad9ba96..ff177d3509 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4021,6 +4021,8 @@ Navigate up Forward to + + Share with Add a message Faster forwards Forwarded messages are now sent immediately.