diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dccf2ab5ac..6ef3ec7cda 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -376,6 +376,11 @@
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
+
+
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchItems.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchItems.kt
index 4c562592bf..5fa2537400 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchItems.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchItems.kt
@@ -119,6 +119,14 @@ object ContactSearchItems {
}
}
+ override fun bindAvatar(model: StoryModel) {
+ if (model.story.recipient.isMyStory) {
+ avatar.setAvatarUsingProfile(Recipient.self())
+ } else {
+ super.bindAvatar(model)
+ }
+ }
+
override fun bindLongPress(model: StoryModel) {
itemView.setOnLongClickListener {
val actions: List = when {
@@ -216,14 +224,18 @@ object ContactSearchItems {
}
name.setText(getRecipient(model))
- avatar.setAvatar(getRecipient(model))
badge.setBadgeFromRecipient(getRecipient(model))
+ bindAvatar(model)
bindNumberField(model)
bindLabelField(model)
bindSmsTagField(model)
}
+ protected open fun bindAvatar(model: T) {
+ avatar.setAvatar(getRecipient(model))
+ }
+
protected open fun bindNumberField(model: T) {
number.visible = getRecipient(model).isGroup
if (getRecipient(model).isGroup) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardActivity.kt
index b49c1918de..8cbfc72243 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardActivity.kt
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.components.FragmentWrapperActivity
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment.Companion.RESULT_SELECTION
-class MultiselectForwardActivity : FragmentWrapperActivity(), MultiselectForwardFragment.Callback {
+open class MultiselectForwardActivity : FragmentWrapperActivity(), MultiselectForwardFragment.Callback, SearchConfigurationProvider {
companion object {
private const val ARGS = "args"
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt
index 689abb544c..8079ea0430 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragment.kt
@@ -119,6 +119,7 @@ class MultiselectForwardFragment :
disposables.bindTo(viewLifecycleOwner.lifecycle)
contactFilterView = view.findViewById(R.id.contact_filter_edit_text)
+ contactFilterView.visible = args.isSearchEnabled
contactFilterView.setOnSearchInputFocusChangedListener { _, hasFocus ->
if (hasFocus) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt
index cb7f1f2993..1d34fa3f30 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt
@@ -44,7 +44,8 @@ data class MultiselectForwardFragmentArgs @JvmOverloads constructor(
val forceSelectionOnly: Boolean = false,
val selectSingleRecipient: Boolean = false,
@ColorInt val sendButtonTint: Int = -1,
- val storySendRequirements: Stories.MediaTransform.SendRequirements = Stories.MediaTransform.SendRequirements.CAN_NOT_SEND
+ val storySendRequirements: Stories.MediaTransform.SendRequirements = Stories.MediaTransform.SendRequirements.CAN_NOT_SEND,
+ val isSearchEnabled: Boolean = true
) : Parcelable {
fun withSendButtonTint(@ColorInt sendButtonTint: Int) = copy(sendButtonTint = sendButtonTint)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt
index afb97ad69c..614dd38305 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt
@@ -13,7 +13,6 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
-import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation
import androidx.navigation.fragment.NavHostFragment
@@ -25,11 +24,8 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
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.MessageSendType
-import org.thoughtcrime.securesms.conversation.mutiselect.forward.SearchConfigurationProvider
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
@@ -49,8 +45,7 @@ class MediaSelectionActivity :
MediaReviewFragment.Callback,
EmojiKeyboardPageFragment.Callback,
EmojiEventListener,
- EmojiSearchFragment.Callback,
- SearchConfigurationProvider {
+ EmojiSearchFragment.Callback {
private var animateInShadowLayerValueAnimator: ValueAnimator? = null
private var animateInTextColorValueAnimator: ValueAnimator? = null
@@ -311,24 +306,6 @@ class MediaSelectionActivity :
viewModel.sendCommand(HudCommand.CloseEmojiSearch)
}
- override fun getSearchConfiguration(fragmentManager: FragmentManager, contactSearchState: ContactSearchState): ContactSearchConfiguration? {
- return if (isStory) {
- ContactSearchConfiguration.build {
- query = contactSearchState.query
-
- addSection(
- ContactSearchConfiguration.Section.Stories(
- groupStories = contactSearchState.groupStories,
- includeHeader = true,
- headerAction = Stories.getHeaderAction(fragmentManager)
- )
- )
- }
- } else {
- null
- }
- }
-
private inner class OnBackPressed : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val navController = Navigation.findNavController(this@MediaSelectionActivity, R.id.fragment_container)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt
index 78c9050902..59ac994663 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt
@@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionNavigator.Companion
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionState
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
import org.thoughtcrime.securesms.mediasend.v2.MediaValidator
+import org.thoughtcrime.securesms.mediasend.v2.stories.StoriesMultiselectForwardActivity
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.util.MediaUtil
@@ -137,7 +138,16 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
sharedViewModel.sendCommand(HudCommand.SaveMedia)
}
- val recipientSelectionLauncher = registerForActivityResult(MultiselectForwardActivity.SelectionContract()) { keys ->
+ val multiselectContract = MultiselectForwardActivity.SelectionContract()
+ val storiesContract = StoriesMultiselectForwardActivity.SelectionContract()
+
+ val multiselectLauncher = registerForActivityResult(multiselectContract) { keys ->
+ if (keys.isNotEmpty()) {
+ performSend(keys)
+ }
+ }
+
+ val storiesLauncher = registerForActivityResult(storiesContract) { keys ->
if (keys.isNotEmpty()) {
performSend(keys)
}
@@ -148,9 +158,16 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
val args = MultiselectForwardFragmentArgs(
false,
title = R.string.MediaReviewFragment__send_to,
- storySendRequirements = sharedViewModel.getStorySendRequirements()
+ storySendRequirements = sharedViewModel.getStorySendRequirements(),
+ isSearchEnabled = !sharedViewModel.isStory()
)
- recipientSelectionLauncher.launch(args)
+
+ if (sharedViewModel.isStory()) {
+ val previews = sharedViewModel.state.value?.selectedMedia?.take(2)?.map { it.uri } ?: emptyList()
+ storiesLauncher.launch(StoriesMultiselectForwardActivity.Args(args, previews))
+ } else {
+ multiselectLauncher.launch(args)
+ }
} else {
performSend()
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/StoriesMultiselectForwardActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/StoriesMultiselectForwardActivity.kt
new file mode 100644
index 0000000000..879e21b49e
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/stories/StoriesMultiselectForwardActivity.kt
@@ -0,0 +1,92 @@
+package org.thoughtcrime.securesms.mediasend.v2.stories
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.fragment.app.FragmentManager
+import com.bumptech.glide.Glide
+import kotlinx.parcelize.Parcelize
+import org.thoughtcrime.securesms.R
+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.MultiselectForwardActivity
+import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
+import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
+import org.thoughtcrime.securesms.stories.Stories
+import org.thoughtcrime.securesms.util.visible
+
+class StoriesMultiselectForwardActivity : MultiselectForwardActivity() {
+
+ companion object {
+ private const val PREVIEW_MEDIA = "preview_media"
+ }
+
+ override val contentViewId: Int = R.layout.stories_multiselect_forward_activity
+
+ override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
+ super.onCreate(savedInstanceState, ready)
+
+ val preview1View: ImageView = findViewById(R.id.preview_media_1)
+ val preview2View: ImageView = findViewById(R.id.preview_media_2)
+ val previewMedia: List = intent.getParcelableArrayListExtra(PREVIEW_MEDIA)!!
+
+ preview1View.visible = previewMedia.isNotEmpty()
+ preview2View.visible = previewMedia.size > 1
+
+ if (previewMedia.isNotEmpty()) {
+ Glide.with(this)
+ .load(DecryptableStreamUriLoader.DecryptableUri(previewMedia.first()))
+ .into(preview1View)
+ }
+
+ if (previewMedia.size > 1) {
+ Glide.with(this).load(DecryptableStreamUriLoader.DecryptableUri(previewMedia[1])).into(preview2View)
+ }
+ }
+
+ override fun getSearchConfiguration(fragmentManager: FragmentManager, contactSearchState: ContactSearchState): ContactSearchConfiguration? {
+ return ContactSearchConfiguration.build {
+ query = contactSearchState.query
+
+ addSection(
+ ContactSearchConfiguration.Section.Stories(
+ groupStories = contactSearchState.groupStories,
+ includeHeader = true,
+ headerAction = Stories.getHeaderAction(fragmentManager)
+ )
+ )
+ }
+ }
+
+ @Suppress("WrongViewCast")
+ override fun getContainer(): ViewGroup {
+ return findViewById(R.id.content)
+ }
+
+ class SelectionContract : ActivityResultContract>() {
+
+ private val multiselectContract = MultiselectForwardActivity.SelectionContract()
+
+ override fun createIntent(context: Context, input: Args): Intent {
+ return multiselectContract.createIntent(context, input.multiselectForwardFragmentArgs)
+ .setClass(context, StoriesMultiselectForwardActivity::class.java)
+ .putExtra(PREVIEW_MEDIA, ArrayList(input.previews))
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): List {
+ return multiselectContract.parseResult(resultCode, intent)
+ }
+ }
+
+ @Parcelize
+ class Args(
+ val multiselectForwardFragmentArgs: MultiselectForwardFragmentArgs,
+ val previews: List
+ ) : Parcelable
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationFragment.kt
index 71c2e2562d..2869a2f924 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationFragment.kt
@@ -14,16 +14,19 @@ 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.mutiselect.forward.MultiselectForwardFragmentArgs
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.stories.StoriesMultiselectForwardActivity
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendRepository
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
+import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.stories.StoryTextPostView
import org.thoughtcrime.securesms.util.LifecycleDisposable
-import org.thoughtcrime.securesms.util.navigation.safeNavigate
+import org.thoughtcrime.securesms.util.visible
class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creation_fragment), TextStoryPostTextEntryFragment.Callback, SafetyNumberBottomSheet.Callbacks {
@@ -31,6 +34,7 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
private lateinit var backgroundButton: AppCompatImageView
private lateinit var send: View
private lateinit var storyTextPostView: StoryTextPostView
+ private lateinit var sendInProgressCard: View
private val sharedViewModel: MediaSelectionViewModel by viewModels(
ownerProducer = {
@@ -65,6 +69,7 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
backgroundButton = view.findViewById(R.id.background_selector)
send = view.findViewById(R.id.send)
storyTextPostView = view.findViewById(R.id.story_text_post)
+ sendInProgressCard = view.findViewById(R.id.send_in_progress_indicator)
val backgroundProtection: View = view.findViewById(R.id.background_protection)
val addLinkProtection: View = view.findViewById(R.id.add_link_protection)
@@ -120,7 +125,19 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
viewModel.setLinkPreview("")
}
+ val launcher = registerForActivityResult(StoriesMultiselectForwardActivity.SelectionContract()) {
+ if (it.isNotEmpty()) {
+ performSend(it.toSet())
+ } else {
+ send.isClickable = true
+ sendInProgressCard.visible = false
+ }
+ }
+
send.setOnClickListener {
+ send.isClickable = false
+ sendInProgressCard.visible = true
+
storyTextPostView.hideCloseButton()
val contacts = (sharedViewModel.destination.getRecipientSearchKeyList() + sharedViewModel.destination.getRecipientSearchKey())
@@ -128,10 +145,20 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
.toSet()
if (contacts.isEmpty()) {
- viewModel.setBitmap(storyTextPostView.drawToBitmap())
- findNavController().safeNavigate(R.id.action_textStoryPostCreationFragment_to_textStoryPostSendFragment)
+ val bitmap = storyTextPostView.drawToBitmap()
+ viewModel.compressToBlob(bitmap).observeOn(AndroidSchedulers.mainThread()).subscribe { uri ->
+ launcher.launch(
+ StoriesMultiselectForwardActivity.Args(
+ MultiselectForwardFragmentArgs(
+ canSendToNonPush = false,
+ storySendRequirements = Stories.MediaTransform.SendRequirements.VALID_DURATION,
+ isSearchEnabled = false
+ ),
+ listOf(uri)
+ )
+ )
+ }
} else {
- send.isClickable = false
performSend(contacts)
}
}
@@ -166,6 +193,8 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
}
is TextStoryPostSendResult.UntrustedRecordsError -> {
send.isClickable = true
+ sendInProgressCard.visible = false
+
SafetyNumberBottomSheet
.forIdentityRecordsAndDestinations(result.untrustedRecords, contacts.toList())
.show(childFragmentManager)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationViewModel.kt
index 63bc79d69e..9182772494 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationViewModel.kt
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.mediasend.v2.text
import android.graphics.Bitmap
import android.graphics.Typeface
+import android.net.Uri
import android.os.Bundle
import androidx.annotation.ColorInt
import androidx.lifecycle.LiveData
@@ -32,9 +33,6 @@ class TextStoryPostCreationViewModel(private val repository: TextStoryPostSendRe
private val temporaryBodySubject: Subject = BehaviorSubject.createDefault("")
private val disposables = CompositeDisposable()
- private val internalThumbnail = MutableLiveData()
- val thumbnail: LiveData = internalThumbnail
-
private val internalTypeface = MutableLiveData()
val state: LiveData = store.stateLiveData
@@ -55,14 +53,12 @@ class TextStoryPostCreationViewModel(private val repository: TextStoryPostSendRe
}
}
- fun setBitmap(bitmap: Bitmap) {
- internalThumbnail.value?.recycle()
- internalThumbnail.value = bitmap
+ fun compressToBlob(bitmap: Bitmap): Single {
+ return repository.compressToBlob(bitmap)
}
override fun onCleared() {
disposables.clear()
- thumbnail.value?.recycle()
}
fun saveToInstanceState(outState: Bundle) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendFragment.kt
deleted file mode 100644
index 2a6e6e214b..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendFragment.kt
+++ /dev/null
@@ -1,213 +0,0 @@
-package org.thoughtcrime.securesms.mediasend.v2.text.send
-
-import android.os.Bundle
-import android.view.View
-import android.widget.EditText
-import android.widget.ImageView
-import android.widget.Toast
-import androidx.appcompat.widget.Toolbar
-import androidx.core.widget.doAfterTextChanged
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.setFragmentResultListener
-import androidx.fragment.app.viewModels
-import androidx.navigation.fragment.findNavController
-import androidx.recyclerview.widget.RecyclerView
-import org.signal.core.util.DimensionUnit
-import org.thoughtcrime.securesms.R
-import org.thoughtcrime.securesms.components.WrapperDialogFragment
-import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
-import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
-import org.thoughtcrime.securesms.contacts.paged.ContactSearchMediator
-import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
-import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseGroupStoryBottomSheet
-import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseStoryTypeBottomSheet
-import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationViewModel
-import org.thoughtcrime.securesms.recipients.RecipientId
-import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
-import org.thoughtcrime.securesms.sharing.ShareSelectionAdapter
-import org.thoughtcrime.securesms.sharing.ShareSelectionMappingModel
-import org.thoughtcrime.securesms.stories.Stories
-import org.thoughtcrime.securesms.stories.settings.create.CreateStoryFlowDialogFragment
-import org.thoughtcrime.securesms.stories.settings.create.CreateStoryWithViewersFragment
-import org.thoughtcrime.securesms.stories.settings.privacy.ChooseInitialMyStoryMembershipBottomSheetDialogFragment
-import org.thoughtcrime.securesms.util.BottomSheetUtil
-import org.thoughtcrime.securesms.util.FeatureFlags
-import org.thoughtcrime.securesms.util.LifecycleDisposable
-import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
-
-class TextStoryPostSendFragment :
- Fragment(R.layout.stories_send_text_post_fragment),
- ChooseStoryTypeBottomSheet.Callback,
- WrapperDialogFragment.WrapperDialogFragmentCallback,
- ChooseInitialMyStoryMembershipBottomSheetDialogFragment.Callback,
- SafetyNumberBottomSheet.Callbacks {
-
- private lateinit var shareListWrapper: View
- private lateinit var shareSelectionRecyclerView: RecyclerView
- private lateinit var shareConfirmButton: View
-
- private val shareSelectionAdapter = ShareSelectionAdapter()
- private val disposables = LifecycleDisposable()
-
- private lateinit var contactSearchMediator: ContactSearchMediator
-
- private val viewModel: TextStoryPostSendViewModel by viewModels(
- factoryProducer = {
- TextStoryPostSendViewModel.Factory(TextStoryPostSendRepository())
- }
- )
-
- private val creationViewModel: TextStoryPostCreationViewModel by viewModels(
- ownerProducer = {
- requireActivity()
- }
- )
-
- private val linkPreviewViewModel: LinkPreviewViewModel by viewModels(
- ownerProducer = {
- requireActivity()
- }
- )
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- val toolbar: Toolbar = view.findViewById(R.id.toolbar)
- val viewPort: ImageView = view.findViewById(R.id.preview_viewport)
- val searchField: EditText = view.findViewById(R.id.search_field)
-
- toolbar.setNavigationOnClickListener {
- findNavController().popBackStack()
- }
-
- shareListWrapper = view.findViewById(R.id.list_wrapper)
- shareConfirmButton = view.findViewById(R.id.share_confirm)
- shareSelectionRecyclerView = view.findViewById(R.id.selected_list)
- shareSelectionRecyclerView.adapter = shareSelectionAdapter
-
- disposables.bindTo(viewLifecycleOwner)
-
- creationViewModel.thumbnail.observe(viewLifecycleOwner) { bitmap ->
- viewPort.setImageBitmap(bitmap)
- }
-
- shareConfirmButton.setOnClickListener {
- viewModel.onSending()
- send()
- }
-
- disposables += viewModel.untrustedIdentities.subscribe { records ->
- SafetyNumberBottomSheet
- .forIdentityRecordsAndDestinations(records, contactSearchMediator.getSelectedContacts().toList())
- .show(childFragmentManager)
- }
-
- searchField.doAfterTextChanged {
- contactSearchMediator.onFilterChanged(it?.toString())
- }
-
- setFragmentResultListener(CreateStoryWithViewersFragment.REQUEST_KEY) { _, bundle ->
- val recipientId: RecipientId = bundle.getParcelable(CreateStoryWithViewersFragment.STORY_RECIPIENT)!!
- contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey.Story(recipientId)))
- contactSearchMediator.onFilterChanged("")
- }
-
- setFragmentResultListener(ChooseGroupStoryBottomSheet.GROUP_STORY) { _, bundle ->
- val groups: Set = bundle.getParcelableArrayList(ChooseGroupStoryBottomSheet.RESULT_SET)?.toSet() ?: emptySet()
- val keys: Set = groups.map { ContactSearchKey.RecipientSearchKey.Story(it) }.toSet()
- contactSearchMediator.addToVisibleGroupStories(keys)
- contactSearchMediator.onFilterChanged("")
- contactSearchMediator.setKeysSelected(keys)
- }
-
- val contactsRecyclerView: RecyclerView = view.findViewById(R.id.contacts_container)
- contactSearchMediator = ContactSearchMediator(this, contactsRecyclerView, FeatureFlags.shareSelectionLimit(), true, { contactSearchState ->
- ContactSearchConfiguration.build {
- query = contactSearchState.query
-
- addSection(
- ContactSearchConfiguration.Section.Stories(
- groupStories = contactSearchState.groupStories,
- includeHeader = true,
- headerAction = Stories.getHeaderAction(childFragmentManager)
- )
- )
- }
- })
-
- contactSearchMediator.getSelectionState().observe(viewLifecycleOwner) { selection ->
- shareSelectionAdapter.submitList(selection.mapIndexed { index, contact -> ShareSelectionMappingModel(contact.requireShareContact(), index == 0) })
- if (selection.isNotEmpty()) {
- animateInSelection()
- } else {
- animateOutSelection()
- }
- }
-
- val saveStateAndSelection = LiveDataUtil.combineLatest(viewModel.state, contactSearchMediator.getSelectionState(), ::Pair)
- saveStateAndSelection.observe(viewLifecycleOwner) { (state, selection) ->
- when (state) {
- TextStoryPostSendState.INIT -> shareConfirmButton.isEnabled = selection.isNotEmpty()
- TextStoryPostSendState.SENDING -> shareConfirmButton.isEnabled = false
- TextStoryPostSendState.SENT -> requireActivity().finish()
- else -> {
- Toast.makeText(requireContext(), R.string.TextStoryPostSendFragment__an_unexpected_error_occurred_try_again, Toast.LENGTH_SHORT).show()
- viewModel.onSendCancelled()
- }
- }
- }
- }
-
- private fun send() {
- shareConfirmButton.isEnabled = false
-
- val textStoryPostCreationState = creationViewModel.state.value
-
- viewModel.onSend(
- contactSearchMediator.getSelectedContacts(),
- textStoryPostCreationState!!,
- linkPreviewViewModel.onSendWithErrorUrl().firstOrNull()
- )
- }
-
- private fun animateInSelection() {
- shareListWrapper.animate()
- .alpha(1f)
- .translationY(0f)
- shareConfirmButton.animate()
- .alpha(1f)
- }
-
- private fun animateOutSelection() {
- shareListWrapper.animate()
- .alpha(0f)
- .translationY(DimensionUnit.DP.toPixels(48f))
- shareConfirmButton.animate()
- .alpha(0f)
- }
-
- override fun onNewStoryClicked() {
- CreateStoryFlowDialogFragment().show(parentFragmentManager, CreateStoryWithViewersFragment.REQUEST_KEY)
- }
-
- override fun onGroupStoryClicked() {
- ChooseGroupStoryBottomSheet().show(parentFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
- }
-
- override fun onWrapperDialogFragmentDismissed() {
- contactSearchMediator.refresh()
- }
-
- override fun onMyStoryConfigured(recipientId: RecipientId) {
- contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey.Story(recipientId)))
- contactSearchMediator.refresh()
- }
-
- override fun sendAnywayAfterSafetyNumberChangedInBottomSheet(destinations: List) {
- send()
- }
-
- override fun onMessageResentAfterSafetyNumberChangeInBottomSheet() = error("Not supported here")
-
- override fun onCanceled() {
- viewModel.onSendCancelled()
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendRepository.kt
index acbf5940f4..5292d53b10 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendRepository.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendRepository.kt
@@ -1,6 +1,9 @@
package org.thoughtcrime.securesms.mediasend.v2.text.send
+import android.graphics.Bitmap
+import android.net.Uri
import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.ThreadUtil
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
@@ -16,14 +19,25 @@ import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationState
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
+import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.util.Base64
+import java.io.ByteArrayOutputStream
private val TAG = Log.tag(TextStoryPostSendRepository::class.java)
class TextStoryPostSendRepository {
+ fun compressToBlob(bitmap: Bitmap): Single {
+ return Single.fromCallable {
+ val outputStream = ByteArrayOutputStream()
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
+ bitmap.recycle()
+ BlobProvider.getInstance().forData(outputStream.toByteArray()).createForSingleUseInMemory()
+ }.subscribeOn(Schedulers.computation())
+ }
+
fun send(contactSearchKey: Set, textStoryPostCreationState: TextStoryPostCreationState, linkPreview: LinkPreview?): Single {
return UntrustedRecords
.checkForBadIdentityRecords(contactSearchKey.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java).toSet())
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendState.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendState.kt
deleted file mode 100644
index da1aa7e0fb..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendState.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.thoughtcrime.securesms.mediasend.v2.text.send
-
-enum class TextStoryPostSendState {
- INIT,
- SENDING,
- SENT,
- FAILED
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendViewModel.kt
deleted file mode 100644
index d9b224a035..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/send/TextStoryPostSendViewModel.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.thoughtcrime.securesms.mediasend.v2.text.send
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import io.reactivex.rxjava3.core.Observable
-import io.reactivex.rxjava3.disposables.CompositeDisposable
-import io.reactivex.rxjava3.kotlin.plusAssign
-import io.reactivex.rxjava3.kotlin.subscribeBy
-import io.reactivex.rxjava3.subjects.PublishSubject
-import org.signal.core.util.logging.Log
-import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
-import org.thoughtcrime.securesms.database.model.IdentityRecord
-import org.thoughtcrime.securesms.linkpreview.LinkPreview
-import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationState
-import org.thoughtcrime.securesms.util.livedata.Store
-
-private val TAG = Log.tag(TextStoryPostSendViewModel::class.java)
-
-class TextStoryPostSendViewModel(private val repository: TextStoryPostSendRepository) : ViewModel() {
-
- private val store = Store(TextStoryPostSendState.INIT)
- private val untrustedIdentitySubject = PublishSubject.create>()
- private val disposables = CompositeDisposable()
-
- val state: LiveData = store.stateLiveData
- val untrustedIdentities: Observable> = untrustedIdentitySubject
-
- override fun onCleared() {
- disposables.clear()
- }
-
- fun onSending() {
- store.update {
- TextStoryPostSendState.SENDING
- }
- }
-
- fun onSendCancelled() {
- store.update {
- TextStoryPostSendState.INIT
- }
- }
-
- fun onSend(contactSearchKeys: Set, textStoryPostCreationState: TextStoryPostCreationState, linkPreview: LinkPreview?) {
- store.update {
- TextStoryPostSendState.SENDING
- }
-
- disposables += repository.send(contactSearchKeys, textStoryPostCreationState, linkPreview).subscribeBy(
- onSuccess = {
- when (it) {
- is TextStoryPostSendResult.Success -> {
- store.update { TextStoryPostSendState.SENT }
- }
- is TextStoryPostSendResult.UntrustedRecordsError -> {
- untrustedIdentitySubject.onNext(it.untrustedRecords)
- store.update { TextStoryPostSendState.INIT }
- }
- is TextStoryPostSendResult.Failure -> {
- store.update { TextStoryPostSendState.FAILED }
- }
- }
- },
- onError = {
- Log.w(TAG, "Unexpected error occurred", it)
- store.update { TextStoryPostSendState.FAILED }
- }
- )
- }
-
- class Factory(private val repository: TextStoryPostSendRepository) : ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- return modelClass.cast(TextStoryPostSendViewModel(repository)) as T
- }
- }
-}
diff --git a/app/src/main/res/layout/stories_multiselect_forward_activity.xml b/app/src/main/res/layout/stories_multiselect_forward_activity.xml
new file mode 100644
index 0000000000..e8a68f60fc
--- /dev/null
+++ b/app/src/main/res/layout/stories_multiselect_forward_activity.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/stories_send_text_post_fragment.xml b/app/src/main/res/layout/stories_send_text_post_fragment.xml
deleted file mode 100644
index 4958e361bd..0000000000
--- a/app/src/main/res/layout/stories_send_text_post_fragment.xml
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/stories_text_post_creation_fragment.xml b/app/src/main/res/layout/stories_text_post_creation_fragment.xml
index d72e1091e6..1ae1743db8 100644
--- a/app/src/main/res/layout/stories_text_post_creation_fragment.xml
+++ b/app/src/main/res/layout/stories_text_post_creation_fragment.xml
@@ -112,4 +112,24 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/media.xml b/app/src/main/res/navigation/media.xml
index 5854c2e687..2992440608 100644
--- a/app/src/main/res/navigation/media.xml
+++ b/app/src/main/res/navigation/media.xml
@@ -47,17 +47,7 @@
android:id="@+id/textStoryPostCreationFragment"
android:name="org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationFragment"
android:label="text_story_post_creation_fragment"
- tools:layout="@layout/stories_text_post_creation_fragment">
-
-
-
-
+ tools:layout="@layout/stories_text_post_creation_fragment" />