mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Merge pull request #4065 from vector-im/feature/fga/improve_room_detail_start
Feature/fga/improve room detail start
This commit is contained in:
commit
9c559a7c2c
1
changelog.d/4065.misc
Normal file
1
changelog.d/4065.misc
Normal file
@ -0,0 +1 @@
|
||||
Improve performances on RoomDetail screen
|
@ -17,6 +17,10 @@
|
||||
package im.vector.app.features.reactions.data
|
||||
|
||||
import im.vector.app.InstrumentedTest
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
@ -30,64 +34,80 @@ import kotlin.system.measureTimeMillis
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class EmojiDataSourceTest : InstrumentedTest {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
@Test
|
||||
fun checkParsingTime() {
|
||||
val time = measureTimeMillis {
|
||||
EmojiDataSource(context().resources)
|
||||
createEmojiDataSource()
|
||||
}
|
||||
|
||||
assertTrue("Too long to parse", time < 100)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkNumberOfResult() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
assertTrue("Wrong number of emojis", emojiDataSource.rawData.emojis.size >= 500)
|
||||
assertTrue("Wrong number of categories", emojiDataSource.rawData.categories.size >= 8)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val rawData = runBlocking {
|
||||
emojiDataSource.rawData.await()
|
||||
}
|
||||
assertTrue("Wrong number of emojis", rawData.emojis.size >= 500)
|
||||
assertTrue("Wrong number of categories", rawData.categories.size >= 8)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchTestEmptySearch() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertTrue("Empty search should return at least 500 results", emojiDataSource.filterWith("").size >= 500)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("")
|
||||
}
|
||||
assertTrue("Empty search should return at least 500 results", result.size >= 500)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchTestNoResult() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertTrue("Should not have result", emojiDataSource.filterWith("noresult").isEmpty())
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("noresult")
|
||||
}
|
||||
assertTrue("Should not have result", result.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchTestOneResult() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertEquals("Should have 1 result", 1, emojiDataSource.filterWith("france").size)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("france")
|
||||
}
|
||||
assertEquals("Should have 1 result", 1, result.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchTestManyResult() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertTrue("Should have many result", emojiDataSource.filterWith("fra").size > 1)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("fra")
|
||||
}
|
||||
assertTrue("Should have many result", result.size > 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTada() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
val result = emojiDataSource.filterWith("tada")
|
||||
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.filterWith("tada")
|
||||
}
|
||||
assertEquals("Should find tada emoji", 1, result.size)
|
||||
assertEquals("Should find tada emoji", "🎉", result[0].emoji)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQuickReactions() {
|
||||
val emojiDataSource = EmojiDataSource(context().resources)
|
||||
|
||||
assertEquals("Should have 8 quick reactions", 8, emojiDataSource.getQuickReactions().size)
|
||||
val emojiDataSource = createEmojiDataSource()
|
||||
val result = runBlocking {
|
||||
emojiDataSource.getQuickReactions()
|
||||
}
|
||||
assertEquals("Should have 8 quick reactions", 8, result.size)
|
||||
}
|
||||
|
||||
private fun createEmojiDataSource() = EmojiDataSource(coroutineScope, context().resources)
|
||||
}
|
||||
|
@ -98,6 +98,7 @@ import im.vector.app.features.usercode.UserCodeActivity
|
||||
import im.vector.app.features.widgets.WidgetActivity
|
||||
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
|
||||
import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
@ -129,6 +130,7 @@ interface ScreenComponent {
|
||||
fun uiStateRepository(): UiStateRepository
|
||||
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
|
||||
fun autoAcceptInvites(): AutoAcceptInvites
|
||||
fun appCoroutineScope(): CoroutineScope
|
||||
|
||||
/* ==========================================================================================
|
||||
* Activities
|
||||
|
@ -61,6 +61,7 @@ import im.vector.app.features.session.SessionListener
|
||||
import im.vector.app.features.settings.VectorDataStore
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
@ -168,6 +169,8 @@ interface VectorComponent {
|
||||
|
||||
fun webRtcCallManager(): WebRtcCallManager
|
||||
|
||||
fun appCoroutineScope(): CoroutineScope
|
||||
|
||||
fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder
|
||||
|
||||
@Component.Factory
|
||||
|
@ -33,12 +33,16 @@ import im.vector.app.features.pin.PinCodeStore
|
||||
import im.vector.app.features.pin.SharedPrefPinCodeStore
|
||||
import im.vector.app.features.ui.SharedPreferencesUiStateRepository
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
abstract class VectorModule {
|
||||
@ -94,6 +98,13 @@ abstract class VectorModule {
|
||||
fun providesHomeServerHistoryService(matrix: Matrix): HomeServerHistoryService {
|
||||
return matrix.homeServerHistoryService()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@Singleton
|
||||
fun providesApplicationCoroutineScope(): CoroutineScope {
|
||||
return CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.platform
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
|
||||
fun <T> LifecycleOwner.lifecycleAwareLazy(initializer: () -> T): Lazy<T> = LifecycleAwareLazy(this, initializer)
|
||||
|
||||
private object UninitializedValue
|
||||
|
||||
class LifecycleAwareLazy<out T>(
|
||||
private val owner: LifecycleOwner,
|
||||
initializer: () -> T
|
||||
) : Lazy<T>, LifecycleObserver {
|
||||
|
||||
private var initializer: (() -> T)? = initializer
|
||||
|
||||
private var _value: Any? = UninitializedValue
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override val value: T
|
||||
@MainThread
|
||||
get() {
|
||||
if (_value === UninitializedValue) {
|
||||
_value = initializer!!()
|
||||
attachToLifecycle()
|
||||
}
|
||||
return _value as T
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
fun resetValue() {
|
||||
_value = UninitializedValue
|
||||
detachFromLifecycle()
|
||||
}
|
||||
|
||||
private fun attachToLifecycle() {
|
||||
if (getLifecycleOwner().lifecycle.currentState == Lifecycle.State.DESTROYED) {
|
||||
throw IllegalStateException("Initialization failed because lifecycle has been destroyed!")
|
||||
}
|
||||
getLifecycleOwner().lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
private fun detachFromLifecycle() {
|
||||
getLifecycleOwner().lifecycle.removeObserver(this)
|
||||
}
|
||||
|
||||
private fun getLifecycleOwner() = when (owner) {
|
||||
is Fragment -> owner.viewLifecycleOwner
|
||||
else -> owner
|
||||
}
|
||||
|
||||
override fun isInitialized(): Boolean = _value !== UninitializedValue
|
||||
|
||||
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
|
||||
}
|
@ -19,7 +19,6 @@ package im.vector.app.core.ui.views
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.databinding.ViewFailedMessagesWarningBinding
|
||||
|
||||
@ -49,8 +48,4 @@ class FailedMessagesWarningView @JvmOverloads constructor(
|
||||
views.failedMessagesDeleteAllButton.setOnClickListener { callback?.onDeleteAllClicked() }
|
||||
views.failedMessagesRetryButton.setOnClickListener { callback?.onRetryClicked() }
|
||||
}
|
||||
|
||||
fun render(hasFailedMessages: Boolean) {
|
||||
isVisible = hasFailedMessages
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,11 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import im.vector.app.features.autocomplete.AutocompleteClickListener
|
||||
import im.vector.app.features.autocomplete.RecyclerViewPresenter
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||
@ -28,11 +33,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||
private val controller: AutocompleteEmojiController) :
|
||||
RecyclerViewPresenter<String>(context), AutocompleteClickListener<String> {
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
init {
|
||||
controller.listener = this
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
coroutineScope.coroutineContext.cancelChildren()
|
||||
controller.listener = null
|
||||
}
|
||||
|
||||
@ -45,12 +53,14 @@ class AutocompleteEmojiPresenter @Inject constructor(context: Context,
|
||||
}
|
||||
|
||||
override fun onQuery(query: CharSequence?) {
|
||||
val data = if (query.isNullOrBlank()) {
|
||||
// Return common emojis
|
||||
emojiDataSource.getQuickReactions()
|
||||
} else {
|
||||
emojiDataSource.filterWith(query.toString())
|
||||
coroutineScope.launch {
|
||||
val data = if (query.isNullOrBlank()) {
|
||||
// Return common emojis
|
||||
emojiDataSource.getQuickReactions()
|
||||
} else {
|
||||
emojiDataSource.filterWith(query.toString())
|
||||
}
|
||||
controller.setData(data)
|
||||
}
|
||||
controller.setData(data)
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ package im.vector.app.features.autocomplete.member
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.autocomplete.AutocompleteClickListener
|
||||
import im.vector.app.features.autocomplete.RecyclerViewPresenter
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
@ -35,7 +35,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
|
||||
private val controller: AutocompleteMemberController
|
||||
) : RecyclerViewPresenter<RoomMemberSummary>(context), AutocompleteClickListener<RoomMemberSummary> {
|
||||
|
||||
private val room = session.getRoom(roomId)!!
|
||||
private val room by lazy { session.getRoom(roomId)!! }
|
||||
|
||||
init {
|
||||
controller.listener = this
|
||||
|
@ -86,6 +86,7 @@ import im.vector.app.core.hardware.vibrate
|
||||
import im.vector.app.core.intent.getFilenameFromUri
|
||||
import im.vector.app.core.intent.getMimeTypeFromUri
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.platform.lifecycleAwareLazy
|
||||
import im.vector.app.core.platform.showOptimizedSnackbar
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.ui.views.CurrentCallsView
|
||||
@ -153,6 +154,7 @@ import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
||||
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
|
||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
|
||||
import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews
|
||||
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
|
||||
import im.vector.app.features.html.EventHtmlRenderer
|
||||
import im.vector.app.features.html.PillImageSpan
|
||||
@ -312,7 +314,10 @@ class RoomDetailFragment @Inject constructor(
|
||||
private var lockSendButton = false
|
||||
private val currentCallsViewPresenter = CurrentCallsViewPresenter()
|
||||
|
||||
private lateinit var emojiPopup: EmojiPopup
|
||||
private val lazyLoadedViews = RoomDetailLazyLoadedViews()
|
||||
private val emojiPopup: EmojiPopup by lifecycleAwareLazy {
|
||||
createEmojiPopup()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -340,16 +345,15 @@ class RoomDetailFragment @Inject constructor(
|
||||
onTapToReturnToCall = ::onTapToReturnToCall
|
||||
)
|
||||
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
||||
lazyLoadedViews.bind(views)
|
||||
setupToolbar(views.roomToolbar)
|
||||
setupRecyclerView()
|
||||
setupComposer()
|
||||
setupInviteView()
|
||||
setupNotificationView()
|
||||
setupJumpToReadMarkerView()
|
||||
setupActiveCallView()
|
||||
setupJumpToBottomView()
|
||||
setupEmojiPopup()
|
||||
setupFailedMessagesWarningView()
|
||||
setupEmojiButton()
|
||||
setupRemoveJitsiWidgetView()
|
||||
setupVoiceMessageView()
|
||||
|
||||
@ -593,8 +597,14 @@ class RoomDetailFragment @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun setupEmojiPopup() {
|
||||
emojiPopup = EmojiPopup
|
||||
private fun setupEmojiButton() {
|
||||
views.composerLayout.views.composerEmojiButton.debouncedClicks {
|
||||
emojiPopup.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmojiPopup(): EmojiPopup {
|
||||
return EmojiPopup
|
||||
.Builder
|
||||
.fromRootView(views.rootConstraintLayout)
|
||||
.setKeyboardAnimationStyle(R.style.emoji_fade_animation_style)
|
||||
@ -611,14 +621,18 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
.build(views.composerLayout.views.composerEditText)
|
||||
}
|
||||
|
||||
views.composerLayout.views.composerEmojiButton.debouncedClicks {
|
||||
emojiPopup.toggle()
|
||||
private val permissionVoiceMessageLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
// In this case, let the user start again the gesture
|
||||
} else if (deniedPermanently) {
|
||||
vectorBaseActivity.onPermissionDeniedSnackbar(R.string.denied_permission_voice_message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFailedMessagesWarningView() {
|
||||
views.failedMessagesWarningView.callback = object : FailedMessagesWarningView.Callback {
|
||||
private fun createFailedMessagesWarningCallback(): FailedMessagesWarningView.Callback {
|
||||
return object : FailedMessagesWarningView.Callback {
|
||||
override fun onDeleteAllClicked() {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.event_status_delete_all_failed_dialog_title)
|
||||
@ -636,14 +650,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val permissionVoiceMessageLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||
if (allGranted) {
|
||||
// In this case, let the user start again the gesture
|
||||
} else if (deniedPermanently) {
|
||||
vectorBaseActivity.onPermissionDeniedSnackbar(R.string.denied_permission_voice_message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupVoiceMessageView() {
|
||||
views.voiceMessageRecorderView.voiceMessagePlaybackTracker = voiceMessagePlaybackTracker
|
||||
|
||||
@ -776,6 +782,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
lazyLoadedViews.unBind()
|
||||
timelineEventController.callback = null
|
||||
timelineEventController.removeModelBuildListener(modelBuildListener)
|
||||
currentCallsViewPresenter.unBind()
|
||||
@ -783,8 +790,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
autoCompleter.clear()
|
||||
debouncer.cancelAll()
|
||||
views.timelineRecyclerView.cleanup()
|
||||
emojiPopup.dismiss()
|
||||
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@ -1359,22 +1364,22 @@ class RoomDetailFragment @Inject constructor(
|
||||
return isHandled
|
||||
}
|
||||
|
||||
private fun setupInviteView() {
|
||||
views.inviteView.callback = this
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(roomDetailViewModel) { state ->
|
||||
invalidateOptionsMenu()
|
||||
val summary = state.asyncRoomSummary()
|
||||
renderToolbar(summary, state.typingMessage)
|
||||
views.removeJitsiWidgetView.render(state)
|
||||
views.failedMessagesWarningView.render(state.hasFailedSending)
|
||||
if (state.hasFailedSending) {
|
||||
lazyLoadedViews.failedMessagesWarningView(inflateIfNeeded = true, createFailedMessagesWarningCallback())?.isVisible = true
|
||||
} else {
|
||||
lazyLoadedViews.failedMessagesWarningView(inflateIfNeeded = false)?.isVisible = false
|
||||
}
|
||||
val inviter = state.asyncInviter()
|
||||
if (summary?.membership == Membership.JOIN) {
|
||||
views.jumpToBottomView.count = summary.notificationCount
|
||||
views.jumpToBottomView.drawBadge = summary.hasUnreadMessages
|
||||
timelineEventController.update(state)
|
||||
views.inviteView.isVisible = false
|
||||
lazyLoadedViews.inviteView(false)?.isVisible = false
|
||||
if (state.tombstoneEvent == null) {
|
||||
if (state.canSendMessage) {
|
||||
if (!views.voiceMessageRecorderView.isActive()) {
|
||||
@ -1395,10 +1400,15 @@ class RoomDetailFragment @Inject constructor(
|
||||
views.notificationAreaView.render(NotificationAreaView.State.Tombstone(state.tombstoneEvent))
|
||||
}
|
||||
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
||||
views.inviteView.isVisible = true
|
||||
views.inviteView.render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState)
|
||||
// Intercept click event
|
||||
views.inviteView.setOnClickListener { }
|
||||
views.composerLayout.isVisible = false
|
||||
views.voiceMessageRecorderView.isVisible = false
|
||||
lazyLoadedViews.inviteView(true)?.apply {
|
||||
callback = this@RoomDetailFragment
|
||||
isVisible = true
|
||||
render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState)
|
||||
setOnClickListener { }
|
||||
}
|
||||
Unit
|
||||
} else if (state.asyncInviter.complete) {
|
||||
vectorBaseActivity.finish()
|
||||
}
|
||||
|
@ -39,13 +39,13 @@ import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItem
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroups
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroups
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem
|
||||
@ -276,6 +276,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
// Don't build anything if membership is not joined
|
||||
if (partialState.roomSummary?.membership != Membership.JOIN) {
|
||||
return
|
||||
}
|
||||
val timestamp = System.currentTimeMillis()
|
||||
|
||||
val showingForwardLoader = LoadingItem_()
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.views
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewStub
|
||||
import im.vector.app.core.ui.views.FailedMessagesWarningView
|
||||
import im.vector.app.databinding.FragmentRoomDetailBinding
|
||||
import im.vector.app.features.invite.VectorInviteView
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
|
||||
/**
|
||||
* This is an holder for lazy loading some views of the RoomDetail screen.
|
||||
* It's using some ViewStub where it makes sense.
|
||||
*/
|
||||
class RoomDetailLazyLoadedViews {
|
||||
|
||||
private var roomDetailBinding: FragmentRoomDetailBinding? = null
|
||||
|
||||
private var failedMessagesWarningView: FailedMessagesWarningView? = null
|
||||
private var inviteView: VectorInviteView? = null
|
||||
|
||||
fun bind(roomDetailBinding: FragmentRoomDetailBinding) {
|
||||
this.roomDetailBinding = roomDetailBinding
|
||||
}
|
||||
|
||||
fun unBind() {
|
||||
roomDetailBinding = null
|
||||
inviteView = null
|
||||
failedMessagesWarningView = null
|
||||
}
|
||||
|
||||
fun failedMessagesWarningView(inflateIfNeeded: Boolean, callback: FailedMessagesWarningView.Callback? = null): FailedMessagesWarningView? {
|
||||
return getOrInflate(inflateIfNeeded, roomDetailBinding?.failedMessagesWarningStub, this::failedMessagesWarningView)?.apply {
|
||||
this.callback = callback
|
||||
}
|
||||
}
|
||||
|
||||
fun inviteView(inflateIfNeeded: Boolean): VectorInviteView? {
|
||||
return getOrInflate(inflateIfNeeded, roomDetailBinding?.inviteViewStub, this::inviteView)
|
||||
}
|
||||
|
||||
private inline fun <reified T : View> getOrInflate(inflateIfNeeded: Boolean, stub: ViewStub?, reference: KMutableProperty0<T?>): T? {
|
||||
if (!inflateIfNeeded || stub == null || stub.parent == null) return reference.get()
|
||||
val inflatedView = stub.inflate() as T
|
||||
reference.set(inflatedView)
|
||||
return inflatedView
|
||||
}
|
||||
}
|
@ -41,15 +41,15 @@ class EmojiChooserFragment @Inject constructor(
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
|
||||
|
||||
emojiRecyclerAdapter.reactionClickListener = this
|
||||
emojiRecyclerAdapter.interactionListener = this
|
||||
|
||||
views.emojiRecyclerView.adapter = emojiRecyclerAdapter
|
||||
|
||||
viewModel.moveToSection.observe(viewLifecycleOwner) { section ->
|
||||
emojiRecyclerAdapter.scrollToSection(section)
|
||||
}
|
||||
viewModel.emojiData.observe(viewLifecycleOwner) {
|
||||
emojiRecyclerAdapter.update(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCoroutineScope() = lifecycleScope
|
||||
|
@ -17,11 +17,16 @@ package im.vector.app.features.reactions
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import im.vector.app.core.utils.LiveEvent
|
||||
import im.vector.app.features.reactions.data.EmojiData
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class EmojiChooserViewModel @Inject constructor() : ViewModel() {
|
||||
class EmojiChooserViewModel @Inject constructor(private val emojiDataSource: EmojiDataSource) : ViewModel() {
|
||||
|
||||
val emojiData: MutableLiveData<EmojiData> = MutableLiveData()
|
||||
val navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
|
||||
var selectedReaction: String? = null
|
||||
var eventId: String? = null
|
||||
@ -29,6 +34,17 @@ class EmojiChooserViewModel @Inject constructor() : ViewModel() {
|
||||
val currentSection: MutableLiveData<Int> = MutableLiveData()
|
||||
val moveToSection: MutableLiveData<Int> = MutableLiveData()
|
||||
|
||||
init {
|
||||
loadEmojiData()
|
||||
}
|
||||
|
||||
private fun loadEmojiData() {
|
||||
viewModelScope.launch {
|
||||
val rawData = emojiDataSource.rawData.await()
|
||||
emojiData.postValue(rawData)
|
||||
}
|
||||
}
|
||||
|
||||
fun onReactionSelected(reaction: String) {
|
||||
selectedReaction = reaction
|
||||
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
||||
|
@ -25,6 +25,7 @@ import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.SearchView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.jakewharton.rxbinding3.widget.queryTextChanges
|
||||
@ -36,6 +37,7 @@ import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivityEmojiReactionPickerBinding
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -91,17 +93,19 @@ class EmojiReactionPickerActivity : VectorBaseActivity<ActivityEmojiReactionPick
|
||||
viewModel = viewModelProvider.get(EmojiChooserViewModel::class.java)
|
||||
|
||||
viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID)
|
||||
|
||||
emojiDataSource.rawData.categories.forEach { category ->
|
||||
val s = category.emojis[0]
|
||||
views.tabs.newTab()
|
||||
.also { tab ->
|
||||
tab.text = emojiDataSource.rawData.emojis[s]!!.emoji
|
||||
tab.contentDescription = category.name
|
||||
}
|
||||
.also { tab ->
|
||||
views.tabs.addTab(tab)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val rawData = emojiDataSource.rawData.await()
|
||||
rawData.categories.forEach { category ->
|
||||
val s = category.emojis[0]
|
||||
views.tabs.newTab()
|
||||
.also { tab ->
|
||||
tab.text = rawData.emojis[s]!!.emoji
|
||||
tab.contentDescription = category.name
|
||||
}
|
||||
.also { tab ->
|
||||
views.tabs.addTab(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
views.tabs.addOnTabSelectedListener(tabLayoutSelectionListener)
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package im.vector.app.features.reactions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Trace
|
||||
import android.text.Layout
|
||||
@ -30,7 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import im.vector.app.features.reactions.data.EmojiData
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -43,13 +44,13 @@ import kotlin.math.abs
|
||||
* TODO: Performances
|
||||
* TODO: Scroll to section - Find a way to snap section to the top
|
||||
*/
|
||||
class EmojiRecyclerAdapter @Inject constructor(
|
||||
private val dataSource: EmojiDataSource
|
||||
) :
|
||||
class EmojiRecyclerAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
|
||||
|
||||
var reactionClickListener: ReactionClickListener? = null
|
||||
var interactionListener: InteractionListener? = null
|
||||
|
||||
private var rawData: EmojiData = EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
|
||||
private var currentFirstVisibleSection = 0
|
||||
@ -61,6 +62,12 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun update(emojiData: EmojiData) {
|
||||
rawData = emojiData
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private var scrollState = ScrollState.UNKNOWN
|
||||
private var isFastScroll = false
|
||||
|
||||
@ -71,10 +78,10 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||
if (itemPosition != RecyclerView.NO_POSITION) {
|
||||
val sectionNumber = getSectionForAbsoluteIndex(itemPosition)
|
||||
if (!isSection(itemPosition)) {
|
||||
val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
|
||||
val sectionMojis = rawData.categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[itemPosition - sectionOffset]
|
||||
val item = dataSource.rawData.emojis.getValue(emoji).emoji
|
||||
val item = rawData.emojis.getValue(emoji).emoji
|
||||
reactionClickListener?.onReactionSelected(item)
|
||||
}
|
||||
}
|
||||
@ -115,7 +122,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||
}
|
||||
|
||||
fun scrollToSection(section: Int) {
|
||||
if (section < 0 || section >= dataSource.rawData.categories.size) {
|
||||
if (section < 0 || section >= rawData.categories.size) {
|
||||
// ignore
|
||||
return
|
||||
}
|
||||
@ -149,7 +156,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||
private fun isSection(position: Int): Boolean {
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
dataSource.rawData.categories.forEach { category ->
|
||||
rawData.categories.forEach { category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position == sectionOffset - 1) return true
|
||||
sectionOffset = lastItemInSection + 2
|
||||
@ -161,7 +168,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
var index = 0
|
||||
dataSource.rawData.categories.forEach { category ->
|
||||
rawData.categories.forEach { category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position <= lastItemInSection) return index
|
||||
sectionOffset = lastItemInSection + 2
|
||||
@ -174,7 +181,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||
// Todo cache this for fast access
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
dataSource.rawData.categories.forEachIndexed { index, category ->
|
||||
rawData.categories.forEachIndexed { index, category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (section == index) return sectionOffset
|
||||
sectionOffset = lastItemInSection + 2
|
||||
@ -186,12 +193,12 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||
Trace.beginSection("MyAdapter.onBindViewHolder")
|
||||
val sectionNumber = getSectionForAbsoluteIndex(position)
|
||||
if (isSection(position)) {
|
||||
holder.bind(dataSource.rawData.categories[sectionNumber].name)
|
||||
holder.bind(rawData.categories[sectionNumber].name)
|
||||
} else {
|
||||
val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
|
||||
val sectionMojis = rawData.categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[position - sectionOffset]
|
||||
val item = dataSource.rawData.emojis[emoji]!!.emoji
|
||||
val item = rawData.emojis[emoji]!!.emoji
|
||||
(holder as EmojiViewHolder).data = item
|
||||
if (scrollState != ScrollState.SETTLING || !isFastScroll) {
|
||||
// Log.i("PERF","Bind with draw at position:$position")
|
||||
@ -220,7 +227,7 @@ class EmojiRecyclerAdapter @Inject constructor(
|
||||
super.onViewRecycled(holder)
|
||||
}
|
||||
|
||||
override fun getItemCount() = dataSource.rawData.categories
|
||||
override fun getItemCount() = rawData.categories
|
||||
.sumOf { emojiCategory -> 1 /* Section */ + emojiCategory.emojis.size }
|
||||
|
||||
abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
@ -15,17 +15,19 @@
|
||||
*/
|
||||
package im.vector.app.features.reactions
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import im.vector.app.features.reactions.data.EmojiItem
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class EmojiSearchResultViewState(
|
||||
val query: String = "",
|
||||
@ -58,11 +60,14 @@ class EmojiSearchResultViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private fun updateQuery(action: EmojiSearchAction.UpdateQuery) {
|
||||
setState {
|
||||
copy(
|
||||
query = action.queryString,
|
||||
results = dataSource.filterWith(action.queryString)
|
||||
)
|
||||
viewModelScope.launch {
|
||||
val results = dataSource.filterWith(action.queryString)
|
||||
setState {
|
||||
copy(
|
||||
query = action.queryString,
|
||||
results = results
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,53 +20,60 @@ import android.graphics.Paint
|
||||
import androidx.core.graphics.PaintCompat
|
||||
import com.squareup.moshi.Moshi
|
||||
import im.vector.app.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class EmojiDataSource @Inject constructor(
|
||||
appScope: CoroutineScope,
|
||||
resources: Resources
|
||||
) {
|
||||
private val paint = Paint()
|
||||
val rawData = resources.openRawResource(R.raw.emoji_picker_datasource)
|
||||
.use { input ->
|
||||
Moshi.Builder()
|
||||
.build()
|
||||
.adapter(EmojiData::class.java)
|
||||
.fromJson(input.bufferedReader().use { it.readText() })
|
||||
}
|
||||
?.let { parsedRawData ->
|
||||
// Add key as a keyword, it will solve the issue that ":tada" is not available in completion
|
||||
// Only add emojis to emojis/categories that can be rendered by the system
|
||||
parsedRawData.copy(
|
||||
emojis = mutableMapOf<String, EmojiItem>().apply {
|
||||
parsedRawData.emojis.keys.forEach { key ->
|
||||
val origin = parsedRawData.emojis[key] ?: return@forEach
|
||||
val rawData = appScope.async(Dispatchers.IO, CoroutineStart.LAZY) {
|
||||
resources.openRawResource(R.raw.emoji_picker_datasource)
|
||||
.use { input ->
|
||||
Moshi.Builder()
|
||||
.build()
|
||||
.adapter(EmojiData::class.java)
|
||||
.fromJson(input.bufferedReader().use { it.readText() })
|
||||
}
|
||||
?.let { parsedRawData ->
|
||||
// Add key as a keyword, it will solve the issue that ":tada" is not available in completion
|
||||
// Only add emojis to emojis/categories that can be rendered by the system
|
||||
parsedRawData.copy(
|
||||
emojis = mutableMapOf<String, EmojiItem>().apply {
|
||||
parsedRawData.emojis.keys.forEach { key ->
|
||||
val origin = parsedRawData.emojis[key] ?: return@forEach
|
||||
|
||||
// Do not add keys containing '_'
|
||||
if (isEmojiRenderable(origin.emoji)) {
|
||||
if (origin.keywords.contains(key) || key.contains("_")) {
|
||||
put(key, origin)
|
||||
} else {
|
||||
put(key, origin.copy(keywords = origin.keywords + key))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
categories = mutableListOf<EmojiCategory>().apply {
|
||||
parsedRawData.categories.forEach { entry ->
|
||||
add(EmojiCategory(entry.id, entry.name, mutableListOf<String>().apply {
|
||||
entry.emojis.forEach { e ->
|
||||
if (isEmojiRenderable(parsedRawData.emojis[e]!!.emoji)) {
|
||||
add(e)
|
||||
// Do not add keys containing '_'
|
||||
if (isEmojiRenderable(origin.emoji)) {
|
||||
if (origin.keywords.contains(key) || key.contains("_")) {
|
||||
put(key, origin)
|
||||
} else {
|
||||
put(key, origin.copy(keywords = origin.keywords + key))
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
},
|
||||
categories = mutableListOf<EmojiCategory>().apply {
|
||||
parsedRawData.categories.forEach { entry ->
|
||||
add(EmojiCategory(entry.id, entry.name, mutableListOf<String>().apply {
|
||||
entry.emojis.forEach { e ->
|
||||
if (isEmojiRenderable(parsedRawData.emojis[e]!!.emoji)) {
|
||||
add(e)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
?: EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||
)
|
||||
}
|
||||
?: EmojiData(emptyList(), emptyMap(), emptyMap())
|
||||
}
|
||||
|
||||
private val quickReactions = mutableListOf<EmojiItem>()
|
||||
|
||||
@ -74,9 +81,9 @@ class EmojiDataSource @Inject constructor(
|
||||
return PaintCompat.hasGlyph(paint, emoji)
|
||||
}
|
||||
|
||||
fun filterWith(query: String): List<EmojiItem> {
|
||||
suspend fun filterWith(query: String): List<EmojiItem> {
|
||||
val words = query.split("\\s".toRegex())
|
||||
|
||||
val rawData = this.rawData.await()
|
||||
// First add emojis with name matching query, sorted by name
|
||||
return (rawData.emojis.values
|
||||
.asSequence()
|
||||
@ -87,9 +94,9 @@ class EmojiDataSource @Inject constructor(
|
||||
// Then emojis with keyword matching any of the word in the query, sorted by name
|
||||
rawData.emojis.values
|
||||
.filter { emojiItem ->
|
||||
words.fold(true, { prev, word ->
|
||||
words.fold(true) { prev, word ->
|
||||
prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) }
|
||||
})
|
||||
}
|
||||
}
|
||||
.sortedBy { it.name })
|
||||
// and ensure they will not be present twice
|
||||
@ -97,7 +104,7 @@ class EmojiDataSource @Inject constructor(
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun getQuickReactions(): List<EmojiItem> {
|
||||
suspend fun getQuickReactions(): List<EmojiItem> {
|
||||
if (quickReactions.isEmpty()) {
|
||||
listOf(
|
||||
"thumbs-up", // 👍
|
||||
@ -109,7 +116,7 @@ class EmojiDataSource @Inject constructor(
|
||||
"rocket", // 🚀
|
||||
"eyes" // 👀
|
||||
)
|
||||
.mapNotNullTo(quickReactions) { rawData.emojis[it] }
|
||||
.mapNotNullTo(quickReactions) { rawData.await().emojis[it] }
|
||||
}
|
||||
|
||||
return quickReactions
|
||||
|
@ -155,15 +155,14 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<im.vector.app.core.ui.views.FailedMessagesWarningView
|
||||
android:id="@+id/failedMessagesWarningView"
|
||||
<ViewStub
|
||||
android:id="@+id/failedMessagesWarningStub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:inflatedId="@layout/view_stub_failed_message_warning_layout"
|
||||
app:layout_constraintBottom_toTopOf="@id/composerLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<im.vector.app.features.home.room.detail.composer.TextComposerView
|
||||
android:id="@+id/composerLayout"
|
||||
@ -180,7 +179,7 @@
|
||||
|
||||
<im.vector.app.features.home.room.detail.composer.VoiceMessageRecorderView
|
||||
android:id="@+id/voiceMessageRecorderView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@ -188,24 +187,23 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<im.vector.app.features.invite.VectorInviteView
|
||||
android:id="@+id/inviteView"
|
||||
<ViewStub
|
||||
android:id="@+id/inviteViewStub"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:background="?android:colorBackground"
|
||||
android:visibility="gone"
|
||||
android:layout="@layout/view_stub_invite_layout"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
|
||||
tools:visibility="gone" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/badgeBarrier"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="composerLayout,notificationAreaView, failedMessagesWarningView" />
|
||||
app:constraint_referenced_ids="composerLayout,notificationAreaView, failedMessagesWarningStub" />
|
||||
|
||||
<im.vector.app.core.platform.BadgeFloatingActionButton
|
||||
android:id="@+id/jumpToBottomView"
|
||||
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<im.vector.app.core.ui.views.FailedMessagesWarningView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
5
vector/src/main/res/layout/view_stub_invite_layout.xml
Normal file
5
vector/src/main/res/layout/view_stub_invite_layout.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<im.vector.app.features.invite.VectorInviteView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
Loading…
Reference in New Issue
Block a user