Add media keyboard support in CFv2.
This commit is contained in:
parent
b042945fef
commit
65255121de
8 changed files with 171 additions and 21 deletions
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue