Add mentions support to CFv2.
This commit is contained in:
parent
0e6a3dd408
commit
04a5e56da7
8 changed files with 446 additions and 7 deletions
|
@ -1,6 +1,11 @@
|
|||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import android.content.Context
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation
|
||||
import org.thoughtcrime.securesms.database.MentionUtil
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
/**
|
||||
* Encapsulate how to replace a query with a user selected result.
|
||||
|
@ -13,4 +18,18 @@ sealed class InlineQueryReplacement(@get:JvmName("isKeywordSearch") val keywordS
|
|||
return emoji
|
||||
}
|
||||
}
|
||||
|
||||
class Mention(private val recipient: Recipient, keywordSearch: Boolean) : InlineQueryReplacement(keywordSearch) {
|
||||
override fun toCharSequence(context: Context): CharSequence {
|
||||
val builder = SpannableStringBuilder().apply {
|
||||
append(MentionUtil.MENTION_STARTER)
|
||||
append(recipient.getDisplayName(context))
|
||||
append(" ")
|
||||
}
|
||||
|
||||
builder.setSpan(MentionAnnotation.mentionAnnotationForRecipientId(recipient.id), 0, builder.length - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
return builder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.concurrent.addTo
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.ComposeText
|
||||
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerFragmentV2
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.AnyMappingModel
|
||||
import org.thoughtcrime.securesms.util.doOnEachLayout
|
||||
|
||||
/**
|
||||
* Controller for inline search results.
|
||||
*/
|
||||
class InlineQueryResultsControllerV2(
|
||||
private val parentFragment: Fragment,
|
||||
private val viewModel: InlineQueryViewModelV2,
|
||||
private val anchor: View,
|
||||
private val container: ViewGroup,
|
||||
editText: ComposeText
|
||||
) : InlineQueryResultsPopup.Callback {
|
||||
|
||||
companion object {
|
||||
private const val MENTION_TAG = "mention_fragment_tag"
|
||||
}
|
||||
|
||||
private val lifecycleDisposable: LifecycleDisposable = LifecycleDisposable()
|
||||
private var emojiPopup: InlineQueryResultsPopup? = null
|
||||
private var mentionFragment: MentionsPickerFragmentV2? = null
|
||||
private var previousResults: InlineQueryViewModelV2.Results? = null
|
||||
private var canShow: Boolean = false
|
||||
private var isLandscape: Boolean = false
|
||||
|
||||
init {
|
||||
lifecycleDisposable.bindTo(parentFragment.viewLifecycleOwner)
|
||||
|
||||
viewModel
|
||||
.results
|
||||
.subscribeBy { updateList(it) }
|
||||
.addTo(lifecycleDisposable)
|
||||
|
||||
parentFragment.viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
dismiss()
|
||||
}
|
||||
})
|
||||
|
||||
canShow = editText.hasFocus()
|
||||
editText.addOnFocusChangeListener { _, hasFocus ->
|
||||
canShow = hasFocus
|
||||
updateList(previousResults ?: InlineQueryViewModelV2.None)
|
||||
}
|
||||
|
||||
anchor.doOnEachLayout { emojiPopup?.updateWithAnchor() }
|
||||
}
|
||||
|
||||
override fun onSelection(model: AnyMappingModel) {
|
||||
viewModel.onSelection(model)
|
||||
}
|
||||
|
||||
override fun onDismiss() {
|
||||
emojiPopup = null
|
||||
}
|
||||
|
||||
fun onOrientationChange(isLandscape: Boolean) {
|
||||
this.isLandscape = isLandscape
|
||||
|
||||
if (isLandscape) {
|
||||
dismiss()
|
||||
} else {
|
||||
updateList(previousResults ?: InlineQueryViewModelV2.None)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateList(results: InlineQueryViewModelV2.Results) {
|
||||
previousResults = results
|
||||
if (results is InlineQueryViewModelV2.None || !canShow || isLandscape) {
|
||||
dismiss()
|
||||
} else if (results is InlineQueryViewModelV2.EmojiResults) {
|
||||
showEmojiPopup(results)
|
||||
} else if (results is InlineQueryViewModelV2.MentionResults) {
|
||||
showMentionsPickerFragment(results)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEmojiPopup(results: InlineQueryViewModelV2.EmojiResults) {
|
||||
if (emojiPopup != null) {
|
||||
emojiPopup?.setResults(results.results)
|
||||
} else {
|
||||
emojiPopup = InlineQueryResultsPopup(
|
||||
anchor = anchor,
|
||||
container = container,
|
||||
results = results.results,
|
||||
baseOffsetX = DimensionUnit.DP.toPixels(16f).toInt(),
|
||||
callback = this
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMentionsPickerFragment(results: InlineQueryViewModelV2.MentionResults) {
|
||||
if (mentionFragment == null) {
|
||||
mentionFragment = parentFragment.childFragmentManager.findFragmentByTag(MENTION_TAG) as? MentionsPickerFragmentV2
|
||||
if (mentionFragment == null) {
|
||||
mentionFragment = MentionsPickerFragmentV2()
|
||||
parentFragment.childFragmentManager.commit {
|
||||
replace(R.id.mention_fragment_container, mentionFragment!!)
|
||||
runOnCommit { mentionFragment!!.updateList(results.results) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parentFragment.childFragmentManager.commit {
|
||||
show(mentionFragment!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismiss() {
|
||||
emojiPopup?.dismiss()
|
||||
emojiPopup = null
|
||||
|
||||
mentionFragment?.let {
|
||||
parentFragment.childFragmentManager.commit {
|
||||
hide(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
|
||||
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionViewState
|
||||
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerRepositoryV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationRecipientRepository
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchRepository
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.AnyMappingModel
|
||||
|
||||
/**
|
||||
* Activity (at least) scope view model for managing inline queries. The view model needs to be larger scope so it can
|
||||
* be shared between the fragment requesting the search and the fragment used for displaying the results.
|
||||
*/
|
||||
class InlineQueryViewModelV2(
|
||||
private val recipientRepository: ConversationRecipientRepository,
|
||||
private val mentionsPickerRepository: MentionsPickerRepositoryV2 = MentionsPickerRepositoryV2(),
|
||||
private val emojiSearchRepository: EmojiSearchRepository = EmojiSearchRepository(ApplicationDependencies.getApplication()),
|
||||
private val recentEmojis: RecentEmojiPageModel = RecentEmojiPageModel(ApplicationDependencies.getApplication(), TextSecurePreferences.RECENT_STORAGE_KEY)
|
||||
) : ViewModel() {
|
||||
|
||||
private val querySubject: PublishSubject<InlineQuery> = PublishSubject.create()
|
||||
private val selectionSubject: PublishSubject<InlineQueryReplacement> = PublishSubject.create()
|
||||
private val isMentionsShowingSubject: BehaviorSubject<Boolean> = BehaviorSubject.createDefault(false)
|
||||
|
||||
val results: Observable<Results>
|
||||
val selection: Observable<InlineQueryReplacement> = selectionSubject.observeOn(AndroidSchedulers.mainThread())
|
||||
val isMentionsShowing: Observable<Boolean> = isMentionsShowingSubject.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
init {
|
||||
results = querySubject.switchMap { query ->
|
||||
when (query) {
|
||||
is InlineQuery.Emoji -> queryEmoji(query)
|
||||
is InlineQuery.Mention -> queryMentions(query)
|
||||
InlineQuery.NoQuery -> Observable.just(None)
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun onQueryChange(inlineQuery: InlineQuery) {
|
||||
querySubject.onNext(inlineQuery)
|
||||
}
|
||||
|
||||
private fun queryEmoji(query: InlineQuery.Emoji): Observable<Results> {
|
||||
return emojiSearchRepository
|
||||
.submitQuery(query.query)
|
||||
.map { r -> if (r.isEmpty()) None else EmojiResults(toMappingModels(r, query.keywordSearch)) }
|
||||
.toObservable()
|
||||
}
|
||||
|
||||
private fun queryMentions(query: InlineQuery.Mention): Observable<Results> {
|
||||
return recipientRepository
|
||||
.groupRecord
|
||||
.take(1)
|
||||
.switchMap { group ->
|
||||
if (group.isPresent) {
|
||||
mentionsPickerRepository.search(query.query, group.get().members)
|
||||
.map { results -> if (results.isEmpty()) None else MentionResults(results.map { MentionViewState(it) }) }
|
||||
.toObservable()
|
||||
} else {
|
||||
Observable.just(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onSelection(model: AnyMappingModel) {
|
||||
when (model) {
|
||||
is InlineQueryEmojiResult.Model -> {
|
||||
recentEmojis.onCodePointSelected(model.preferredEmoji)
|
||||
selectionSubject.onNext(InlineQueryReplacement.Emoji(model.preferredEmoji, model.keywordSearch))
|
||||
}
|
||||
is MentionViewState -> {
|
||||
selectionSubject.onNext(InlineQueryReplacement.Mention(model.recipient, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setIsMentionsShowing(showing: Boolean) {
|
||||
isMentionsShowingSubject.onNext(showing)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun toMappingModels(emojiWithLabels: List<String>, keywordSearch: Boolean): List<AnyMappingModel> {
|
||||
val emojiValues = SignalStore.emojiValues()
|
||||
return emojiWithLabels
|
||||
.distinct()
|
||||
.map { emoji ->
|
||||
InlineQueryEmojiResult.Model(
|
||||
canonicalEmoji = emoji,
|
||||
preferredEmoji = emojiValues.getPreferredVariation(emoji),
|
||||
keywordSearch = keywordSearch
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Results
|
||||
object None : Results
|
||||
data class EmojiResults(val results: List<AnyMappingModel>) : Results
|
||||
data class MentionResults(val results: List<AnyMappingModel>) : Results
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package org.thoughtcrime.securesms.conversation.ui.mentions
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.concurrent.addTo
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryViewModelV2
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import org.thoughtcrime.securesms.util.viewholders.RecipientViewHolder
|
||||
|
||||
/**
|
||||
* Show inline query results for mentions in a group during message compose.
|
||||
*/
|
||||
class MentionsPickerFragmentV2 : LoggingFragment() {
|
||||
|
||||
private val lifecycleDisposable: LifecycleDisposable = LifecycleDisposable()
|
||||
private val viewModel: InlineQueryViewModelV2 by activityViewModels()
|
||||
|
||||
private lateinit var adapter: MentionsPickerAdapter
|
||||
private lateinit var list: RecyclerView
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
|
||||
private val lockSheetAfterListUpdate = Runnable { behavior.setHideable(false) }
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.mentions_picker_fragment, container, false)
|
||||
list = view.findViewById(R.id.mentions_picker_list)
|
||||
behavior = BottomSheetBehavior.from(view.findViewById(R.id.mentions_picker_bottom_sheet))
|
||||
initializeBehavior()
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
||||
|
||||
initializeList()
|
||||
viewModel
|
||||
.results
|
||||
.subscribeBy {
|
||||
if (it !is InlineQueryViewModelV2.MentionResults) {
|
||||
updateList(emptyList())
|
||||
} else {
|
||||
updateList(it.results)
|
||||
}
|
||||
}
|
||||
.addTo(
|
||||
lifecycleDisposable
|
||||
)
|
||||
|
||||
viewModel
|
||||
.isMentionsShowing
|
||||
.subscribeBy { isShowing ->
|
||||
if (isShowing && VibrateUtil.isHapticFeedbackEnabled(requireContext())) {
|
||||
VibrateUtil.vibrateTick(requireContext())
|
||||
}
|
||||
}
|
||||
.addTo(lifecycleDisposable)
|
||||
}
|
||||
|
||||
private fun initializeBehavior() {
|
||||
behavior.isHideable = true
|
||||
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
behavior.addBottomSheetCallback(object : BottomSheetCallback() {
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
||||
adapter.submitList(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
|
||||
})
|
||||
}
|
||||
|
||||
private fun initializeList() {
|
||||
adapter = MentionsPickerAdapter(MentionEventListener()) { updateBottomSheetBehavior(adapter.itemCount) }
|
||||
|
||||
list.layoutManager = LinearLayoutManager(requireContext())
|
||||
list.adapter = adapter
|
||||
list.itemAnimator = null
|
||||
}
|
||||
|
||||
fun updateList(mappingModels: List<MappingModel<*>>) {
|
||||
if (adapter.itemCount > 0 && mappingModels.isEmpty()) {
|
||||
updateBottomSheetBehavior(0)
|
||||
} else {
|
||||
adapter.submitList(mappingModels)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBottomSheetBehavior(count: Int) {
|
||||
val isShowing = count > 0
|
||||
if (isShowing) {
|
||||
list.scrollToPosition(0)
|
||||
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
handler.post(lockSheetAfterListUpdate)
|
||||
} else {
|
||||
handler.removeCallbacks(lockSheetAfterListUpdate)
|
||||
behavior.isHideable = true
|
||||
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
}
|
||||
viewModel.setIsMentionsShowing(isShowing)
|
||||
}
|
||||
|
||||
private inner class MentionEventListener : RecipientViewHolder.EventListener<MentionViewState> {
|
||||
override fun onModelClick(model: MentionViewState) {
|
||||
viewModel.onSelection(model)
|
||||
}
|
||||
|
||||
override fun onClick(recipient: Recipient) = Unit
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.thoughtcrime.securesms.conversation.ui.mentions
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
/**
|
||||
* Search for members that match the query for rendering in the mentions picker during message compose.
|
||||
*/
|
||||
class MentionsPickerRepositoryV2(
|
||||
private val recipients: RecipientTable = SignalDatabase.recipients
|
||||
) {
|
||||
fun search(query: String, members: List<RecipientId>): Single<List<Recipient>> {
|
||||
return if (query.isBlank() || members.isEmpty()) {
|
||||
Single.just(emptyList())
|
||||
} else {
|
||||
Single
|
||||
.fromCallable { recipients.queryRecipientsForMentions(query, members) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -165,8 +165,8 @@ import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSe
|
|||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQuery
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryChangedListener
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryReplacement
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryResultsController
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryViewModel
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryResultsControllerV2
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryViewModelV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupCallViewModel
|
||||
import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupViewModel
|
||||
import org.thoughtcrime.securesms.conversation.v2.keyboard.AttachmentKeyboardFragment
|
||||
|
@ -277,6 +277,7 @@ import org.thoughtcrime.securesms.util.StorageUtil
|
|||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.WindowUtil
|
||||
import org.thoughtcrime.securesms.util.activityViewModel
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture
|
||||
import org.thoughtcrime.securesms.util.doAfterNextLayout
|
||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||
|
@ -377,14 +378,17 @@ class ConversationFragment :
|
|||
StickerSuggestionsViewModel()
|
||||
}
|
||||
|
||||
private val inlineQueryViewModel: InlineQueryViewModel by activityViewModels()
|
||||
private val inlineQueryController: InlineQueryResultsController by lazy {
|
||||
InlineQueryResultsController(
|
||||
private val inlineQueryViewModel: InlineQueryViewModelV2 by activityViewModel {
|
||||
InlineQueryViewModelV2(recipientRepository = conversationRecipientRepository)
|
||||
}
|
||||
|
||||
private val inlineQueryController: InlineQueryResultsControllerV2 by lazy {
|
||||
InlineQueryResultsControllerV2(
|
||||
this,
|
||||
inlineQueryViewModel,
|
||||
inputPanel,
|
||||
(requireView() as ViewGroup),
|
||||
composeText,
|
||||
viewLifecycleOwner
|
||||
composeText
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -531,6 +535,7 @@ class ConversationFragment :
|
|||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
ToolbarDependentMarginListener(binding.toolbar)
|
||||
inlineQueryController.onOrientationChange(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util
|
|||
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
@ -35,3 +36,12 @@ inline fun <reified VM : ViewModel> Fragment.viewModel(
|
|||
factoryProducer = ViewModelFactory.factoryProducer(create)
|
||||
)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
inline fun <reified VM : ViewModel> Fragment.activityViewModel(
|
||||
noinline create: () -> VM
|
||||
): Lazy<VM> {
|
||||
return activityViewModels(
|
||||
factoryProducer = ViewModelFactory.factoryProducer(create)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -156,6 +156,15 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/mention_fragment_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toStartOf="@id/parent_start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
|
||||
app:layout_constraintBottom_toTopOf="@id/conversation_bottom_panel_barrier"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/conversation_bottom_panel_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
Loading…
Add table
Reference in a new issue