Add media keyboard support in CFv2.

This commit is contained in:
Cody Henthorne 2023-06-23 13:07:58 -04:00 committed by Nicholas
parent b042945fef
commit 65255121de
8 changed files with 171 additions and 21 deletions

View file

@ -34,10 +34,13 @@ class InputAwareConstraintLayout @JvmOverloads constructor(
hideInput(resetKeyboardGuideline = false) hideInput(resetKeyboardGuideline = false)
} }
fun toggleInput(fragmentCreator: FragmentCreator, imeTarget: EditText, toggled: (Boolean) -> Unit = { }) { fun toggleInput(fragmentCreator: FragmentCreator, imeTarget: EditText, showSoftKeyOnHide: Boolean = false) {
if (fragmentCreator.id == inputId) { if (fragmentCreator.id == inputId) {
hideInput(resetKeyboardGuideline = true) if (showSoftKeyOnHide) {
toggled(false) showSoftkey(imeTarget)
} else {
hideInput(resetKeyboardGuideline = true)
}
} else { } else {
hideInput(resetKeyboardGuideline = false) hideInput(resetKeyboardGuideline = false)
showInput(fragmentCreator, imeTarget) showInput(fragmentCreator, imeTarget)
@ -55,6 +58,7 @@ class InputAwareConstraintLayout @JvmOverloads constructor(
fragmentManager fragmentManager
.beginTransaction() .beginTransaction()
.replace(R.id.input_container, input!!) .replace(R.id.input_container, input!!)
.runOnCommit { (input as? InputFragment)?.show() }
.commit() .commit()
overrideKeyboardGuidelineWithPreviousHeight() overrideKeyboardGuidelineWithPreviousHeight()
@ -66,6 +70,7 @@ class InputAwareConstraintLayout @JvmOverloads constructor(
private fun hideInput(resetKeyboardGuideline: Boolean) { private fun hideInput(resetKeyboardGuideline: Boolean) {
val inputHidden = input != null val inputHidden = input != null
input?.let { input?.let {
(input as? InputFragment)?.hide()
fragmentManager fragmentManager
.beginTransaction() .beginTransaction()
.remove(it) .remove(it)
@ -94,4 +99,9 @@ class InputAwareConstraintLayout @JvmOverloads constructor(
fun onInputShown() fun onInputShown()
fun onInputHidden() fun onInputHidden()
} }
interface InputFragment {
fun show()
fun hide()
}
} }

View file

@ -105,7 +105,6 @@ public class MediaKeyboard extends FrameLayout implements InputView {
if (!isInitialised) initView(); if (!isInitialised) initView();
setVisibility(VISIBLE); setVisibility(VISIBLE);
if (keyboardListener != null) keyboardListener.onShown();
keyboardPagerFragment.show(); keyboardPagerFragment.show();
} }
@ -113,7 +112,6 @@ public class MediaKeyboard extends FrameLayout implements InputView {
public void hide(boolean immediate) { public void hide(boolean immediate) {
setVisibility(GONE); setVisibility(GONE);
onCloseEmojiSearchInternal(false); onCloseEmojiSearchInternal(false);
if (keyboardListener != null) keyboardListener.onHidden();
Log.i(TAG, "hide()"); Log.i(TAG, "hide()");
keyboardPagerFragment.hide(); keyboardPagerFragment.hide();
} }

View file

@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.contactshare.ContactShareEditActivity import org.thoughtcrime.securesms.contactshare.ContactShareEditActivity
import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.conversation.MessageSendType
import org.thoughtcrime.securesms.conversation.colors.ChatColors import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.giph.ui.GiphyActivity
import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
@ -32,6 +33,7 @@ class ConversationActivityResultContracts(fragment: Fragment, private val callba
private val contactShareLauncher = fragment.registerForActivityResult(ContactShareEditor) { contacts -> callbacks.onSendContacts(contacts) } private val contactShareLauncher = fragment.registerForActivityResult(ContactShareEditor) { contacts -> callbacks.onSendContacts(contacts) }
private val mediaSelectionLauncher = fragment.registerForActivityResult(MediaSelection) { result -> callbacks.onMediaSend(result) } private val mediaSelectionLauncher = fragment.registerForActivityResult(MediaSelection) { result -> callbacks.onMediaSend(result) }
private val gifSearchLauncher = fragment.registerForActivityResult(GifSearch) { result -> callbacks.onMediaSend(result) }
fun launchContactShareEditor(uri: Uri, chatColors: ChatColors) { fun launchContactShareEditor(uri: Uri, chatColors: ChatColors) {
contactShareLauncher.launch(uri to chatColors) contactShareLauncher.launch(uri to chatColors)
@ -41,14 +43,18 @@ class ConversationActivityResultContracts(fragment: Fragment, private val callba
mediaSelectionLauncher.launch(MediaSelectionInput(mediaList, recipientId, text)) mediaSelectionLauncher.launch(MediaSelectionInput(mediaList, recipientId, text))
} }
private object MediaSelection : ActivityResultContract<MediaSelectionInput, MediaSendActivityResult>() { fun launchGifSearch(recipientId: RecipientId, text: CharSequence?) {
gifSearchLauncher.launch(GifSearchInput(recipientId, text))
}
private object MediaSelection : ActivityResultContract<MediaSelectionInput, MediaSendActivityResult?>() {
override fun createIntent(context: Context, input: MediaSelectionInput): Intent { override fun createIntent(context: Context, input: MediaSelectionInput): Intent {
val (media, recipientId, text) = input val (media, recipientId, text) = input
return MediaSelectionActivity.editor(context, MessageSendType.SignalMessageSendType, media, recipientId, text) return MediaSelectionActivity.editor(context, MessageSendType.SignalMessageSendType, media, recipientId, text)
} }
override fun parseResult(resultCode: Int, intent: Intent?): MediaSendActivityResult { override fun parseResult(resultCode: Int, intent: Intent?): MediaSendActivityResult? {
return MediaSendActivityResult.fromData(intent!!) return intent?.let { MediaSendActivityResult.fromData(intent) }
} }
} }
@ -63,10 +69,27 @@ class ConversationActivityResultContracts(fragment: Fragment, private val callba
} }
} }
private object GifSearch : ActivityResultContract<GifSearchInput, MediaSendActivityResult?>() {
override fun createIntent(context: Context, input: GifSearchInput): Intent {
return Intent(context, GiphyActivity::class.java).apply {
putExtra(GiphyActivity.EXTRA_IS_MMS, false)
putExtra(GiphyActivity.EXTRA_RECIPIENT_ID, input.recipientId)
putExtra(GiphyActivity.EXTRA_TRANSPORT, MessageSendType.SignalMessageSendType)
putExtra(GiphyActivity.EXTRA_TEXT, input.text)
}
}
override fun parseResult(resultCode: Int, intent: Intent?): MediaSendActivityResult? {
return intent?.let { MediaSendActivityResult.fromData(intent) }
}
}
private data class MediaSelectionInput(val media: List<Media>, val recipientId: RecipientId, val text: CharSequence?) private data class MediaSelectionInput(val media: List<Media>, val recipientId: RecipientId, val text: CharSequence?)
private data class GifSearchInput(val recipientId: RecipientId, val text: CharSequence?)
interface Callbacks { interface Callbacks {
fun onSendContacts(contacts: List<Contact>) fun onSendContacts(contacts: List<Contact>)
fun onMediaSend(result: MediaSendActivityResult) fun onMediaSend(result: MediaSendActivityResult?)
} }
} }

View file

@ -52,6 +52,7 @@ import androidx.core.view.doOnPreDraw
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentResultListener import androidx.fragment.app.FragmentResultListener
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -106,6 +107,9 @@ import org.thoughtcrime.securesms.components.ProgressCardDialogFragmentArgs
import org.thoughtcrime.securesms.components.ScrollToPositionDelegate import org.thoughtcrime.securesms.components.ScrollToPositionDelegate
import org.thoughtcrime.securesms.components.SendButton import org.thoughtcrime.securesms.components.SendButton
import org.thoughtcrime.securesms.components.ViewBinderDelegate import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.components.emoji.EmojiEventListener
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
import org.thoughtcrime.securesms.components.mention.MentionAnnotation import org.thoughtcrime.securesms.components.mention.MentionAnnotation
import org.thoughtcrime.securesms.components.menu.ActionItem import org.thoughtcrime.securesms.components.menu.ActionItem
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
@ -190,6 +194,12 @@ import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationSuggestio
import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult
import org.thoughtcrime.securesms.invites.InviteActions import org.thoughtcrime.securesms.invites.InviteActions
import org.thoughtcrime.securesms.keyboard.KeyboardPage import org.thoughtcrime.securesms.keyboard.KeyboardPage
import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment
import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.gif.GifKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.sticker.StickerKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.sticker.StickerSearchDialogFragment
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2 import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2
@ -219,6 +229,7 @@ import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment
@ -233,7 +244,9 @@ import org.thoughtcrime.securesms.revealable.ViewOnceMessageActivity
import org.thoughtcrime.securesms.revealable.ViewOnceUtil import org.thoughtcrime.securesms.revealable.ViewOnceUtil
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
import org.thoughtcrime.securesms.sms.MessageSender import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.stickers.StickerEventListener
import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.stickers.StickerManagementActivity
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity
import org.thoughtcrime.securesms.stories.StoryViewerArgs import org.thoughtcrime.securesms.stories.StoryViewerArgs
@ -271,6 +284,7 @@ import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil
import java.util.Locale import java.util.Locale
import java.util.Optional import java.util.Optional
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import kotlin.time.Duration.Companion.milliseconds
/** /**
* A single unified fragment for Conversations. * A single unified fragment for Conversations.
@ -278,7 +292,13 @@ import java.util.concurrent.ExecutionException
class ConversationFragment : class ConversationFragment :
LoggingFragment(R.layout.v2_conversation_fragment), LoggingFragment(R.layout.v2_conversation_fragment),
ReactWithAnyEmojiBottomSheetDialogFragment.Callback, ReactWithAnyEmojiBottomSheetDialogFragment.Callback,
ReactionsBottomSheetDialogFragment.Callback { ReactionsBottomSheetDialogFragment.Callback,
EmojiKeyboardPageFragment.Callback,
EmojiEventListener,
GifKeyboardPageFragment.Host,
StickerEventListener,
StickerKeyboardPageFragment.Callback,
MediaKeyboard.MediaKeyboardListener {
companion object { companion object {
private val TAG = Log.tag(ConversationFragment::class.java) private val TAG = Log.tag(ConversationFragment::class.java)
@ -340,6 +360,8 @@ class ConversationFragment :
ConversationSearchViewModel(getString(R.string.note_to_self)) ConversationSearchViewModel(getString(R.string.note_to_self))
} }
private val keyboardPagerViewModel: KeyboardPagerViewModel by activityViewModels()
private val stickerViewModel: StickerSuggestionsViewModel by viewModel { private val stickerViewModel: StickerSuggestionsViewModel by viewModel {
StickerSuggestionsViewModel() StickerSuggestionsViewModel()
} }
@ -347,6 +369,7 @@ class ConversationFragment :
private val conversationTooltips = ConversationTooltips(this) private val conversationTooltips = ConversationTooltips(this)
private val colorizer = Colorizer() private val colorizer = Colorizer()
private val textDraftSaveDebouncer = Debouncer(500) private val textDraftSaveDebouncer = Debouncer(500)
private val recentEmojis: RecentEmojiPageModel by lazy { RecentEmojiPageModel(ApplicationDependencies.getApplication(), TextSecurePreferences.RECENT_STORAGE_KEY) }
private lateinit var layoutManager: LinearLayoutManager private lateinit var layoutManager: LinearLayoutManager
private lateinit var markReadHelper: MarkReadHelper private lateinit var markReadHelper: MarkReadHelper
@ -441,7 +464,7 @@ class ConversationFragment :
container.fragmentManager = childFragmentManager container.fragmentManager = childFragmentManager
ToolbarDependentMarginListener(binding.toolbar) ToolbarDependentMarginListener(binding.toolbar)
initializeMediaKeyboardToggle() initializeMediaKeyboard()
} }
override fun onViewStateRestored(savedInstanceState: Bundle?) { override fun onViewStateRestored(savedInstanceState: Bundle?) {
@ -506,6 +529,70 @@ class ConversationFragment :
clearFocusedItem() clearFocusedItem()
} }
override fun openEmojiSearch() {
// TODO [cfv2] emoji search
}
override fun onEmojiSelected(emoji: String?) {
if (emoji != null) {
inputPanel.onEmojiSelected(emoji)
recentEmojis.onCodePointSelected(emoji)
}
}
override fun onKeyEvent(keyEvent: KeyEvent?) {
if (keyEvent != null) {
inputPanel.onKeyEvent(keyEvent)
}
}
override fun openStickerSearch() {
StickerSearchDialogFragment.show(childFragmentManager)
}
override fun onStickerSelected(sticker: StickerRecord) {
sendSticker(
stickerRecord = sticker,
clearCompose = false
)
}
override fun onStickerManagementClicked() {
startActivity(StickerManagementActivity.getIntent(requireContext()))
container.hideInput()
}
override fun isMms(): Boolean {
return false
}
override fun openGifSearch() {
val recipientId = viewModel.recipientSnapshot?.id ?: return
conversationActivityResultContracts.launchGifSearch(recipientId, composeText.textTrimmed)
}
override fun onGifSelectSuccess(blobUri: Uri, width: Int, height: Int) {
setMedia(
uri = blobUri,
mediaType = SlideFactory.MediaType.from(BlobProvider.getMimeType(blobUri))!!,
width = width,
height = height,
videoGif = true
)
}
override fun onShown() {
inputPanel.mediaKeyboardListener.onShown()
}
override fun onHidden() {
inputPanel.mediaKeyboardListener.onHidden()
}
override fun onKeyboardChanged(page: KeyboardPage) {
inputPanel.mediaKeyboardListener.onKeyboardChanged(page)
}
private fun observeConversationThread() { private fun observeConversationThread() {
var firstRender = true var firstRender = true
disposables += viewModel disposables += viewModel
@ -1069,20 +1156,21 @@ class ConversationFragment :
.addTo(disposables) .addTo(disposables)
} }
private fun initializeMediaKeyboardToggle() { private fun initializeMediaKeyboard() {
val isSystemEmojiPreferred = SignalStore.settings().isPreferSystemEmoji val isSystemEmojiPreferred = SignalStore.settings().isPreferSystemEmoji
val keyboardMode: TextSecurePreferences.MediaKeyboardMode = TextSecurePreferences.getMediaKeyboardMode(requireContext()) val keyboardMode: TextSecurePreferences.MediaKeyboardMode = TextSecurePreferences.getMediaKeyboardMode(requireContext())
val stickerIntro: Boolean = !TextSecurePreferences.hasSeenStickerIntroTooltip(requireContext()) val stickerIntro: Boolean = !TextSecurePreferences.hasSeenStickerIntroTooltip(requireContext())
inputPanel.showMediaKeyboardToggle(true) inputPanel.showMediaKeyboardToggle(true)
val toggleMode = when (keyboardMode) { val keyboardPage = when (keyboardMode) {
TextSecurePreferences.MediaKeyboardMode.EMOJI -> if (isSystemEmojiPreferred) KeyboardPage.STICKER else KeyboardPage.EMOJI TextSecurePreferences.MediaKeyboardMode.EMOJI -> if (isSystemEmojiPreferred) KeyboardPage.STICKER else KeyboardPage.EMOJI
TextSecurePreferences.MediaKeyboardMode.STICKER -> KeyboardPage.STICKER TextSecurePreferences.MediaKeyboardMode.STICKER -> KeyboardPage.STICKER
TextSecurePreferences.MediaKeyboardMode.GIF -> KeyboardPage.GIF TextSecurePreferences.MediaKeyboardMode.GIF -> KeyboardPage.GIF
} }
inputPanel.setMediaKeyboardToggleMode(toggleMode) inputPanel.setMediaKeyboardToggleMode(keyboardPage)
keyboardPagerViewModel.switchToPage(keyboardPage)
if (stickerIntro) { if (stickerIntro) {
TextSecurePreferences.setMediaKeyboardMode(requireContext(), TextSecurePreferences.MediaKeyboardMode.STICKER) TextSecurePreferences.setMediaKeyboardMode(requireContext(), TextSecurePreferences.MediaKeyboardMode.STICKER)
@ -1165,6 +1253,8 @@ class ConversationFragment :
) )
sendMessageWithoutComposeInput(slide, clearCompose = clearCompose) sendMessageWithoutComposeInput(slide, clearCompose = clearCompose)
viewModel.updateStickerLastUsedTime(stickerRecord, System.currentTimeMillis().milliseconds)
} }
private fun sendMessageWithoutComposeInput( private fun sendMessageWithoutComposeInput(
@ -1199,7 +1289,7 @@ class ConversationFragment :
preUploadResults: List<MessageSender.PreUploadResult> = emptyList(), preUploadResults: List<MessageSender.PreUploadResult> = emptyList(),
afterSendComplete: () -> Unit = {} afterSendComplete: () -> Unit = {}
) { ) {
val metricId = viewModel.recipientSnapshot?.let { if (it.isGroup == true) SignalLocalMetrics.GroupMessageSend.start() else SignalLocalMetrics.IndividualMessageSend.start() } val metricId = viewModel.recipientSnapshot?.let { if (it.isGroup) SignalLocalMetrics.GroupMessageSend.start() else SignalLocalMetrics.IndividualMessageSend.start() }
val send: Completable = viewModel.sendMessage( val send: Completable = viewModel.sendMessage(
metricId = metricId, metricId = metricId,
@ -2511,7 +2601,11 @@ class ConversationFragment :
) )
} }
override fun onMediaSend(result: MediaSendActivityResult) { override fun onMediaSend(result: MediaSendActivityResult?) {
if (result == null) {
return
}
val recipientSnapshot = viewModel.recipientSnapshot val recipientSnapshot = viewModel.recipientSnapshot
if (result.recipientId != recipientSnapshot?.id) { if (result.recipientId != recipientSnapshot?.id) {
Log.w(TAG, "Result's recipientId did not match ours! Result: " + result.recipientId + ", Ours: " + recipientSnapshot?.id) Log.w(TAG, "Result's recipientId did not match ours! Result: " + result.recipientId + ", Ours: " + recipientSnapshot?.id)
@ -2955,7 +3049,7 @@ class ConversationFragment :
} }
override fun onEmojiToggle() { override fun onEmojiToggle() {
// TODO [cfv2] Not yet implemented container.toggleInput(MediaKeyboardFragmentCreator, composeText, showSoftKeyOnHide = true)
} }
override fun onLinkPreviewCanceled() { override fun onLinkPreviewCanceled() {
@ -3032,6 +3126,11 @@ class ConversationFragment :
} }
} }
private object MediaKeyboardFragmentCreator : InputAwareConstraintLayout.FragmentCreator {
override val id: Int = 2
override fun create(): Fragment = KeyboardPagerFragment()
}
private inner class KeyboardEvents : OnBackPressedCallback(false), InputAwareConstraintLayout.Listener { private inner class KeyboardEvents : OnBackPressedCallback(false), InputAwareConstraintLayout.Listener {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
container.hideInput() container.hideInput()

View file

@ -68,6 +68,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.Quote import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.database.model.StickerRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob
@ -102,6 +103,7 @@ import org.thoughtcrime.securesms.util.requireTextSlide
import java.io.IOException import java.io.IOException
import java.util.Optional import java.util.Optional
import kotlin.math.max import kotlin.math.max
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
class ConversationRepository( class ConversationRepository(
@ -225,7 +227,8 @@ class ConversationRepository(
messageToEdit = messageToEdit?.id ?: 0, messageToEdit = messageToEdit?.id ?: 0,
mentions = mentions, mentions = mentions,
sharedContacts = contacts, sharedContacts = contacts,
linkPreviews = linkPreviews linkPreviews = linkPreviews,
attachments = slideDeck?.asAttachments() ?: emptyList()
) )
if (preUploadResults.isEmpty()) { if (preUploadResults.isEmpty()) {
@ -551,6 +554,12 @@ class ConversationRepository(
} }
} }
fun updateStickerLastUsedTime(stickerRecord: StickerRecord, timestamp: Duration) {
SignalExecutors.BOUNDED_IO.execute {
SignalDatabase.stickers.updateStickerLastUsedTime(stickerRecord.rowId, timestamp.inWholeMilliseconds)
}
}
/** /**
* Glide target for a contact photo which expects an error drawable, and publishes * Glide target for a contact photo which expects an error drawable, and publishes
* the result to the given emitter. * the result to the given emitter.

View file

@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.Quote import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.database.model.StickerRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob import org.thoughtcrime.securesms.jobs.RetrieveProfileJob
@ -62,6 +63,7 @@ import org.thoughtcrime.securesms.util.hasGiftBadge
import org.thoughtcrime.securesms.util.rx.RxStore import org.thoughtcrime.securesms.util.rx.RxStore
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
import java.util.Optional import java.util.Optional
import kotlin.time.Duration
/** /**
* ConversationViewModel, which operates solely off of a thread id that never changes. * ConversationViewModel, which operates solely off of a thread id that never changes.
@ -362,4 +364,8 @@ class ConversationViewModel(
fun deleteSlideData(slides: List<Slide>) { fun deleteSlideData(slides: List<Slide>) {
repository.deleteSlideData(slides) repository.deleteSlideData(slides)
} }
fun updateStickerLastUsedTime(stickerRecord: StickerRecord, timestamp: Duration) {
repository.updateStickerLastUsedTime(stickerRecord, timestamp)
}
} }

View file

@ -12,6 +12,7 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.util.concurrent.LifecycleDisposable import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.concurrent.addTo import org.signal.core.util.concurrent.addTo
@ -62,6 +63,7 @@ class AttachmentKeyboardFragment : LoggingFragment(R.layout.attachment_keyboard_
conversationViewModel = ViewModelProvider(requireParentFragment()).get(ConversationViewModel::class.java) conversationViewModel = ViewModelProvider(requireParentFragment()).get(ConversationViewModel::class.java)
conversationViewModel conversationViewModel
.recipient .recipient
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy { .subscribeBy {
attachmentKeyboardView.setWallpaperEnabled(it.hasWallpaper()) attachmentKeyboardView.setWallpaperEnabled(it.hasWallpaper())
} }

View file

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.InputAwareConstraintLayout
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
import org.thoughtcrime.securesms.keyboard.gif.GifKeyboardPageFragment import org.thoughtcrime.securesms.keyboard.gif.GifKeyboardPageFragment
@ -20,7 +21,7 @@ import org.thoughtcrime.securesms.util.fragments.findListener
import org.thoughtcrime.securesms.util.visible import org.thoughtcrime.securesms.util.visible
import kotlin.reflect.KClass import kotlin.reflect.KClass
class KeyboardPagerFragment : Fragment() { class KeyboardPagerFragment : Fragment(), InputAwareConstraintLayout.InputFragment {
private lateinit var emojiButton: View private lateinit var emojiButton: View
private lateinit var stickerButton: View private lateinit var stickerButton: View
@ -113,7 +114,8 @@ class KeyboardPagerFragment : Fragment() {
transaction.commitAllowingStateLoss() transaction.commitAllowingStateLoss()
} }
fun show() { override fun show() {
findListener<MediaKeyboard.MediaKeyboardListener>()?.onShown()
if (isAdded && view != null) { if (isAdded && view != null) {
onHiddenChanged(false) onHiddenChanged(false)
@ -121,7 +123,8 @@ class KeyboardPagerFragment : Fragment() {
} }
} }
fun hide() { override fun hide() {
findListener<MediaKeyboard.MediaKeyboardListener>()?.onHidden()
if (isAdded && view != null) { if (isAdded && view != null) {
onHiddenChanged(true) onHiddenChanged(true)