From 9d17bf473ce60e581f500f0668cd4a9f97e3fde5 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 14 Apr 2023 15:40:26 -0300 Subject: [PATCH] Add several callbacks to v2 convo fragment. --- .../DSLSettingsBottomSheetFragment.kt | 2 - .../conversation/ConversationFragment.java | 42 +++---- .../conversation/ConversationRepository.java | 2 +- .../conversation/v2/AddToContactsContract.kt | 60 +++++++++ .../conversation/v2/ConversationFragment.kt | 115 +++++++++++++++--- .../conversation/v2/ConversationRepository.kt | 4 + .../conversation/v2/ConversationViewModel.kt | 8 ++ .../v2/groups/ConversationGroupViewModel.kt | 12 +- .../res/layout/conversation_input_panel.xml | 1 - 9 files changed, 197 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/AddToContactsContract.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsBottomSheetFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsBottomSheetFragment.kt index 094274729e..ba9d5d4b97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsBottomSheetFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsBottomSheetFragment.kt @@ -7,7 +7,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EdgeEffect -import androidx.annotation.Discouraged import androidx.annotation.LayoutRes import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager @@ -16,7 +15,6 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.util.WindowUtil -@Discouraged("The DSL API can be completely replaced by compose. See ComposeFragment or ComposeBottomSheetFragment for an alternative to this API") abstract class DSLSettingsBottomSheetFragment( @LayoutRes private val layoutId: Int = R.layout.dsl_settings_bottom_sheet, val layoutManagerProducer: (Context) -> RecyclerView.LayoutManager = { context -> LinearLayoutManager(context) }, 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 b14a87754c..2517021201 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -46,6 +46,8 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.ViewSwitcher; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -111,6 +113,7 @@ import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectFor import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs; import org.thoughtcrime.securesms.conversation.quotes.MessageQuotesBottomSheet; import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog; +import org.thoughtcrime.securesms.conversation.v2.AddToContactsContract; import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -216,7 +219,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect private static final String TAG = Log.tag(ConversationFragment.class); private static final int SCROLL_ANIMATION_THRESHOLD = 50; - private static final int CODE_ADD_EDIT_CONTACT = 77; private static final int MAX_SCROLL_DELAY_COUNT = 5; private final ActionModeCallback actionModeCallback = new ActionModeCallback(); @@ -271,6 +273,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect private final DatabaseObserver.Observer threadDeletedObserver = this::onThreadDelete; + private ActivityResultLauncher addToContactsLauncher; + public static void prepare(@NonNull Context context) { FrameLayout parent = new FrameLayout(context); parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); @@ -294,6 +298,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) { + addToContactsLauncher = registerForActivityResult(new AddToContactsContract(), result -> {}); + disposables.bindTo(getViewLifecycleOwner()); lastSeenDisposable.bindTo(getViewLifecycleOwner()); @@ -1819,24 +1825,15 @@ public class ConversationFragment extends LoggingFragment implements Multiselect @Override public void onAddToContactsClicked(@NonNull Contact contactWithAvatar) { - if (getContext() != null) { - new AsyncTask() { - @Override - protected Intent doInBackground(Void... voids) { - return ContactUtil.buildAddToContactsIntent(getContext(), contactWithAvatar); - } - - @Override - protected void onPostExecute(Intent intent) { - try { - startActivityForResult(intent, CODE_ADD_EDIT_CONTACT); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Could not locate contacts activity", e); - Toast.makeText(requireContext(), R.string.ConversationFragment__contacts_app_not_found, Toast.LENGTH_SHORT).show(); - } - } - }.execute(); + if (getContext() == null) { + return; } + + disposables.add(AddToContactsContract.createIntentAndLaunch( + ConversationFragment.this, + addToContactsLauncher, + contactWithAvatar + )); } @Override @@ -2149,15 +2146,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect } } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == CODE_ADD_EDIT_CONTACT && getContext() != null) { - ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false)); - } - } - private void handleEnterMultiSelect(@NonNull ConversationMessage conversationMessage) { Set multiselectParts = conversationMessage.getMultiselectCollection().toSet(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java index 43a8069066..a922c4b604 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java @@ -114,7 +114,7 @@ public class ConversationRepository { return new ConversationData(threadId, lastSeen, lastSeenPosition, lastScrolledPosition, jumpToPosition, threadSize, messageRequestData, showUniversalExpireTimerUpdate); } - void markGiftBadgeRevealed(long messageId) { + public void markGiftBadgeRevealed(long messageId) { SignalExecutors.BOUNDED_IO.execute(() -> { List markedMessageInfo = SignalDatabase.messages().setOutgoingGiftsRevealed(Collections.singletonList(messageId)); if (!markedMessageInfo.isEmpty()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/AddToContactsContract.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/AddToContactsContract.kt new file mode 100644 index 0000000000..24cb2551dd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/AddToContactsContract.kt @@ -0,0 +1,60 @@ +package org.thoughtcrime.securesms.conversation.v2 + +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContract +import androidx.fragment.app.Fragment +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.Disposable +import io.reactivex.rxjava3.kotlin.subscribeBy +import io.reactivex.rxjava3.schedulers.Schedulers +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.contactshare.Contact +import org.thoughtcrime.securesms.contactshare.ContactUtil +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob + +/** + * Wraps up the "Add shared contact to contact list" into a contract. The flow here is a little + * weird because buildAddToContactsIntent has to be run in the background (as it loads image data). + * + * Usage: + * Register for result from your fragment, and then pass the created launcher in when you call + * createIntentAndLaunch. + */ +class AddToContactsContract : ActivityResultContract() { + override fun createIntent(context: Context, input: Intent): Intent = input + + override fun parseResult(resultCode: Int, intent: Intent?) { + ApplicationDependencies.getJobManager().add(DirectoryRefreshJob(false)) + } + + companion object { + + private val TAG = Log.tag(AddToContactsContract::class.java) + + @JvmStatic + fun createIntentAndLaunch( + fragment: Fragment, + launcher: ActivityResultLauncher, + contact: Contact + ): Disposable { + return Single.fromCallable { ContactUtil.buildAddToContactsIntent(fragment.requireContext(), contact) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy { + try { + launcher.launch(it) + } catch (e: ActivityNotFoundException) { + Log.w(TAG, "Could not locate contacts activity", e) + Toast.makeText(fragment.requireContext(), R.string.ConversationFragment__contacts_app_not_found, Toast.LENGTH_SHORT).show() + } + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 34a8aa8d85..8aa0747182 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -1,10 +1,14 @@ package org.thoughtcrime.securesms.conversation.v2 +import android.app.ActivityOptions +import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher import androidx.core.app.ActivityCompat import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat @@ -14,6 +18,8 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single @@ -24,6 +30,8 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.MainActivity import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.badges.gifts.viewgift.received.ViewReceivedGiftBottomSheet +import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet import org.thoughtcrime.securesms.components.ViewBinderDelegate import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner @@ -55,12 +63,18 @@ import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionPlayerHolder import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionRecycler import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange +import org.thoughtcrime.securesms.groups.ui.GroupErrors +import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult import org.thoughtcrime.securesms.invites.InviteActions import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.longmessage.LongMessageFragment import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory +import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory.create +import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity +import org.thoughtcrime.securesms.mms.AttachmentManager import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.notifications.v2.ConversationId +import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment import org.thoughtcrime.securesms.recipients.Recipient @@ -69,12 +83,14 @@ import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheet import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity +import org.thoughtcrime.securesms.util.BottomSheetUtil import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.ContextUtil import org.thoughtcrime.securesms.util.DrawableUtil import org.thoughtcrime.securesms.util.FullscreenHelper import org.thoughtcrime.securesms.util.WindowUtil import org.thoughtcrime.securesms.util.fragments.requireListener +import org.thoughtcrime.securesms.util.hasGiftBadge import org.thoughtcrime.securesms.util.visible import org.thoughtcrime.securesms.wallpaper.ChatWallpaper import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil @@ -117,8 +133,12 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) private lateinit var conversationOptionsMenuProvider: ConversationOptionsMenu.Provider private lateinit var layoutManager: SmoothScrollingLinearLayoutManager private lateinit var markReadHelper: MarkReadHelper + private lateinit var giphyMp4ProjectionRecycler: GiphyMp4ProjectionRecycler + private lateinit var addToContactsLauncher: ActivityResultLauncher override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + registerForResults() + conversationOptionsMenuProvider = ConversationOptionsMenu.Provider(ConversationOptionsMenuCallback(), disposables) markReadHelper = MarkReadHelper(ConversationId.forConversation(args.threadId), requireContext(), viewLifecycleOwner) @@ -166,6 +186,10 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) groupCallViewModel.peekGroupCall() } + private fun registerForResults() { + addToContactsLauncher = registerForActivityResult(AddToContactsContract()) {} + } + private fun onFirstRecipientLoad(recipient: Recipient) { Log.d(TAG, "onFirstRecipientLoad") @@ -183,7 +207,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) adapter.setPagingController(viewModel.pagingController) viewLifecycleOwner.lifecycle.addObserver(LastSeenPositionUpdater(adapter, layoutManager, viewModel)) binding.conversationItemRecycler.adapter = adapter - initializeGiphyMp4() + giphyMp4ProjectionRecycler = initializeGiphyMp4() binding.conversationItemRecycler.addItemDecoration( MultiselectItemDecoration( @@ -266,7 +290,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } disposables += groupCallViewModel.hasActiveGroupCall.subscribeBy(onNext = { - // invalidateOptionsMenu + invalidateOptionsMenu() binding.conversationGroupCallJoin.visible = it }) @@ -296,6 +320,17 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } } + private fun handleBlockJoinRequest(recipient: Recipient) { + disposables += conversationGroupViewModel.blockJoinRequests(recipient).subscribeBy { result -> + if (result.isFailure()) { + val failureReason = GroupErrors.getUserDisplayMessage((result as GroupBlockJoinRequestResult.Failure).reason) + Toast.makeText(requireContext(), failureReason, Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(requireContext(), R.string.ConversationFragment__blocked, Toast.LENGTH_SHORT).show() + } + } + } + private fun getVoiceNoteMediaController() = requireListener().voiceNoteMediaController private fun initializeGiphyMp4(): GiphyMp4ProjectionRecycler { @@ -309,13 +344,15 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) val callback = GiphyMp4ProjectionRecycler(holders) GiphyMp4PlaybackController.attach(binding.conversationItemRecycler, callback, maxPlayback) - binding.conversationItemRecycler.addItemDecoration(GiphyMp4ItemDecoration(callback) { translationY: Float -> - // TODO [alex] reactionsShade.setTranslationY(translationY + list.getHeight()) - }, 0) + binding.conversationItemRecycler.addItemDecoration( + GiphyMp4ItemDecoration(callback) { translationY: Float -> + // TODO [alex] reactionsShade.setTranslationY(translationY + list.getHeight()) + }, + 0 + ) return callback } - private inner class ConversationItemClickListener : ConversationAdapter.ItemClickListener { override fun onQuoteClicked(messageRecord: MmsMessageRecord?) { // TODO [alex] - ("Not yet implemented") @@ -352,7 +389,11 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onAddToContactsClicked(contact: Contact) { - // TODO [alex] - ("Not yet implemented") + disposables += AddToContactsContract.createIntentAndLaunch( + this@ConversationFragment, + addToContactsLauncher, + contact + ) } override fun onMessageSharedContactClicked(choices: MutableList) { @@ -374,12 +415,12 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) { - parentFragment ?: return + context ?: return ReactionsBottomSheetDialogFragment.create(messageId, isMms).show(parentFragmentManager, null) } override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) { - parentFragment ?: return + context ?: return RecipientBottomSheetDialogFragment.create(recipientId, groupId).show(parentFragmentManager, "BOTTOM") } @@ -476,11 +517,21 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onBlockJoinRequest(recipient: Recipient) { - // TODO [alex] - ("Not yet implemented") + MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.ConversationFragment__block_request) + .setMessage(getString(R.string.ConversationFragment__s_will_not_be_able_to_join_or_request_to_join_this_group_via_the_group_link, recipient.getDisplayName(requireContext()))) + .setNegativeButton(R.string.ConversationFragment__cancel, null) + .setPositiveButton(R.string.ConversationFragment__block_request_button) { _, _ -> handleBlockJoinRequest(recipient) } + .show() } override fun onRecipientNameClicked(target: RecipientId) { - // TODO [alex] ("Not yet implemented") + context ?: return + disposables += viewModel.recipient.firstOrError().observeOn(AndroidSchedulers.mainThread()).subscribeBy { + RecipientBottomSheetDialogFragment.create( + target, + it.groupId.orElse(null) + ).show(parentFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) + } } override fun onInviteToSignalClicked() { @@ -494,11 +545,16 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onActivatePaymentsClicked() { - // TODO [alex] -- ("Not yet implemented") + startActivity(Intent(requireContext(), PaymentsActivity::class.java)) } override fun onSendPaymentClicked(recipientId: RecipientId) { - // TODO [alex] -- ("Not yet implemented") + disposables += viewModel.recipient + .firstOrError() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy { + AttachmentManager.selectPayment(this@ConversationFragment, it) + } } override fun onScheduledIndicatorClicked(view: View, messageRecord: MessageRecord) { @@ -511,15 +567,40 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) { - // TODO [alex] -- ("Not yet implemented") + if (!messageRecord.hasGiftBadge()) { + return + } + + if (messageRecord.isOutgoing) { + ViewSentGiftBottomSheet.show(childFragmentManager, (messageRecord as MmsMessageRecord)) + } else { + ViewReceivedGiftBottomSheet.show(childFragmentManager, (messageRecord as MmsMessageRecord)) + } } override fun onGiftBadgeRevealed(messageRecord: MessageRecord) { - // TODO [alex] -- ("Not yet implemented") + viewModel.markGiftBadgeRevealed(messageRecord) } - override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) { - // TODO [alex] -- ("Not yet implemented") + override fun goToMediaPreview(parent: ConversationItem, sharedElement: View, args: MediaIntentFactory.MediaPreviewArgs) { + if (this@ConversationFragment.args.conversationScreenType.isInBubble) { + requireActivity().startActivity(create(requireActivity(), args.skipSharedElementTransition(true))) + return + } + + if (args.isVideoGif) { + val adapterPosition: Int = binding.conversationItemRecycler.getChildAdapterPosition(parent) + val holder: GiphyMp4ProjectionPlayerHolder? = giphyMp4ProjectionRecycler.getCurrentHolder(adapterPosition) + if (holder != null) { + parent.showProjectionArea() + holder.hide() + } + } + + sharedElement.transitionName = MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME + requireActivity().setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback()) + val options = ActivityOptions.makeSceneTransitionAnimation(requireActivity(), sharedElement, MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME) + requireActivity().startActivity(create(requireActivity(), args), options.toBundle()) } override fun onItemClick(item: MultiselectPart?) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt index a0617d35ca..92543b0b60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt @@ -124,4 +124,8 @@ class ConversationRepository(context: Context) { fun setLastVisibleMessageTimestamp(threadId: Long, lastVisibleMessageTimestamp: Long) { SignalExecutors.BOUNDED.submit { threads.setLastScrolled(threadId, lastVisibleMessageTimestamp) } } + + fun markGiftBadgeRevealed(messageId: Long) { + oldConversationRepository.markGiftBadgeRevealed(messageId) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 4567966c18..e95ad615b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -13,8 +13,10 @@ import org.thoughtcrime.securesms.conversation.ConversationIntents.Args import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper import org.thoughtcrime.securesms.conversation.colors.NameColor import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.hasGiftBadge import org.thoughtcrime.securesms.wallpaper.ChatWallpaper /** @@ -67,6 +69,12 @@ class ConversationViewModel( ) } + fun markGiftBadgeRevealed(messageRecord: MessageRecord) { + if (messageRecord.isOutgoing && messageRecord.hasGiftBadge()) { + repository.markGiftBadgeRevealed(messageRecord.id) + } + } + class Factory( private val args: Args, private val repository: ConversationRepository diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/groups/ConversationGroupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/groups/ConversationGroupViewModel.kt index e913b7131f..e2496aa6c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/groups/ConversationGroupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/groups/ConversationGroupViewModel.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2.groups import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -14,6 +15,8 @@ import org.thoughtcrime.securesms.database.GroupTable import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.GroupRecord import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil +import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult +import org.thoughtcrime.securesms.groups.v2.GroupManagementRepository import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId @@ -22,7 +25,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId * Manages group state and actions for conversations. */ class ConversationGroupViewModel( - private val threadId: Long + private val threadId: Long, + private val groupManagementRepository: GroupManagementRepository = GroupManagementRepository() ) : ViewModel() { private val disposables = CompositeDisposable() @@ -83,6 +87,12 @@ class ConversationGroupViewModel( return memberLevel.groupTableMemberLevel != GroupTable.MemberLevel.ADMINISTRATOR && memberLevel.isAnnouncementGroup } + fun blockJoinRequests(recipient: Recipient): Single { + return _recipient.firstOrError().flatMap { + groupManagementRepository.blockJoinRequests(it.requireGroupId().requireV2(), recipient) + }.observeOn(AndroidSchedulers.mainThread()) + } + private fun getActionableRequestingMembersCount(groupRecord: GroupRecord): Int { return if (groupRecord.isV2Group && groupRecord.memberLevel(Recipient.self()) == GroupTable.MemberLevel.ADMINISTRATOR) { groupRecord.requireV2GroupProperties() diff --git a/app/src/main/res/layout/conversation_input_panel.xml b/app/src/main/res/layout/conversation_input_panel.xml index 252b3a929b..bc1f00353f 100644 --- a/app/src/main/res/layout/conversation_input_panel.xml +++ b/app/src/main/res/layout/conversation_input_panel.xml @@ -1,7 +1,6 @@