mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Add initial send support to CFv2.
This commit is contained in:
committed by
Greyson Parrelli
parent
5c78de2f46
commit
2aaeda6ca8
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -76,6 +77,10 @@ public class InsetAwareConstraintLayout extends ConstraintLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void showSoftkey(@NonNull EditText editText) {
|
||||
ViewUtil.focusAndShowKeyboard(editText);
|
||||
}
|
||||
|
||||
public interface WindowInsetsTypeProvider {
|
||||
|
||||
WindowInsetsTypeProvider ALL = () ->
|
||||
|
||||
@@ -216,7 +216,7 @@ class SendButton(context: Context, attributeSet: AttributeSet?) : AppCompatImage
|
||||
.show(items)
|
||||
}
|
||||
|
||||
interface SendTypeChangedListener {
|
||||
fun interface SendTypeChangedListener {
|
||||
fun onSendTypeChanged(newType: MessageSendType, manuallySelected: Boolean)
|
||||
}
|
||||
|
||||
|
||||
@@ -411,6 +411,10 @@ public class ConversationIntents {
|
||||
return Objects.equals(this, BUBBLE);
|
||||
}
|
||||
|
||||
public boolean isInPopup() {
|
||||
return Objects.equals(this, POPUP);
|
||||
}
|
||||
|
||||
public boolean isNormal() {
|
||||
return Objects.equals(this, NORMAL);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,21 @@ package org.thoughtcrime.securesms.conversation.v2
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Intent
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.OnFocusChangeListener
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import android.widget.TextView.OnEditorActionListener
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.StringRes
|
||||
@@ -30,6 +40,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
@@ -38,13 +49,21 @@ import org.greenrobot.eventbus.EventBus
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.libsignal.protocol.InvalidMessageException
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.MainActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.gifts.flow.GiftFlowActivity
|
||||
import org.thoughtcrime.securesms.badges.gifts.viewgift.received.ViewReceivedGiftBottomSheet
|
||||
import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle
|
||||
import org.thoughtcrime.securesms.components.ComposeText
|
||||
import org.thoughtcrime.securesms.components.HidingLinearLayout
|
||||
import org.thoughtcrime.securesms.components.InputPanel
|
||||
import org.thoughtcrime.securesms.components.InsetAwareConstraintLayout
|
||||
import org.thoughtcrime.securesms.components.ScrollToPositionDelegate
|
||||
import org.thoughtcrime.securesms.components.SendButton
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalFragment
|
||||
@@ -62,6 +81,7 @@ import org.thoughtcrime.securesms.conversation.ConversationItem
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.conversation.ConversationOptionsMenu
|
||||
import org.thoughtcrime.securesms.conversation.MarkReadHelper
|
||||
import org.thoughtcrime.securesms.conversation.MessageSendType
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.conversation.colors.Colorizer
|
||||
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
|
||||
@@ -93,6 +113,7 @@ import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupDescription
|
||||
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInfoBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult
|
||||
import org.thoughtcrime.securesms.invites.InviteActions
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.longmessage.LongMessageFragment
|
||||
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
|
||||
@@ -101,12 +122,14 @@ import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity
|
||||
import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity
|
||||
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientExporter
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
||||
@@ -181,6 +204,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
private lateinit var scrollToPositionDelegate: ScrollToPositionDelegate
|
||||
private lateinit var adapter: ConversationAdapterV2
|
||||
private lateinit var recyclerViewColorizer: RecyclerViewColorizer
|
||||
private lateinit var attachmentManager: AttachmentManager
|
||||
|
||||
private var animationsAllowed = false
|
||||
|
||||
@@ -191,6 +215,21 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
}
|
||||
}
|
||||
|
||||
private val container: InsetAwareConstraintLayout
|
||||
get() = requireView() as InsetAwareConstraintLayout
|
||||
|
||||
private val inputPanel: InputPanel
|
||||
get() = binding.conversationInputPanel.root
|
||||
|
||||
private val composeText: ComposeText
|
||||
get() = binding.conversationInputPanel.embeddedTextEditor
|
||||
|
||||
private val sendButton: SendButton
|
||||
get() = binding.conversationInputPanel.sendButton
|
||||
|
||||
private val sendEditButton: ImageButton
|
||||
get() = binding.conversationInputPanel.sendEditButton
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
SignalLocalMetrics.ConversationOpen.start()
|
||||
@@ -213,6 +252,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
)
|
||||
conversationToolbarOnScrollHelper.attach(binding.conversationItemRecycler)
|
||||
presentWallpaper(args.wallpaper)
|
||||
presentChatColors(args.chatColors)
|
||||
presentActionBarMenu()
|
||||
|
||||
observeConversationThread()
|
||||
@@ -230,6 +270,11 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
ApplicationDependencies.getMessageNotifier().clearVisibleThread()
|
||||
}
|
||||
|
||||
private fun observeConversationThread() {
|
||||
var firstRender = true
|
||||
disposables += viewModel
|
||||
@@ -255,9 +300,9 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
scrollToPositionDelegate.notifyListCommitted()
|
||||
|
||||
if (firstRender) {
|
||||
firstRender = false
|
||||
binding.conversationItemRecycler.doAfterNextLayout {
|
||||
SignalLocalMetrics.ConversationOpen.onRenderFinished()
|
||||
firstRender = false
|
||||
doAfterFirstRender()
|
||||
animationsAllowed = true
|
||||
}
|
||||
@@ -268,6 +313,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
|
||||
private fun doAfterFirstRender() {
|
||||
Log.d(TAG, "doAfterFirstRender")
|
||||
attachmentManager = AttachmentManager(requireContext(), requireView(), AttachmentManagerListener())
|
||||
|
||||
EventBus.getDefault().registerForLifecycle(groupCallViewModel, viewLifecycleOwner)
|
||||
viewLifecycleOwner.lifecycle.addObserver(LastSeenPositionUpdater(adapter, layoutManager, viewModel))
|
||||
@@ -291,6 +337,27 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
adapter.notifyItemRangeChanged(0, adapter.itemCount)
|
||||
})
|
||||
|
||||
val sendButtonListener = SendButtonListener()
|
||||
val composeTextEventsListener = ComposeTextEventsListener()
|
||||
|
||||
composeText.apply {
|
||||
setOnEditorActionListener(sendButtonListener)
|
||||
|
||||
setCursorPositionChangedListener(composeTextEventsListener)
|
||||
setOnKeyListener(composeTextEventsListener)
|
||||
addTextChangedListener(composeTextEventsListener)
|
||||
setOnClickListener(composeTextEventsListener)
|
||||
onFocusChangeListener = composeTextEventsListener
|
||||
|
||||
setMessageSendType(MessageSendType.SignalMessageSendType)
|
||||
}
|
||||
|
||||
sendButton.apply {
|
||||
setOnClickListener(sendButtonListener)
|
||||
isEnabled = true
|
||||
post { sendButton.triggerSelectedChangedEvent() }
|
||||
}
|
||||
|
||||
presentGroupCallJoinButton()
|
||||
|
||||
binding.scrollToBottom.setOnClickListener {
|
||||
@@ -304,9 +371,23 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
adapter.registerAdapterDataObserver(DataObserver(scrollToPositionDelegate))
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
ApplicationDependencies.getMessageNotifier().clearVisibleThread()
|
||||
private fun calculateCharactersRemaining() {
|
||||
val messageBody: String = binding.conversationInputPanel.embeddedTextEditor.textTrimmed.toString()
|
||||
val charactersLeftView: TextView = binding.conversationInputSpaceLeft
|
||||
val characterState = MessageSendType.SignalMessageSendType.calculateCharacters(messageBody)
|
||||
|
||||
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
|
||||
charactersLeftView.text = String.format(
|
||||
Locale.getDefault(),
|
||||
"%d/%d (%d)",
|
||||
characterState.charactersRemaining,
|
||||
characterState.maxTotalMessageSize,
|
||||
characterState.messagesSpent
|
||||
)
|
||||
charactersLeftView.visibility = View.VISIBLE
|
||||
} else {
|
||||
charactersLeftView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerForResults() {
|
||||
@@ -376,6 +457,10 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
recyclerViewColorizer.setChatColors(chatColors)
|
||||
binding.scrollToMention.setUnreadCountBackgroundTint(chatColors.asSingleColor())
|
||||
binding.scrollToBottom.setUnreadCountBackgroundTint(chatColors.asSingleColor())
|
||||
binding.conversationInputPanel.buttonToggle.background.apply {
|
||||
colorFilter = PorterDuffColorFilter(chatColors.asSingleColor(), PorterDuff.Mode.MULTIPLY)
|
||||
invalidateSelf()
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentScrollButtons(scrollButtonState: ConversationScrollButtonState) {
|
||||
@@ -504,6 +589,97 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
return callback
|
||||
}
|
||||
|
||||
private fun updateToggleButtonState() {
|
||||
val buttonToggle: AnimatingToggle = binding.conversationInputPanel.buttonToggle
|
||||
val quickAttachment: HidingLinearLayout = binding.conversationInputPanel.quickAttachmentToggle
|
||||
val inlineAttachment: HidingLinearLayout = binding.conversationInputPanel.inlineAttachmentContainer
|
||||
|
||||
when {
|
||||
inputPanel.isRecordingInLockedMode -> {
|
||||
buttonToggle.display(sendButton)
|
||||
quickAttachment.show()
|
||||
inlineAttachment.hide()
|
||||
}
|
||||
|
||||
inputPanel.inEditMessageMode() -> {
|
||||
buttonToggle.display(sendEditButton)
|
||||
quickAttachment.hide()
|
||||
inlineAttachment.hide()
|
||||
}
|
||||
// todo [cody] draftViewModel.voiceNoteDraft != null) { {
|
||||
// buttonToggle.display(sendButton)
|
||||
// quickAttachment.hide()
|
||||
// inlineAttachment.hide()
|
||||
// }
|
||||
composeText.text?.isEmpty() == true && !attachmentManager.isAttachmentPresent -> {
|
||||
buttonToggle.display(binding.conversationInputPanel.attachButton)
|
||||
quickAttachment.show()
|
||||
inlineAttachment.hide()
|
||||
}
|
||||
|
||||
else -> {
|
||||
buttonToggle.display(sendButton)
|
||||
quickAttachment.hide()
|
||||
|
||||
if (!attachmentManager.isAttachmentPresent) { // todo [cody] && !linkPreviewViewModel.hasLinkPreviewUi()) {
|
||||
inlineAttachment.show()
|
||||
} else {
|
||||
inlineAttachment.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMessage(metricId: String? = null, scheduledDate: Long = -1) {
|
||||
val slideDeck: SlideDeck? = if (attachmentManager.isAttachmentPresent) attachmentManager.buildSlideDeck() else null
|
||||
|
||||
val send: Completable = viewModel.sendMessage(
|
||||
metricId = metricId,
|
||||
body = composeText.editableText.toString(),
|
||||
slideDeck = slideDeck,
|
||||
scheduledDate = scheduledDate,
|
||||
messageToEdit = inputPanel.editMessageId,
|
||||
quote = inputPanel.quote.orNull(),
|
||||
mentions = composeText.mentions,
|
||||
bodyRanges = composeText.styling
|
||||
)
|
||||
|
||||
disposables += send
|
||||
.doOnSubscribe {
|
||||
composeText.setText("")
|
||||
}
|
||||
.subscribeBy(
|
||||
onError = { t ->
|
||||
Log.w(TAG, "Error sending", t)
|
||||
when (t) {
|
||||
is InvalidMessageException -> toast(R.string.ConversationActivity_message_is_empty_exclamation)
|
||||
is RecipientFormattingException -> toast(R.string.ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation, Toast.LENGTH_LONG)
|
||||
}
|
||||
},
|
||||
onComplete = this::onSendComplete
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSendComplete() {
|
||||
if (isDetached || activity?.isFinishing == true) {
|
||||
if (args.conversationScreenType.isInPopup) {
|
||||
activity?.finish()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// todo [cody] fragment.setLastSeen(0);
|
||||
|
||||
scrollToPositionDelegate.resetScrollPosition()
|
||||
attachmentManager.cleanup()
|
||||
|
||||
// todo [cody] updateLinkPreviewState();
|
||||
|
||||
// todo [cody] draftViewModel.onSendComplete(threadId);
|
||||
|
||||
inputPanel.exitEditMessageMode()
|
||||
}
|
||||
|
||||
private fun toast(@StringRes toastTextId: Int, toastDuration: Int = Toast.LENGTH_SHORT) {
|
||||
ThreadUtil.runOnMain {
|
||||
if (context != null) {
|
||||
@@ -514,6 +690,8 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
}
|
||||
}
|
||||
|
||||
//region Scroll Handling
|
||||
|
||||
/**
|
||||
* Requests a jump to the desired position, and ensures that the position desired will be visible on the screen.
|
||||
*/
|
||||
@@ -564,6 +742,10 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Scroll Handling
|
||||
|
||||
// region Conversation Callbacks
|
||||
|
||||
private inner class ConversationItemClickListener : ConversationAdapter.ItemClickListener {
|
||||
override fun onQuoteClicked(messageRecord: MmsMessageRecord) {
|
||||
val quote: Quote? = messageRecord.quote
|
||||
@@ -1005,6 +1187,8 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Conversation Callbacks
|
||||
|
||||
private class LastSeenPositionUpdater(
|
||||
val adapter: ConversationAdapterV2,
|
||||
val layoutManager: LinearLayoutManager,
|
||||
@@ -1022,4 +1206,93 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
||||
viewModel.setLastScrolled(lastVisibleMessageTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
//region Compose + Send Callbacks
|
||||
|
||||
private inner class SendButtonListener : View.OnClickListener, OnEditorActionListener {
|
||||
override fun onClick(v: View) {
|
||||
val metricId = if (viewModel.recipientSnapshot?.isGroup == true) {
|
||||
SignalLocalMetrics.GroupMessageSend.start()
|
||||
} else {
|
||||
SignalLocalMetrics.IndividualMessageSend.start()
|
||||
}
|
||||
|
||||
sendMessage(metricId)
|
||||
}
|
||||
|
||||
override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent): Boolean {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
if (inputPanel.isInEditMode) {
|
||||
sendEditButton.performClick()
|
||||
} else {
|
||||
sendButton.performClick()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ComposeTextEventsListener :
|
||||
View.OnKeyListener,
|
||||
View.OnClickListener,
|
||||
TextWatcher,
|
||||
OnFocusChangeListener,
|
||||
ComposeText.CursorPositionChangedListener {
|
||||
|
||||
private var beforeLength = 0
|
||||
|
||||
override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
if (SignalStore.settings().isEnterKeySends || event.isCtrlPressed) {
|
||||
sendButton.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER))
|
||||
sendButton.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER))
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
container.showSoftkey(composeText)
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
beforeLength = composeText.textTrimmed.length
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
calculateCharactersRemaining()
|
||||
if (composeText.textTrimmed.isEmpty() || beforeLength == 0) {
|
||||
composeText.postDelayed({ updateToggleButtonState() }, 50)
|
||||
}
|
||||
// todo [cody] stickerViewModel.onInputTextUpdated(s.toString())
|
||||
}
|
||||
|
||||
override fun onFocusChange(v: View, hasFocus: Boolean) {
|
||||
if (hasFocus) { // && container.getCurrentInput() == emojiDrawerStub.get()) {
|
||||
container.showSoftkey(composeText)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCursorPositionChanged(start: Int, end: Int) {
|
||||
// todo [cody] linkPreviewViewModel.onTextChanged(requireContext(), composeText.getTextTrimmed().toString(), start, end);
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit
|
||||
}
|
||||
|
||||
//endregion Compose + Send Callbacks
|
||||
|
||||
private inner class AttachmentManagerListener : AttachmentManager.AttachmentListener {
|
||||
override fun onAttachmentChanged() {
|
||||
// TODO [cody] implement
|
||||
}
|
||||
|
||||
override fun onLocationRemoved() {
|
||||
// TODO [cody] implement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.content.Context
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.libsignal.protocol.InvalidMessageException
|
||||
import org.signal.paging.PagedData
|
||||
import org.signal.paging.PagingConfig
|
||||
import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper
|
||||
@@ -18,11 +20,21 @@ import org.thoughtcrime.securesms.conversation.colors.NameColor
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationDataSource
|
||||
import org.thoughtcrime.securesms.database.RxDatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.Mention
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.Quote
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class ConversationRepository(context: Context) {
|
||||
|
||||
@@ -79,6 +91,58 @@ class ConversationRepository(context: Context) {
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun sendMessage(
|
||||
threadId: Long,
|
||||
threadRecipient: Recipient?,
|
||||
metricId: String?,
|
||||
body: String,
|
||||
slideDeck: SlideDeck?,
|
||||
scheduledDate: Long,
|
||||
messageToEdit: MessageId?,
|
||||
quote: QuoteModel?,
|
||||
mentions: List<Mention>,
|
||||
bodyRanges: BodyRangeList?
|
||||
): Completable {
|
||||
val sendCompletable = Completable.create { emitter ->
|
||||
if (body.isEmpty() && slideDeck?.containsMediaSlide() != true) {
|
||||
emitter.onError(InvalidMessageException("Message is empty!"))
|
||||
return@create
|
||||
}
|
||||
|
||||
if (threadRecipient == null) {
|
||||
emitter.onError(RecipientFormattingException("Badly formatted"))
|
||||
return@create
|
||||
}
|
||||
|
||||
val message = OutgoingMessage(
|
||||
threadRecipient = threadRecipient,
|
||||
sentTimeMillis = System.currentTimeMillis(),
|
||||
body = body,
|
||||
expiresIn = threadRecipient.expiresInSeconds.seconds.inWholeMilliseconds,
|
||||
isUrgent = true,
|
||||
isSecure = true,
|
||||
bodyRanges = bodyRanges,
|
||||
scheduledDate = scheduledDate,
|
||||
outgoingQuote = quote,
|
||||
messageToEdit = messageToEdit?.id ?: 0,
|
||||
mentions = mentions
|
||||
)
|
||||
|
||||
MessageSender.send(
|
||||
ApplicationDependencies.getApplication(),
|
||||
message,
|
||||
threadId,
|
||||
MessageSender.SendType.SIGNAL,
|
||||
metricId
|
||||
) {
|
||||
emitter.onComplete()
|
||||
}
|
||||
}
|
||||
|
||||
return sendCompletable
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun setLastVisibleMessageTimestamp(threadId: Long, lastVisibleMessageTimestamp: Long) {
|
||||
SignalExecutors.BOUNDED.submit { SignalDatabase.threads.setLastScrolled(threadId, lastVisibleMessageTimestamp) }
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
@@ -24,9 +25,14 @@ import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper
|
||||
import org.thoughtcrime.securesms.conversation.colors.NameColor
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.model.Mention
|
||||
import org.thoughtcrime.securesms.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.Quote
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.hasGiftBadge
|
||||
@@ -159,6 +165,30 @@ class ConversationViewModel(
|
||||
fun requestMarkRead(timestamp: Long) {
|
||||
}
|
||||
|
||||
fun sendMessage(
|
||||
metricId: String?,
|
||||
body: String,
|
||||
slideDeck: SlideDeck?,
|
||||
scheduledDate: Long,
|
||||
messageToEdit: MessageId?,
|
||||
quote: QuoteModel?,
|
||||
mentions: List<Mention>,
|
||||
bodyRanges: BodyRangeList?
|
||||
): Completable {
|
||||
return repository.sendMessage(
|
||||
threadId = threadId,
|
||||
threadRecipient = recipientSnapshot,
|
||||
metricId = metricId,
|
||||
body = body,
|
||||
slideDeck = slideDeck,
|
||||
scheduledDate = scheduledDate,
|
||||
messageToEdit = messageToEdit,
|
||||
quote = quote,
|
||||
mentions = mentions,
|
||||
bodyRanges = bodyRanges
|
||||
).observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val args: Args,
|
||||
private val repository: ConversationRepository,
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
android:scrollbars="vertical"
|
||||
android:splitMotionEvents="false"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/conversation_bottom_panel_barrier"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:itemCount="20"
|
||||
tools:listitem="@layout/conversation_item_sent_text_only" />
|
||||
|
||||
@@ -122,19 +122,44 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/conversation_bottom_panel_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="conversation_input_panel" />
|
||||
app:constraint_referenced_ids="conversation_input_panel,attachment_editor_stub" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/attachment_editor_stub"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/attachment_editor"
|
||||
android:layout="@layout/conversation_activity_attachment_editor_stub"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/conversation_input_panel"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/conversation_input_panel"
|
||||
layout="@layout/conversation_input_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="@id/navigation_bar_guideline" />
|
||||
app:layout_constraintBottom_toTopOf="@+id/conversation_input_space_left"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/conversation_input_space_left"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:windowBackground"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/navigation_bar_guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="160/160 (1)"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</org.thoughtcrime.securesms.components.InsetAwareConstraintLayout>
|
||||
|
||||
Reference in New Issue
Block a user