Enable sharing to stories and refactor share activity.

This commit is contained in:
Alex Hart
2022-04-07 15:47:56 -03:00
committed by Greyson Parrelli
parent fd4543ffe0
commit 523537cf05
62 changed files with 1188 additions and 1855 deletions

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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())

View File

@@ -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(

View File

@@ -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 ->

View File

@@ -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) },

View File

@@ -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 }
)
)

View File

@@ -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 = {

View File

@@ -98,4 +98,7 @@ object TextStoryBackgroundColors {
return backgroundColors[indexOfNextColor]
}
@JvmStatic
fun getRandomBackgroundColor() = backgroundColors.random()
}

View File

@@ -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)
}
}
}
}
}

View File

@@ -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"

View File

@@ -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)

View File

@@ -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())