2022-02-24 17:40:28 +00:00
|
|
|
package org.thoughtcrime.securesms.mediasend.v2.text
|
|
|
|
|
2022-04-06 15:25:06 +00:00
|
|
|
import android.content.pm.ActivityInfo
|
2022-02-24 17:40:28 +00:00
|
|
|
import android.os.Bundle
|
|
|
|
import android.view.View
|
2022-04-07 18:47:56 +00:00
|
|
|
import android.widget.Toast
|
2022-02-24 17:40:28 +00:00
|
|
|
import androidx.constraintlayout.widget.ConstraintLayout
|
2022-09-07 17:55:39 +00:00
|
|
|
import androidx.constraintlayout.widget.ConstraintSet
|
2022-02-24 17:40:28 +00:00
|
|
|
import androidx.core.view.drawToBitmap
|
2022-03-23 16:45:07 +00:00
|
|
|
import androidx.core.view.postDelayed
|
2022-09-07 17:55:39 +00:00
|
|
|
import androidx.core.view.updateLayoutParams
|
2022-02-24 17:40:28 +00:00
|
|
|
import androidx.fragment.app.Fragment
|
|
|
|
import androidx.fragment.app.viewModels
|
|
|
|
import androidx.navigation.fragment.findNavController
|
2022-11-29 17:12:56 +00:00
|
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
2022-04-07 18:47:56 +00:00
|
|
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
2022-02-24 17:40:28 +00:00
|
|
|
import org.thoughtcrime.securesms.R
|
2022-04-07 18:47:56 +00:00
|
|
|
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
2022-07-26 19:55:19 +00:00
|
|
|
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
|
2022-09-07 17:55:39 +00:00
|
|
|
import org.thoughtcrime.securesms.databinding.StoriesTextPostCreationFragmentBinding
|
2022-10-11 15:34:22 +00:00
|
|
|
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
2022-02-24 17:40:28 +00:00
|
|
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository
|
|
|
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
2022-10-11 15:34:22 +00:00
|
|
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
|
2022-09-07 17:55:39 +00:00
|
|
|
import org.thoughtcrime.securesms.mediasend.CameraDisplay
|
2022-02-24 17:40:28 +00:00
|
|
|
import org.thoughtcrime.securesms.mediasend.v2.HudCommand
|
|
|
|
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
2022-07-26 19:55:19 +00:00
|
|
|
import org.thoughtcrime.securesms.mediasend.v2.stories.StoriesMultiselectForwardActivity
|
2022-04-07 18:47:56 +00:00
|
|
|
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendRepository
|
|
|
|
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
|
2022-07-11 15:54:30 +00:00
|
|
|
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
2022-07-26 19:55:19 +00:00
|
|
|
import org.thoughtcrime.securesms.stories.Stories
|
2022-02-24 17:40:28 +00:00
|
|
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
2022-08-01 16:13:45 +00:00
|
|
|
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
|
2022-07-26 19:55:19 +00:00
|
|
|
import org.thoughtcrime.securesms.util.visible
|
2022-10-11 15:34:22 +00:00
|
|
|
import java.util.Optional
|
2022-02-24 17:40:28 +00:00
|
|
|
|
2022-07-11 15:54:30 +00:00
|
|
|
class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creation_fragment), TextStoryPostTextEntryFragment.Callback, SafetyNumberBottomSheet.Callbacks {
|
2022-02-24 17:40:28 +00:00
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
private var _binding: StoriesTextPostCreationFragmentBinding? = null
|
|
|
|
private val binding: StoriesTextPostCreationFragmentBinding get() = _binding!!
|
2022-02-24 17:40:28 +00:00
|
|
|
|
|
|
|
private val sharedViewModel: MediaSelectionViewModel by viewModels(
|
|
|
|
ownerProducer = {
|
|
|
|
requireActivity()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
private val viewModel: TextStoryPostCreationViewModel by viewModels(
|
|
|
|
ownerProducer = {
|
|
|
|
requireActivity()
|
2022-04-07 18:47:56 +00:00
|
|
|
},
|
|
|
|
factoryProducer = {
|
|
|
|
TextStoryPostCreationViewModel.Factory(TextStoryPostSendRepository())
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
private val linkPreviewViewModel: LinkPreviewViewModel by viewModels(
|
|
|
|
ownerProducer = {
|
|
|
|
requireActivity()
|
|
|
|
},
|
|
|
|
factoryProducer = {
|
2022-09-16 15:29:45 +00:00
|
|
|
LinkPreviewViewModel.Factory(LinkPreviewRepository(), true)
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
private val lifecycleDisposable = LifecycleDisposable()
|
|
|
|
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
_binding = StoriesTextPostCreationFragmentBinding.bind(view)
|
2022-02-24 17:40:28 +00:00
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.storyTextPost.showCloseButton()
|
2022-02-24 17:40:28 +00:00
|
|
|
|
|
|
|
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
|
|
|
lifecycleDisposable += sharedViewModel.hudCommands.subscribe {
|
|
|
|
if (it == HudCommand.GoToCapture) {
|
|
|
|
findNavController().popBackStack()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
viewModel.typeface.observe(viewLifecycleOwner) { typeface ->
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.storyTextPost.setTypeface(typeface)
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.backgroundSelector.background = state.backgroundColor.chatBubbleMask
|
|
|
|
binding.storyTextPost.bindFromCreationState(state)
|
2022-02-24 17:40:28 +00:00
|
|
|
|
|
|
|
if (state.linkPreviewUri != null) {
|
|
|
|
linkPreviewViewModel.onTextChanged(requireContext(), state.linkPreviewUri, 0, state.linkPreviewUri.lastIndex)
|
|
|
|
} else {
|
|
|
|
linkPreviewViewModel.onSend()
|
|
|
|
}
|
|
|
|
|
|
|
|
val canSend = state.body.isNotEmpty() || !state.linkPreviewUri.isNullOrEmpty()
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.send.alpha = if (canSend) 1f else 0.5f
|
|
|
|
binding.send.isEnabled = canSend
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
|
|
|
|
2022-08-01 16:13:45 +00:00
|
|
|
LiveDataUtil.combineLatest(viewModel.state, linkPreviewViewModel.linkPreviewState) { viewState, linkState ->
|
|
|
|
Pair(viewState.body.isBlank(), linkState)
|
|
|
|
}.observe(viewLifecycleOwner) { (useLargeThumb, linkState) ->
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.storyTextPost.bindLinkPreviewState(linkState, View.GONE, useLargeThumb)
|
|
|
|
binding.storyTextPost.postAdjustLinkPreviewTranslationY()
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.storyTextPost.setTextViewClickListener {
|
|
|
|
binding.storyTextPost.hidePostContent()
|
|
|
|
binding.storyTextPost.isEnabled = false
|
2022-02-24 17:40:28 +00:00
|
|
|
TextStoryPostTextEntryFragment().show(childFragmentManager, null)
|
|
|
|
}
|
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.backgroundProtection.setOnClickListener {
|
2022-02-24 17:40:28 +00:00
|
|
|
viewModel.cycleBackgroundColor()
|
|
|
|
}
|
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.addLinkProtection.setOnClickListener {
|
2022-02-24 17:40:28 +00:00
|
|
|
TextStoryPostLinkEntryFragment().show(childFragmentManager, null)
|
|
|
|
}
|
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.storyTextPost.setLinkPreviewCloseListener {
|
2022-02-24 17:40:28 +00:00
|
|
|
viewModel.setLinkPreview("")
|
|
|
|
}
|
|
|
|
|
2022-07-26 19:55:19 +00:00
|
|
|
val launcher = registerForActivityResult(StoriesMultiselectForwardActivity.SelectionContract()) {
|
|
|
|
if (it.isNotEmpty()) {
|
|
|
|
performSend(it.toSet())
|
|
|
|
} else {
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.send.isClickable = true
|
|
|
|
binding.sendInProgressIndicator.visible = false
|
2022-07-26 19:55:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.send.setOnClickListener {
|
|
|
|
binding.send.isClickable = false
|
|
|
|
binding.sendInProgressIndicator.visible = true
|
2022-07-26 19:55:19 +00:00
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.storyTextPost.hideCloseButton()
|
2022-04-07 18:47:56 +00:00
|
|
|
|
|
|
|
val contacts = (sharedViewModel.destination.getRecipientSearchKeyList() + sharedViewModel.destination.getRecipientSearchKey())
|
|
|
|
.filterIsInstance(ContactSearchKey::class.java)
|
|
|
|
.toSet()
|
|
|
|
|
|
|
|
if (contacts.isEmpty()) {
|
2022-09-07 17:55:39 +00:00
|
|
|
val bitmap = binding.storyTextPost.drawToBitmap()
|
2022-07-26 19:55:19 +00:00
|
|
|
viewModel.compressToBlob(bitmap).observeOn(AndroidSchedulers.mainThread()).subscribe { uri ->
|
|
|
|
launcher.launch(
|
|
|
|
StoriesMultiselectForwardActivity.Args(
|
|
|
|
MultiselectForwardFragmentArgs(
|
2022-08-26 15:48:32 +00:00
|
|
|
title = R.string.MediaReviewFragment__send_to,
|
2022-07-26 19:55:19 +00:00
|
|
|
canSendToNonPush = false,
|
|
|
|
storySendRequirements = Stories.MediaTransform.SendRequirements.VALID_DURATION,
|
|
|
|
isSearchEnabled = false
|
|
|
|
),
|
|
|
|
listOf(uri)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2022-11-29 17:12:56 +00:00
|
|
|
} else if (sharedViewModel.isAddToGroupStoryFlow) {
|
|
|
|
MaterialAlertDialogBuilder(requireContext())
|
|
|
|
.setMessage(getString(R.string.MediaReviewFragment__add_to_the_group_story, sharedViewModel.state.value!!.recipient!!.getDisplayName(requireContext())))
|
|
|
|
.setPositiveButton(R.string.MediaReviewFragment__add_to_story) { _, _ -> performSend(contacts) }
|
|
|
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
|
|
|
.show()
|
2022-04-07 18:47:56 +00:00
|
|
|
} else {
|
2022-07-05 15:58:02 +00:00
|
|
|
performSend(contacts)
|
2022-04-07 18:47:56 +00:00
|
|
|
}
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
2022-09-07 17:55:39 +00:00
|
|
|
|
|
|
|
initializeScenePositioning()
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onResume() {
|
|
|
|
super.onResume()
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.storyTextPost.showCloseButton()
|
2022-04-06 15:25:06 +00:00
|
|
|
requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
|
|
|
|
2022-09-07 17:55:39 +00:00
|
|
|
override fun onDestroy() {
|
|
|
|
super.onDestroy()
|
|
|
|
_binding = null
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:40:28 +00:00
|
|
|
override fun onTextStoryPostTextEntryDismissed() {
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.storyTextPost.postDelayed(resources.getInteger(R.integer.text_entry_exit_duration).toLong()) {
|
|
|
|
binding.storyTextPost.showPostContent()
|
|
|
|
binding.storyTextPost.isEnabled = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun initializeScenePositioning() {
|
2022-09-13 14:16:01 +00:00
|
|
|
val cameraDisplay = CameraDisplay.getDisplay(requireActivity())
|
2022-09-07 17:55:39 +00:00
|
|
|
|
|
|
|
if (!cameraDisplay.roundViewFinderCorners) {
|
|
|
|
binding.storyTextPostCard.radius = 0f
|
|
|
|
}
|
|
|
|
|
|
|
|
binding.send.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
|
|
|
bottomMargin = cameraDisplay.getToggleBottomMargin()
|
|
|
|
}
|
|
|
|
|
|
|
|
listOf(binding.backgroundProtection, binding.addLinkProtection).forEach {
|
|
|
|
it.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
|
|
|
bottomMargin += cameraDisplay.getCameraCaptureMarginBottom(resources)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cameraDisplay.getCameraViewportGravity() == CameraDisplay.CameraViewportGravity.CENTER) {
|
|
|
|
val constraintSet = ConstraintSet()
|
|
|
|
constraintSet.clone(binding.scene)
|
|
|
|
constraintSet.connect(R.id.story_text_post_card, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
|
|
|
|
constraintSet.applyTo(binding.scene)
|
|
|
|
} else {
|
|
|
|
binding.storyTextPostCard.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
|
|
|
bottomMargin = cameraDisplay.getCameraViewportMarginBottom()
|
|
|
|
}
|
2022-03-23 16:45:07 +00:00
|
|
|
}
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|
2022-04-07 18:47:56 +00:00
|
|
|
|
|
|
|
private fun performSend(contacts: Set<ContactSearchKey>) {
|
|
|
|
lifecycleDisposable += viewModel.send(
|
|
|
|
contacts = contacts,
|
2022-10-11 15:34:22 +00:00
|
|
|
getLinkPreview()
|
2022-04-07 18:47:56 +00:00
|
|
|
).observeOn(AndroidSchedulers.mainThread()).subscribe { result ->
|
|
|
|
when (result) {
|
|
|
|
TextStoryPostSendResult.Success -> {
|
|
|
|
Toast.makeText(requireContext(), R.string.TextStoryPostCreationFragment__sent_story, Toast.LENGTH_SHORT).show()
|
|
|
|
requireActivity().finish()
|
|
|
|
}
|
|
|
|
TextStoryPostSendResult.Failure -> {
|
|
|
|
Toast.makeText(requireContext(), R.string.TextStoryPostCreationFragment__failed_to_send_story, Toast.LENGTH_SHORT).show()
|
|
|
|
requireActivity().finish()
|
|
|
|
}
|
|
|
|
is TextStoryPostSendResult.UntrustedRecordsError -> {
|
2022-09-07 17:55:39 +00:00
|
|
|
binding.send.isClickable = true
|
|
|
|
binding.sendInProgressIndicator.visible = false
|
2022-07-26 19:55:19 +00:00
|
|
|
|
2022-07-11 15:54:30 +00:00
|
|
|
SafetyNumberBottomSheet
|
|
|
|
.forIdentityRecordsAndDestinations(result.untrustedRecords, contacts.toList())
|
|
|
|
.show(childFragmentManager)
|
2022-04-07 18:47:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-11 15:54:30 +00:00
|
|
|
|
2022-10-11 15:34:22 +00:00
|
|
|
private fun getLinkPreview(): LinkPreview? {
|
|
|
|
val linkPreviewState: LinkPreviewState = linkPreviewViewModel.linkPreviewState.value ?: return null
|
|
|
|
|
|
|
|
return if (linkPreviewState.linkPreview.isPresent) {
|
|
|
|
linkPreviewState.linkPreview.get()
|
|
|
|
} else if (!linkPreviewState.activeUrlForError.isNullOrEmpty()) {
|
|
|
|
LinkPreview(linkPreviewState.activeUrlForError!!, "", "", 0L, Optional.empty())
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-11 15:54:30 +00:00
|
|
|
override fun sendAnywayAfterSafetyNumberChangedInBottomSheet(destinations: List<ContactSearchKey.RecipientSearchKey>) {
|
|
|
|
performSend(destinations.toSet())
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onMessageResentAfterSafetyNumberChangeInBottomSheet() {
|
|
|
|
error("Unsupported, we do not hand in a message id.")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCanceled() = Unit
|
2022-02-24 17:40:28 +00:00
|
|
|
}
|