mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-26 11:51:10 +01:00
Enable sharing to stories and refactor share activity.
This commit is contained in:
committed by
Greyson Parrelli
parent
fd4543ffe0
commit
523537cf05
@@ -19,6 +19,7 @@ import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.animation.ArgbEvaluatorCompat
|
||||
import org.signal.core.util.BreakIteratorCompat
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
@@ -26,15 +27,18 @@ import org.thoughtcrime.securesms.TransportOption
|
||||
import org.thoughtcrime.securesms.TransportOptions
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchState
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.SearchConfigurationProvider
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
||||
import org.thoughtcrime.securesms.mediasend.v2.review.MediaReviewFragment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationViewModel
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendRepository
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
@@ -56,7 +60,11 @@ class MediaSelectionActivity :
|
||||
|
||||
lateinit var viewModel: MediaSelectionViewModel
|
||||
|
||||
private val textViewModel: TextStoryPostCreationViewModel by viewModels()
|
||||
private val textViewModel: TextStoryPostCreationViewModel by viewModels(
|
||||
factoryProducer = {
|
||||
TextStoryPostCreationViewModel.Factory(TextStoryPostSendRepository())
|
||||
}
|
||||
)
|
||||
|
||||
private val destination: MediaSelectionDestination
|
||||
get() = MediaSelectionDestination.fromBundle(requireNotNull(intent.getBundleExtra(DESTINATION)))
|
||||
@@ -64,6 +72,12 @@ class MediaSelectionActivity :
|
||||
private val isStory: Boolean
|
||||
get() = intent.getBooleanExtra(IS_STORY, false)
|
||||
|
||||
private val shareToTextStory: Boolean
|
||||
get() = intent.getBooleanExtra(AS_TEXT_STORY, false)
|
||||
|
||||
private val draftText: CharSequence?
|
||||
get() = intent.getCharSequenceExtra(MESSAGE)
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES
|
||||
super.attachBaseContext(newBase)
|
||||
@@ -74,7 +88,7 @@ class MediaSelectionActivity :
|
||||
|
||||
val transportOption: TransportOption = requireNotNull(intent.getParcelableExtra(TRANSPORT_OPTION))
|
||||
val initialMedia: List<Media> = intent.getParcelableArrayListExtra(MEDIA) ?: listOf()
|
||||
val message: CharSequence? = intent.getCharSequenceExtra(MESSAGE)
|
||||
val message: CharSequence? = if (shareToTextStory) null else draftText
|
||||
val isReply: Boolean = intent.getBooleanExtra(IS_REPLY, false)
|
||||
|
||||
val factory = MediaSelectionViewModel.Factory(destination, transportOption, initialMedia, message, isReply, isStory, MediaSelectionRepository(this))
|
||||
@@ -100,6 +114,10 @@ class MediaSelectionActivity :
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (shareToTextStory) {
|
||||
initializeTextStory()
|
||||
}
|
||||
|
||||
cameraSwitch.isSelected = true
|
||||
|
||||
val navHostFragment = NavHostFragment.create(R.navigation.media)
|
||||
@@ -168,6 +186,25 @@ class MediaSelectionActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeTextStory() {
|
||||
val message = draftText?.toString() ?: return
|
||||
val firstLink = LinkPreviewUtil.findValidPreviewUrls(message).findFirst()
|
||||
val firstLinkUrl = firstLink.map { it.url }.orElse(null)
|
||||
|
||||
val iterator = BreakIteratorCompat.getInstance()
|
||||
iterator.setText(message)
|
||||
val trimmedMessage = iterator.take(700).toString()
|
||||
|
||||
if (firstLinkUrl == message) {
|
||||
textViewModel.setLinkPreview(firstLinkUrl)
|
||||
} else if (firstLinkUrl != null) {
|
||||
textViewModel.setLinkPreview(firstLinkUrl)
|
||||
textViewModel.setBody(trimmedMessage.replace(firstLinkUrl, "").trim())
|
||||
} else {
|
||||
textViewModel.setBody(trimmedMessage.trim())
|
||||
}
|
||||
}
|
||||
|
||||
private fun canDisplayStorySwitch(): Boolean {
|
||||
return Stories.isFeatureEnabled() &&
|
||||
FeatureFlags.storiesTextPosts() &&
|
||||
@@ -292,6 +329,11 @@ class MediaSelectionActivity :
|
||||
private inner class OnBackPressed : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
val navController = Navigation.findNavController(this@MediaSelectionActivity, R.id.fragment_container)
|
||||
|
||||
if (shareToTextStory && navController.currentDestination?.id == R.id.textStoryPostCreationFragment) {
|
||||
finish()
|
||||
}
|
||||
|
||||
if (!navController.popBackStack()) {
|
||||
finish()
|
||||
}
|
||||
@@ -310,6 +352,7 @@ class MediaSelectionActivity :
|
||||
private const val DESTINATION = "destination"
|
||||
private const val IS_REPLY = "is_reply"
|
||||
private const val IS_STORY = "is_story"
|
||||
private const val AS_TEXT_STORY = "as_text_story"
|
||||
|
||||
@JvmStatic
|
||||
fun camera(context: Context): Intent {
|
||||
@@ -383,15 +426,18 @@ class MediaSelectionActivity :
|
||||
context: Context,
|
||||
transportOption: TransportOption,
|
||||
media: List<Media>,
|
||||
recipientIds: List<RecipientId>,
|
||||
message: CharSequence?
|
||||
recipientSearchKeys: List<ContactSearchKey.RecipientSearchKey>,
|
||||
message: CharSequence?,
|
||||
asTextStory: Boolean
|
||||
): Intent {
|
||||
return buildIntent(
|
||||
context = context,
|
||||
transportOption = transportOption,
|
||||
media = media,
|
||||
destination = MediaSelectionDestination.MultipleRecipients(recipientIds),
|
||||
message = message
|
||||
destination = MediaSelectionDestination.MultipleRecipients(recipientSearchKeys),
|
||||
message = message,
|
||||
asTextStory = asTextStory,
|
||||
startAction = if (asTextStory) R.id.action_directly_to_textPostCreationFragment else -1
|
||||
)
|
||||
}
|
||||
|
||||
@@ -403,7 +449,8 @@ class MediaSelectionActivity :
|
||||
destination: MediaSelectionDestination = MediaSelectionDestination.ChooseAfterMediaSelection,
|
||||
message: CharSequence? = null,
|
||||
isReply: Boolean = false,
|
||||
isStory: Boolean = false
|
||||
isStory: Boolean = false,
|
||||
asTextStory: Boolean = false
|
||||
): Intent {
|
||||
return Intent(context, MediaSelectionActivity::class.java).apply {
|
||||
putExtra(START_ACTION, startAction)
|
||||
@@ -413,6 +460,7 @@ class MediaSelectionActivity :
|
||||
putExtra(DESTINATION, destination.toBundle())
|
||||
putExtra(IS_REPLY, isReply)
|
||||
putExtra(IS_STORY, isStory)
|
||||
putExtra(AS_TEXT_STORY, asTextStory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.mediasend.v2
|
||||
|
||||
import android.os.Bundle
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
sealed class MediaSelectionDestination {
|
||||
@@ -30,7 +29,7 @@ sealed class MediaSelectionDestination {
|
||||
}
|
||||
|
||||
class SingleRecipient(private val id: RecipientId) : MediaSelectionDestination() {
|
||||
override fun getRecipientSearchKey(): RecipientSearchKey = ContactSearchKey.KnownRecipient(id)
|
||||
override fun getRecipientSearchKey(): ContactSearchKey.RecipientSearchKey = ContactSearchKey.RecipientSearchKey.KnownRecipient(id)
|
||||
|
||||
override fun toBundle(): Bundle {
|
||||
return Bundle().apply {
|
||||
@@ -39,18 +38,25 @@ sealed class MediaSelectionDestination {
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleRecipients(val recipientIds: List<RecipientId>) : MediaSelectionDestination() {
|
||||
override fun getRecipientSearchKeyList(): List<RecipientSearchKey> = recipientIds.map { ContactSearchKey.KnownRecipient(it) }
|
||||
class MultipleRecipients(val recipientSearchKeys: List<ContactSearchKey.RecipientSearchKey>) : MediaSelectionDestination() {
|
||||
|
||||
companion object {
|
||||
fun fromParcel(parcelables: List<ContactSearchKey.ParcelableRecipientSearchKey>): MultipleRecipients {
|
||||
return MultipleRecipients(parcelables.map { it.asContactSearchKey() }.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRecipientSearchKeyList(): List<ContactSearchKey.RecipientSearchKey> = recipientSearchKeys
|
||||
|
||||
override fun toBundle(): Bundle {
|
||||
return Bundle().apply {
|
||||
putParcelableArrayList(RECIPIENT_LIST, ArrayList(recipientIds))
|
||||
putParcelableArrayList(RECIPIENT_LIST, ArrayList(recipientSearchKeys.map { it.requireParcelable() }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun getRecipientSearchKey(): RecipientSearchKey? = null
|
||||
open fun getRecipientSearchKeyList(): List<RecipientSearchKey> = emptyList()
|
||||
open fun getRecipientSearchKey(): ContactSearchKey.RecipientSearchKey? = null
|
||||
open fun getRecipientSearchKeyList(): List<ContactSearchKey.RecipientSearchKey> = emptyList()
|
||||
|
||||
abstract fun toBundle(): Bundle
|
||||
|
||||
@@ -65,7 +71,7 @@ sealed class MediaSelectionDestination {
|
||||
bundle.containsKey(WALLPAPER) -> Wallpaper
|
||||
bundle.containsKey(AVATAR) -> Avatar
|
||||
bundle.containsKey(RECIPIENT) -> SingleRecipient(requireNotNull(bundle.getParcelable(RECIPIENT)))
|
||||
bundle.containsKey(RECIPIENT_LIST) -> MultipleRecipients(requireNotNull(bundle.getParcelableArrayList(RECIPIENT_LIST)))
|
||||
bundle.containsKey(RECIPIENT_LIST) -> MultipleRecipients.fromParcel(requireNotNull(bundle.getParcelableArrayList(RECIPIENT_LIST)))
|
||||
else -> ChooseAfterMediaSelection
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.imageeditor.core.model.EditorModel
|
||||
import org.thoughtcrime.securesms.TransportOption
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
@@ -72,8 +71,8 @@ class MediaSelectionRepository(context: Context) {
|
||||
message: CharSequence?,
|
||||
isSms: Boolean,
|
||||
isViewOnce: Boolean,
|
||||
singleContact: RecipientSearchKey?,
|
||||
contacts: List<RecipientSearchKey>,
|
||||
singleContact: ContactSearchKey.RecipientSearchKey?,
|
||||
contacts: List<ContactSearchKey.RecipientSearchKey>,
|
||||
mentions: List<Mention>,
|
||||
transport: TransportOption
|
||||
): Maybe<MediaSendActivityResult> {
|
||||
@@ -198,14 +197,14 @@ class MediaSelectionRepository(context: Context) {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun sendMessages(contacts: List<RecipientSearchKey>, body: String, preUploadResults: Collection<PreUploadResult>, mentions: List<Mention>, isViewOnce: Boolean) {
|
||||
private fun sendMessages(contacts: List<ContactSearchKey.RecipientSearchKey>, body: String, preUploadResults: Collection<PreUploadResult>, mentions: List<Mention>, isViewOnce: Boolean) {
|
||||
val broadcastMessages: MutableList<OutgoingSecureMediaMessage> = ArrayList(contacts.size)
|
||||
val storyMessages: MutableMap<PreUploadResult, MutableList<OutgoingSecureMediaMessage>> = mutableMapOf()
|
||||
val distributionListSentTimestamps: MutableMap<PreUploadResult, Long> = mutableMapOf()
|
||||
|
||||
for (contact in contacts) {
|
||||
val recipient = Recipient.resolved(contact.recipientId)
|
||||
val isStory = contact is ContactSearchKey.Story || recipient.isDistributionList
|
||||
val isStory = contact.isStory || recipient.isDistributionList
|
||||
|
||||
if (isStory && recipient.isActiveGroup) {
|
||||
SignalDatabase.groups.markDisplayAsStory(recipient.requireGroupId())
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.reactivex.rxjava3.disposables.Disposable
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import org.thoughtcrime.securesms.TransportOption
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation
|
||||
import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
||||
import org.thoughtcrime.securesms.mediasend.VideoEditorFragment
|
||||
@@ -280,7 +280,7 @@ class MediaSelectionViewModel(
|
||||
}
|
||||
|
||||
fun send(
|
||||
selectedContacts: List<RecipientSearchKey> = emptyList()
|
||||
selectedContacts: List<ContactSearchKey.RecipientSearchKey> = emptyList()
|
||||
): Maybe<MediaSendActivityResult> {
|
||||
return UntrustedRecords.checkForBadIdentityRecords(selectedContacts.toSet()).andThen(
|
||||
repository.send(
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.core.util.Consumer
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
object UntrustedRecords {
|
||||
|
||||
fun checkForBadIdentityRecords(contactSearchKeys: Set<RecipientSearchKey>): Completable {
|
||||
fun checkForBadIdentityRecords(contactSearchKeys: Set<ContactSearchKey.RecipientSearchKey>): Completable {
|
||||
return Completable.fromAction {
|
||||
val untrustedRecords: List<IdentityRecord> = checkForBadIdentityRecordsSync(contactSearchKeys)
|
||||
if (untrustedRecords.isNotEmpty()) {
|
||||
@@ -21,13 +21,13 @@ object UntrustedRecords {
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun checkForBadIdentityRecords(contactSearchKeys: Set<RecipientSearchKey>, consumer: Consumer<List<IdentityRecord>>) {
|
||||
fun checkForBadIdentityRecords(contactSearchKeys: Set<ContactSearchKey.RecipientSearchKey>, consumer: Consumer<List<IdentityRecord>>) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
consumer.accept(checkForBadIdentityRecordsSync(contactSearchKeys))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkForBadIdentityRecordsSync(contactSearchKeys: Set<RecipientSearchKey>): List<IdentityRecord> {
|
||||
private fun checkForBadIdentityRecordsSync(contactSearchKeys: Set<ContactSearchKey.RecipientSearchKey>): List<IdentityRecord> {
|
||||
val recipients: List<Recipient> = contactSearchKeys
|
||||
.map { Recipient.resolved(it.recipientId) }
|
||||
.map { recipient ->
|
||||
|
||||
@@ -28,7 +28,6 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.TransportOption
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
||||
@@ -140,7 +139,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
|
||||
}
|
||||
|
||||
setFragmentResultListener(MultiselectForwardFragment.RESULT_KEY) { _, bundle ->
|
||||
val parcelizedKeys: List<ContactSearchKey.ParcelableContactSearchKey> = bundle.getParcelableArrayList(MultiselectForwardFragment.RESULT_SELECTION)!!
|
||||
val parcelizedKeys: List<ContactSearchKey.ParcelableRecipientSearchKey> = bundle.getParcelableArrayList(MultiselectForwardFragment.RESULT_SELECTION)!!
|
||||
val contactSearchKeys = parcelizedKeys.map { it.asContactSearchKey() }
|
||||
performSend(contactSearchKeys)
|
||||
}
|
||||
@@ -269,7 +268,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
|
||||
.alpha(1f)
|
||||
|
||||
sharedViewModel
|
||||
.send(selection.filterIsInstance(RecipientSearchKey::class.java))
|
||||
.send(selection.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java))
|
||||
.subscribe(
|
||||
{ result -> callback.onSentWithResult(result) },
|
||||
{ error -> callback.onSendError(error) },
|
||||
|
||||
@@ -80,7 +80,7 @@ class ChooseGroupStoryBottomSheet : FixedRoundedCornerBottomSheetDialogFragment(
|
||||
|
||||
mediator.getSelectionState().observe(viewLifecycleOwner) { state ->
|
||||
adapter.submitList(
|
||||
state.filterIsInstance(ContactSearchKey.Story::class.java)
|
||||
state.filterIsInstance(ContactSearchKey.RecipientSearchKey.Story::class.java)
|
||||
.map { it.recipientId }
|
||||
.mapIndexed { index, recipientId ->
|
||||
ShareSelectionMappingModel(
|
||||
@@ -144,7 +144,7 @@ class ChooseGroupStoryBottomSheet : FixedRoundedCornerBottomSheetDialogFragment(
|
||||
RESULT_SET,
|
||||
ArrayList(
|
||||
mediator.getSelectedContacts()
|
||||
.filterIsInstance(ContactSearchKey.Story::class.java)
|
||||
.filterIsInstance(ContactSearchKey.RecipientSearchKey.Story::class.java)
|
||||
.map { it.recipientId }
|
||||
)
|
||||
)
|
||||
|
||||
@@ -38,9 +38,9 @@ class ChooseStoryTypeBottomSheet : DSLSettingsBottomSheetFragment(
|
||||
),
|
||||
icon = DSLSettingsIcon.from(
|
||||
R.drawable.ic_plus_24,
|
||||
R.color.core_grey_15,
|
||||
R.color.signal_icon_tint_primary,
|
||||
R.drawable.circle_tintable,
|
||||
R.color.core_grey_80,
|
||||
R.color.signal_button_secondary_ripple,
|
||||
DimensionUnit.DP.toPixels(8f).toInt()
|
||||
),
|
||||
onClick = {
|
||||
@@ -60,9 +60,9 @@ class ChooseStoryTypeBottomSheet : DSLSettingsBottomSheetFragment(
|
||||
),
|
||||
icon = DSLSettingsIcon.from(
|
||||
R.drawable.ic_group_outline_24,
|
||||
R.color.core_grey_15,
|
||||
R.color.signal_icon_tint_primary,
|
||||
R.drawable.circle_tintable,
|
||||
R.color.core_grey_80,
|
||||
R.color.signal_button_secondary_ripple,
|
||||
DimensionUnit.DP.toPixels(8f).toInt()
|
||||
),
|
||||
onClick = {
|
||||
|
||||
@@ -98,4 +98,7 @@ object TextStoryBackgroundColors {
|
||||
|
||||
return backgroundColors[indexOfNextColor]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getRandomBackgroundColor() = backgroundColors.random()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.mediasend.v2.text
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.drawToBitmap
|
||||
@@ -10,12 +11,19 @@ import androidx.core.view.postDelayed
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
||||
import org.thoughtcrime.securesms.mediasend.v2.HudCommand
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendRepository
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostView
|
||||
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
|
||||
import org.thoughtcrime.securesms.stories.settings.hide.HideStoryFromDialogFragment
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
@@ -35,6 +43,9 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||
private val viewModel: TextStoryPostCreationViewModel by viewModels(
|
||||
ownerProducer = {
|
||||
requireActivity()
|
||||
},
|
||||
factoryProducer = {
|
||||
TextStoryPostCreationViewModel.Factory(TextStoryPostSendRepository())
|
||||
}
|
||||
)
|
||||
|
||||
@@ -113,8 +124,33 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||
|
||||
send.setOnClickListener {
|
||||
storyTextPostView.hideCloseButton()
|
||||
viewModel.setBitmap(storyTextPostView.drawToBitmap())
|
||||
findNavController().safeNavigate(R.id.action_textStoryPostCreationFragment_to_textStoryPostSendFragment)
|
||||
|
||||
val contacts = (sharedViewModel.destination.getRecipientSearchKeyList() + sharedViewModel.destination.getRecipientSearchKey())
|
||||
.filterIsInstance(ContactSearchKey::class.java)
|
||||
.toSet()
|
||||
|
||||
if (contacts.isEmpty()) {
|
||||
viewModel.setBitmap(storyTextPostView.drawToBitmap())
|
||||
findNavController().safeNavigate(R.id.action_textStoryPostCreationFragment_to_textStoryPostSendFragment)
|
||||
} else {
|
||||
send.isClickable = false
|
||||
StoryDialogs.guardWithAddToYourStoryDialog(
|
||||
contacts = contacts,
|
||||
context = requireContext(),
|
||||
onAddToStory = {
|
||||
performSend(contacts)
|
||||
},
|
||||
onEditViewers = {
|
||||
send.isClickable = true
|
||||
storyTextPostView.hideCloseButton()
|
||||
HideStoryFromDialogFragment().show(childFragmentManager, null)
|
||||
},
|
||||
onCancel = {
|
||||
send.isClickable = true
|
||||
storyTextPostView.hideCloseButton()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,4 +166,26 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||
storyTextPostView.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun performSend(contacts: Set<ContactSearchKey>) {
|
||||
lifecycleDisposable += viewModel.send(
|
||||
contacts = contacts,
|
||||
linkPreviewViewModel.linkPreviewState.value?.linkPreview?.orElse(null)
|
||||
).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 -> {
|
||||
send.isClickable = true
|
||||
SafetyNumberChangeDialog.show(childFragmentManager, result.untrustedRecords)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,25 @@ import androidx.annotation.ColorInt
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.fonts.Fonts
|
||||
import org.thoughtcrime.securesms.fonts.TextFont
|
||||
import org.thoughtcrime.securesms.fonts.TextToScript
|
||||
import org.thoughtcrime.securesms.fonts.TypefaceCache
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendRepository
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
class TextStoryPostCreationViewModel : ViewModel() {
|
||||
class TextStoryPostCreationViewModel(private val repository: TextStoryPostSendRepository) : ViewModel() {
|
||||
|
||||
private val store = Store(TextStoryPostCreationState())
|
||||
private val textFontSubject: Subject<TextFont> = BehaviorSubject.create()
|
||||
@@ -57,30 +60,6 @@ class TextStoryPostCreationViewModel : ViewModel() {
|
||||
internalThumbnail.value = bitmap
|
||||
}
|
||||
|
||||
private fun asyncFontEmitter(async: Fonts.FontResult.Async): Observable<Typeface> {
|
||||
return Observable.create {
|
||||
it.onNext(async.placeholder)
|
||||
|
||||
val listener = object : FutureTaskListener<Typeface> {
|
||||
override fun onSuccess(result: Typeface) {
|
||||
it.onNext(result)
|
||||
it.onComplete()
|
||||
}
|
||||
|
||||
override fun onFailure(exception: ExecutionException?) {
|
||||
Log.w(TAG, "Failed to load remote font.", exception)
|
||||
it.onComplete()
|
||||
}
|
||||
}
|
||||
|
||||
it.setCancellable {
|
||||
async.future.removeListener(listener)
|
||||
}
|
||||
|
||||
async.future.addListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
thumbnail.value?.recycle()
|
||||
@@ -144,6 +123,20 @@ class TextStoryPostCreationViewModel : ViewModel() {
|
||||
temporaryBodySubject.onNext(temporaryBody)
|
||||
}
|
||||
|
||||
fun send(contacts: Set<ContactSearchKey>, linkPreview: LinkPreview?): Single<TextStoryPostSendResult> {
|
||||
return repository.send(
|
||||
contacts,
|
||||
store.state,
|
||||
linkPreview
|
||||
)
|
||||
}
|
||||
|
||||
class Factory(private val repository: TextStoryPostSendRepository) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(TextStoryPostCreationViewModel(repository)) as T
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(TextStoryPostCreationViewModel::class.java)
|
||||
private const val TEXT_STORY_INSTANCE_STATE = "text.story.instance.state"
|
||||
|
||||
@@ -109,13 +109,13 @@ class TextStoryPostSendFragment : Fragment(R.layout.stories_send_text_post_fragm
|
||||
|
||||
setFragmentResultListener(CreateStoryWithViewersFragment.REQUEST_KEY) { _, bundle ->
|
||||
val recipientId: RecipientId = bundle.getParcelable(CreateStoryWithViewersFragment.STORY_RECIPIENT)!!
|
||||
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.Story(recipientId)))
|
||||
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey.Story(recipientId)))
|
||||
contactSearchMediator.onFilterChanged("")
|
||||
}
|
||||
|
||||
setFragmentResultListener(ChooseGroupStoryBottomSheet.GROUP_STORY) { _, bundle ->
|
||||
val groups: Set<RecipientId> = bundle.getParcelableArrayList<RecipientId>(ChooseGroupStoryBottomSheet.RESULT_SET)?.toSet() ?: emptySet()
|
||||
val keys: Set<ContactSearchKey.Story> = groups.map { ContactSearchKey.Story(it) }.toSet()
|
||||
val keys: Set<ContactSearchKey.RecipientSearchKey.Story> = groups.map { ContactSearchKey.RecipientSearchKey.Story(it) }.toSet()
|
||||
contactSearchMediator.addToVisibleGroupStories(keys)
|
||||
contactSearchMediator.onFilterChanged("")
|
||||
contactSearchMediator.setKeysSelected(keys)
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.mediasend.v2.text.send
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.contacts.paged.RecipientSearchKey
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.StoryType
|
||||
@@ -22,7 +21,7 @@ class TextStoryPostSendRepository {
|
||||
|
||||
fun send(contactSearchKey: Set<ContactSearchKey>, textStoryPostCreationState: TextStoryPostCreationState, linkPreview: LinkPreview?): Single<TextStoryPostSendResult> {
|
||||
return UntrustedRecords
|
||||
.checkForBadIdentityRecords(contactSearchKey.filterIsInstance(RecipientSearchKey::class.java).toSet())
|
||||
.checkForBadIdentityRecords(contactSearchKey.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java).toSet())
|
||||
.toSingleDefault<TextStoryPostSendResult>(TextStoryPostSendResult.Success)
|
||||
.onErrorReturn {
|
||||
if (it is UntrustedRecords.UntrustedRecordsException) {
|
||||
@@ -47,7 +46,7 @@ class TextStoryPostSendRepository {
|
||||
|
||||
for (contact in contactSearchKey) {
|
||||
val recipient = Recipient.resolved(contact.requireShareContact().recipientId.get())
|
||||
val isStory = contact is ContactSearchKey.Story || recipient.isDistributionList
|
||||
val isStory = contact is ContactSearchKey.RecipientSearchKey.Story || recipient.isDistributionList
|
||||
|
||||
if (isStory && recipient.isActiveGroup) {
|
||||
SignalDatabase.groups.markDisplayAsStory(recipient.requireGroupId())
|
||||
|
||||
Reference in New Issue
Block a user