Add new story send final screen.
This commit is contained in:
parent
3c78d8619a
commit
87cb2d6bf8
18 changed files with 282 additions and 468 deletions
|
@ -376,6 +376,11 @@
|
||||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
|
<activity android:name=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
|
||||||
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||||
|
|
||||||
<activity android:name=".PassphraseChangeActivity"
|
<activity android:name=".PassphraseChangeActivity"
|
||||||
android:label="@string/AndroidManifest__change_passphrase"
|
android:label="@string/AndroidManifest__change_passphrase"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
|
@ -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) {
|
override fun bindLongPress(model: StoryModel) {
|
||||||
itemView.setOnLongClickListener {
|
itemView.setOnLongClickListener {
|
||||||
val actions: List<ActionItem> = when {
|
val actions: List<ActionItem> = when {
|
||||||
|
@ -216,14 +224,18 @@ object ContactSearchItems {
|
||||||
}
|
}
|
||||||
|
|
||||||
name.setText(getRecipient(model))
|
name.setText(getRecipient(model))
|
||||||
avatar.setAvatar(getRecipient(model))
|
|
||||||
badge.setBadgeFromRecipient(getRecipient(model))
|
badge.setBadgeFromRecipient(getRecipient(model))
|
||||||
|
|
||||||
|
bindAvatar(model)
|
||||||
bindNumberField(model)
|
bindNumberField(model)
|
||||||
bindLabelField(model)
|
bindLabelField(model)
|
||||||
bindSmsTagField(model)
|
bindSmsTagField(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun bindAvatar(model: T) {
|
||||||
|
avatar.setAvatar(getRecipient(model))
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun bindNumberField(model: T) {
|
protected open fun bindNumberField(model: T) {
|
||||||
number.visible = getRecipient(model).isGroup
|
number.visible = getRecipient(model).isGroup
|
||||||
if (getRecipient(model).isGroup) {
|
if (getRecipient(model).isGroup) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.components.FragmentWrapperActivity
|
||||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment.Companion.RESULT_SELECTION
|
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 {
|
companion object {
|
||||||
private const val ARGS = "args"
|
private const val ARGS = "args"
|
||||||
|
|
|
@ -119,6 +119,7 @@ class MultiselectForwardFragment :
|
||||||
disposables.bindTo(viewLifecycleOwner.lifecycle)
|
disposables.bindTo(viewLifecycleOwner.lifecycle)
|
||||||
|
|
||||||
contactFilterView = view.findViewById(R.id.contact_filter_edit_text)
|
contactFilterView = view.findViewById(R.id.contact_filter_edit_text)
|
||||||
|
contactFilterView.visible = args.isSearchEnabled
|
||||||
|
|
||||||
contactFilterView.setOnSearchInputFocusChangedListener { _, hasFocus ->
|
contactFilterView.setOnSearchInputFocusChangedListener { _, hasFocus ->
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
|
|
|
@ -44,7 +44,8 @@ data class MultiselectForwardFragmentArgs @JvmOverloads constructor(
|
||||||
val forceSelectionOnly: Boolean = false,
|
val forceSelectionOnly: Boolean = false,
|
||||||
val selectSingleRecipient: Boolean = false,
|
val selectSingleRecipient: Boolean = false,
|
||||||
@ColorInt val sendButtonTint: Int = -1,
|
@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 {
|
) : Parcelable {
|
||||||
|
|
||||||
fun withSendButtonTint(@ColorInt sendButtonTint: Int) = copy(sendButtonTint = sendButtonTint)
|
fun withSendButtonTint(@ColorInt sendButtonTint: Int) = copy(sendButtonTint = sendButtonTint)
|
||||||
|
|
|
@ -13,7 +13,6 @@ import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
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.PassphraseRequiredActivity
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
|
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.ContactSearchKey
|
||||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchState
|
|
||||||
import org.thoughtcrime.securesms.conversation.MessageSendType
|
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.EmojiKeyboardPageFragment
|
||||||
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
|
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
|
||||||
|
@ -49,8 +45,7 @@ class MediaSelectionActivity :
|
||||||
MediaReviewFragment.Callback,
|
MediaReviewFragment.Callback,
|
||||||
EmojiKeyboardPageFragment.Callback,
|
EmojiKeyboardPageFragment.Callback,
|
||||||
EmojiEventListener,
|
EmojiEventListener,
|
||||||
EmojiSearchFragment.Callback,
|
EmojiSearchFragment.Callback {
|
||||||
SearchConfigurationProvider {
|
|
||||||
|
|
||||||
private var animateInShadowLayerValueAnimator: ValueAnimator? = null
|
private var animateInShadowLayerValueAnimator: ValueAnimator? = null
|
||||||
private var animateInTextColorValueAnimator: ValueAnimator? = null
|
private var animateInTextColorValueAnimator: ValueAnimator? = null
|
||||||
|
@ -311,24 +306,6 @@ class MediaSelectionActivity :
|
||||||
viewModel.sendCommand(HudCommand.CloseEmojiSearch)
|
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) {
|
private inner class OnBackPressed : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
val navController = Navigation.findNavController(this@MediaSelectionActivity, R.id.fragment_container)
|
val navController = Navigation.findNavController(this@MediaSelectionActivity, R.id.fragment_container)
|
||||||
|
|
|
@ -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.MediaSelectionState
|
||||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
||||||
import org.thoughtcrime.securesms.mediasend.v2.MediaValidator
|
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.mms.SentMediaQuality
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil
|
import org.thoughtcrime.securesms.util.MediaUtil
|
||||||
|
@ -137,7 +138,16 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
|
||||||
sharedViewModel.sendCommand(HudCommand.SaveMedia)
|
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()) {
|
if (keys.isNotEmpty()) {
|
||||||
performSend(keys)
|
performSend(keys)
|
||||||
}
|
}
|
||||||
|
@ -148,9 +158,16 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
|
||||||
val args = MultiselectForwardFragmentArgs(
|
val args = MultiselectForwardFragmentArgs(
|
||||||
false,
|
false,
|
||||||
title = R.string.MediaReviewFragment__send_to,
|
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 {
|
} else {
|
||||||
performSend()
|
performSend()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Uri> = 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<Args, List<ContactSearchKey.RecipientSearchKey>>() {
|
||||||
|
|
||||||
|
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<ContactSearchKey.RecipientSearchKey> {
|
||||||
|
return multiselectContract.parseResult(resultCode, intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class Args(
|
||||||
|
val multiselectForwardFragmentArgs: MultiselectForwardFragmentArgs,
|
||||||
|
val previews: List<Uri>
|
||||||
|
) : Parcelable
|
||||||
|
}
|
|
@ -14,16 +14,19 @@ import androidx.navigation.fragment.findNavController
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
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.LinkPreviewRepository
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
||||||
import org.thoughtcrime.securesms.mediasend.v2.HudCommand
|
import org.thoughtcrime.securesms.mediasend.v2.HudCommand
|
||||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
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.TextStoryPostSendRepository
|
||||||
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
|
import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult
|
||||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
||||||
|
import org.thoughtcrime.securesms.stories.Stories
|
||||||
import org.thoughtcrime.securesms.stories.StoryTextPostView
|
import org.thoughtcrime.securesms.stories.StoryTextPostView
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
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 {
|
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 backgroundButton: AppCompatImageView
|
||||||
private lateinit var send: View
|
private lateinit var send: View
|
||||||
private lateinit var storyTextPostView: StoryTextPostView
|
private lateinit var storyTextPostView: StoryTextPostView
|
||||||
|
private lateinit var sendInProgressCard: View
|
||||||
|
|
||||||
private val sharedViewModel: MediaSelectionViewModel by viewModels(
|
private val sharedViewModel: MediaSelectionViewModel by viewModels(
|
||||||
ownerProducer = {
|
ownerProducer = {
|
||||||
|
@ -65,6 +69,7 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||||
backgroundButton = view.findViewById(R.id.background_selector)
|
backgroundButton = view.findViewById(R.id.background_selector)
|
||||||
send = view.findViewById(R.id.send)
|
send = view.findViewById(R.id.send)
|
||||||
storyTextPostView = view.findViewById(R.id.story_text_post)
|
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 backgroundProtection: View = view.findViewById(R.id.background_protection)
|
||||||
val addLinkProtection: View = view.findViewById(R.id.add_link_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("")
|
viewModel.setLinkPreview("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val launcher = registerForActivityResult(StoriesMultiselectForwardActivity.SelectionContract()) {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
performSend(it.toSet())
|
||||||
|
} else {
|
||||||
|
send.isClickable = true
|
||||||
|
sendInProgressCard.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
send.setOnClickListener {
|
send.setOnClickListener {
|
||||||
|
send.isClickable = false
|
||||||
|
sendInProgressCard.visible = true
|
||||||
|
|
||||||
storyTextPostView.hideCloseButton()
|
storyTextPostView.hideCloseButton()
|
||||||
|
|
||||||
val contacts = (sharedViewModel.destination.getRecipientSearchKeyList() + sharedViewModel.destination.getRecipientSearchKey())
|
val contacts = (sharedViewModel.destination.getRecipientSearchKeyList() + sharedViewModel.destination.getRecipientSearchKey())
|
||||||
|
@ -128,10 +145,20 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
if (contacts.isEmpty()) {
|
if (contacts.isEmpty()) {
|
||||||
viewModel.setBitmap(storyTextPostView.drawToBitmap())
|
val bitmap = storyTextPostView.drawToBitmap()
|
||||||
findNavController().safeNavigate(R.id.action_textStoryPostCreationFragment_to_textStoryPostSendFragment)
|
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 {
|
} else {
|
||||||
send.isClickable = false
|
|
||||||
performSend(contacts)
|
performSend(contacts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,6 +193,8 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
|
||||||
}
|
}
|
||||||
is TextStoryPostSendResult.UntrustedRecordsError -> {
|
is TextStoryPostSendResult.UntrustedRecordsError -> {
|
||||||
send.isClickable = true
|
send.isClickable = true
|
||||||
|
sendInProgressCard.visible = false
|
||||||
|
|
||||||
SafetyNumberBottomSheet
|
SafetyNumberBottomSheet
|
||||||
.forIdentityRecordsAndDestinations(result.untrustedRecords, contacts.toList())
|
.forIdentityRecordsAndDestinations(result.untrustedRecords, contacts.toList())
|
||||||
.show(childFragmentManager)
|
.show(childFragmentManager)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.mediasend.v2.text
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
@ -32,9 +33,6 @@ class TextStoryPostCreationViewModel(private val repository: TextStoryPostSendRe
|
||||||
private val temporaryBodySubject: Subject<String> = BehaviorSubject.createDefault("")
|
private val temporaryBodySubject: Subject<String> = BehaviorSubject.createDefault("")
|
||||||
private val disposables = CompositeDisposable()
|
private val disposables = CompositeDisposable()
|
||||||
|
|
||||||
private val internalThumbnail = MutableLiveData<Bitmap>()
|
|
||||||
val thumbnail: LiveData<Bitmap> = internalThumbnail
|
|
||||||
|
|
||||||
private val internalTypeface = MutableLiveData<Typeface>()
|
private val internalTypeface = MutableLiveData<Typeface>()
|
||||||
|
|
||||||
val state: LiveData<TextStoryPostCreationState> = store.stateLiveData
|
val state: LiveData<TextStoryPostCreationState> = store.stateLiveData
|
||||||
|
@ -55,14 +53,12 @@ class TextStoryPostCreationViewModel(private val repository: TextStoryPostSendRe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBitmap(bitmap: Bitmap) {
|
fun compressToBlob(bitmap: Bitmap): Single<Uri> {
|
||||||
internalThumbnail.value?.recycle()
|
return repository.compressToBlob(bitmap)
|
||||||
internalThumbnail.value = bitmap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
disposables.clear()
|
disposables.clear()
|
||||||
thumbnail.value?.recycle()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveToInstanceState(outState: Bundle) {
|
fun saveToInstanceState(outState: Bundle) {
|
||||||
|
|
|
@ -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<RecipientId> = bundle.getParcelableArrayList<RecipientId>(ChooseGroupStoryBottomSheet.RESULT_SET)?.toSet() ?: emptySet()
|
|
||||||
val keys: Set<ContactSearchKey.RecipientSearchKey.Story> = 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<ContactSearchKey.RecipientSearchKey>) {
|
|
||||||
send()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMessageResentAfterSafetyNumberChangeInBottomSheet() = error("Not supported here")
|
|
||||||
|
|
||||||
override fun onCanceled() {
|
|
||||||
viewModel.onSendCancelled()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.thoughtcrime.securesms.mediasend.v2.text.send
|
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.core.Single
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import org.signal.core.util.ThreadUtil
|
import org.signal.core.util.ThreadUtil
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
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.mediasend.v2.text.TextStoryPostCreationState
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
||||||
|
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.stories.Stories
|
import org.thoughtcrime.securesms.stories.Stories
|
||||||
import org.thoughtcrime.securesms.util.Base64
|
import org.thoughtcrime.securesms.util.Base64
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
private val TAG = Log.tag(TextStoryPostSendRepository::class.java)
|
private val TAG = Log.tag(TextStoryPostSendRepository::class.java)
|
||||||
|
|
||||||
class TextStoryPostSendRepository {
|
class TextStoryPostSendRepository {
|
||||||
|
|
||||||
|
fun compressToBlob(bitmap: Bitmap): Single<Uri> {
|
||||||
|
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<ContactSearchKey>, textStoryPostCreationState: TextStoryPostCreationState, linkPreview: LinkPreview?): Single<TextStoryPostSendResult> {
|
fun send(contactSearchKey: Set<ContactSearchKey>, textStoryPostCreationState: TextStoryPostCreationState, linkPreview: LinkPreview?): Single<TextStoryPostSendResult> {
|
||||||
return UntrustedRecords
|
return UntrustedRecords
|
||||||
.checkForBadIdentityRecords(contactSearchKey.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java).toSet())
|
.checkForBadIdentityRecords(contactSearchKey.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java).toSet())
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.mediasend.v2.text.send
|
|
||||||
|
|
||||||
enum class TextStoryPostSendState {
|
|
||||||
INIT,
|
|
||||||
SENDING,
|
|
||||||
SENT,
|
|
||||||
FAILED
|
|
||||||
}
|
|
|
@ -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<List<IdentityRecord>>()
|
|
||||||
private val disposables = CompositeDisposable()
|
|
||||||
|
|
||||||
val state: LiveData<TextStoryPostSendState> = store.stateLiveData
|
|
||||||
val untrustedIdentities: Observable<List<IdentityRecord>> = untrustedIdentitySubject
|
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
disposables.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSending() {
|
|
||||||
store.update {
|
|
||||||
TextStoryPostSendState.SENDING
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSendCancelled() {
|
|
||||||
store.update {
|
|
||||||
TextStoryPostSendState.INIT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSend(contactSearchKeys: Set<ContactSearchKey>, 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 <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
return modelClass.cast(TextStoryPostSendViewModel(repository)) as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.util.views.DarkOverflowToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:navigationIcon="@drawable/ic_arrow_left_24"
|
||||||
|
app:title="@string/conversation_activity__send" />
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:id="@+id/fragment_container_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/signal_colorBackground"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/preview_viewport"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="22.5dp"
|
||||||
|
app:layout_collapseMode="parallax">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/preview_media_2"
|
||||||
|
android:layout_width="110dp"
|
||||||
|
android:layout_height="177dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:rotation="-15"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:translationX="-28dp"
|
||||||
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.WallpaperPreview"
|
||||||
|
tools:background="@color/red" />
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/preview_media_1"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="215dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:padding="1.5dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.WallpaperPreview"
|
||||||
|
app:strokeColor="@color/signal_colorBackground"
|
||||||
|
app:strokeWidth="3dp"
|
||||||
|
tools:background="@color/green" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
</LinearLayout>
|
|
@ -1,118 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.util.views.DarkOverflowToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:navigationIcon="@drawable/ic_arrow_left_24"
|
|
||||||
app:title="@string/conversation_activity__send" />
|
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@color/core_grey_95"
|
|
||||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/preview_viewport"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="215dp"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:scaleType="centerInside"
|
|
||||||
app:layout_collapseMode="parallax" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
|
||||||
android:id="@+id/search_field"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
android:background="@drawable/rounded_rectangle_secondary_18"
|
|
||||||
android:hint="@string/TextStoryPostSendFragment__search"
|
|
||||||
android:minHeight="44dp"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:textAppearance="@style/Signal.Text.Body" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/contacts_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:paddingBottom="44dp"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
tools:listitem="@layout/contact_search_item" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/list_wrapper"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:alpha="0"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:translationY="48dp">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="@color/signal_divider_major"
|
|
||||||
android:translationY="48dp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/selected_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="44dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:background="@color/signal_background_primary"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="78dp"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
tools:listitem="@layout/share_contact_selection_item" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/share_confirm"
|
|
||||||
android:layout_width="56dp"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:layout_gravity="bottom|end"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:contentDescription="@string/ShareActivity__share"
|
|
||||||
app:backgroundTint="@color/core_ultramarine"
|
|
||||||
app:srcCompat="@drawable/ic_send_24"
|
|
||||||
app:tint="@color/core_white" />
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -112,4 +112,24 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/send_in_progress_indicator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:cardCornerRadius="18dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="24dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:indicatorColor="@color/signal_colorPrimary" />
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -47,17 +47,7 @@
|
||||||
android:id="@+id/textStoryPostCreationFragment"
|
android:id="@+id/textStoryPostCreationFragment"
|
||||||
android:name="org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationFragment"
|
android:name="org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationFragment"
|
||||||
android:label="text_story_post_creation_fragment"
|
android:label="text_story_post_creation_fragment"
|
||||||
tools:layout="@layout/stories_text_post_creation_fragment">
|
tools:layout="@layout/stories_text_post_creation_fragment" />
|
||||||
<action
|
|
||||||
android:id="@+id/action_textStoryPostCreationFragment_to_textStoryPostSendFragment"
|
|
||||||
app:destination="@id/textStoryPostSendFragment" />
|
|
||||||
</fragment>
|
|
||||||
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/textStoryPostSendFragment"
|
|
||||||
android:name="org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendFragment"
|
|
||||||
android:label="text_story_post_send_fragment"
|
|
||||||
tools:layout="@layout/stories_send_text_post_fragment" />
|
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_directly_to_mediaCaptureFragment"
|
android:id="@+id/action_directly_to_mediaCaptureFragment"
|
||||||
|
|
Loading…
Add table
Reference in a new issue