Merge pull request #746 from vector-im/feature/fix_various_issues
Fix 2 crashes reported by the PlayStore
This commit is contained in:
		
						commit
						79ef055bfb
					
				@ -12,12 +12,14 @@ Other changes:
 | 
			
		||||
 | 
			
		||||
Bugfix 🐛:
 | 
			
		||||
 - When automardown is ON, pills are sent as MD in body (#739)
 | 
			
		||||
 - "ban" event are not rendered correctly (#716)
 | 
			
		||||
 - Fix crash when rotating screen in Room timeline
 | 
			
		||||
 | 
			
		||||
Translations 🗣:
 | 
			
		||||
 -
 | 
			
		||||
 | 
			
		||||
Build 🧱:
 | 
			
		||||
 - "ban" event are not rendered correctly (#716)
 | 
			
		||||
 -
 | 
			
		||||
 | 
			
		||||
Changes in RiotX 0.9.1 (2019-12-05)
 | 
			
		||||
===================================================
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,8 @@ interface ContentUploadStateTracker {
 | 
			
		||||
 | 
			
		||||
    fun untrack(key: String, updateListener: UpdateListener)
 | 
			
		||||
 | 
			
		||||
    fun clear()
 | 
			
		||||
 | 
			
		||||
    interface UpdateListener {
 | 
			
		||||
        fun onUpdate(state: State)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,7 @@ interface RelationService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a reaction (emoji) to the targetedEvent.
 | 
			
		||||
     * It has no effect if the user has already added the same reaction to the event.
 | 
			
		||||
     * @param targetEventId the id of the event being reacted
 | 
			
		||||
     * @param reaction the reaction (preferably emoji)
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -42,6 +42,10 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun clear() {
 | 
			
		||||
        listeners.clear()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal fun setFailure(key: String, throwable: Throwable) {
 | 
			
		||||
        val failure = ContentUploadStateTracker.State.Failure(throwable)
 | 
			
		||||
        updateState(key, failure)
 | 
			
		||||
 | 
			
		||||
@ -30,10 +30,13 @@ import im.vector.matrix.android.api.session.room.model.message.MessageType
 | 
			
		||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
 | 
			
		||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
 | 
			
		||||
import im.vector.matrix.android.api.util.Cancelable
 | 
			
		||||
import im.vector.matrix.android.api.util.NoOpCancellable
 | 
			
		||||
import im.vector.matrix.android.api.util.Optional
 | 
			
		||||
import im.vector.matrix.android.api.util.toOptional
 | 
			
		||||
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
 | 
			
		||||
import im.vector.matrix.android.internal.database.mapper.asDomain
 | 
			
		||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
 | 
			
		||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
 | 
			
		||||
import im.vector.matrix.android.internal.database.query.where
 | 
			
		||||
import im.vector.matrix.android.internal.di.UserId
 | 
			
		||||
import im.vector.matrix.android.internal.session.room.send.EncryptEventWorker
 | 
			
		||||
@ -44,6 +47,7 @@ import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEvent
 | 
			
		||||
import im.vector.matrix.android.internal.task.TaskExecutor
 | 
			
		||||
import im.vector.matrix.android.internal.task.configureWith
 | 
			
		||||
import im.vector.matrix.android.internal.util.CancelableWork
 | 
			
		||||
import im.vector.matrix.android.internal.util.fetchCopyMap
 | 
			
		||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
 | 
			
		||||
@ -54,6 +58,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
 | 
			
		||||
                                                                  private val cryptoService: CryptoService,
 | 
			
		||||
                                                                  private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
 | 
			
		||||
                                                                  private val fetchEditHistoryTask: FetchEditHistoryTask,
 | 
			
		||||
                                                                  private val timelineEventMapper: TimelineEventMapper,
 | 
			
		||||
                                                                  private val monarchy: Monarchy,
 | 
			
		||||
                                                                  private val taskExecutor: TaskExecutor)
 | 
			
		||||
    : RelationService {
 | 
			
		||||
@ -64,11 +69,27 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun sendReaction(targetEventId: String, reaction: String): Cancelable {
 | 
			
		||||
        return if (monarchy
 | 
			
		||||
                        .fetchCopyMap(
 | 
			
		||||
                                { realm ->
 | 
			
		||||
                                    TimelineEventEntity.where(realm, roomId, targetEventId).findFirst()
 | 
			
		||||
                                },
 | 
			
		||||
                                { entity, _ ->
 | 
			
		||||
                                    timelineEventMapper.map(entity)
 | 
			
		||||
                                })
 | 
			
		||||
                        ?.annotations
 | 
			
		||||
                        ?.reactionsSummary
 | 
			
		||||
                        .orEmpty()
 | 
			
		||||
                        .none { it.addedByMe && it.key == reaction }) {
 | 
			
		||||
            val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
 | 
			
		||||
                    .also { saveLocalEcho(it) }
 | 
			
		||||
            val sendRelationWork = createSendEventWork(event, true)
 | 
			
		||||
            TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork)
 | 
			
		||||
        return CancelableWork(context, sendRelationWork.id)
 | 
			
		||||
            CancelableWork(context, sendRelationWork.id)
 | 
			
		||||
        } else {
 | 
			
		||||
            Timber.w("Reaction already added")
 | 
			
		||||
            NoOpCancellable
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun undoReaction(targetEventId: String, reaction: String): Cancelable {
 | 
			
		||||
 | 
			
		||||
@ -289,6 +289,9 @@ internal class DefaultTimeline(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun addListener(listener: Timeline.Listener) = synchronized(listeners) {
 | 
			
		||||
        if (listeners.contains(listener)) {
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
        listeners.add(listener).also {
 | 
			
		||||
            postSnapshot()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -82,3 +82,6 @@ layout_constraintLeft_
 | 
			
		||||
### Will crash on API < 21. Use ?colorAccent instead
 | 
			
		||||
\?android:colorAccent
 | 
			
		||||
\?android:attr/colorAccent
 | 
			
		||||
 | 
			
		||||
### Use androidx.recyclerview.widget.RecyclerView because EpoxyRecyclerViews add behavior we do not want to
 | 
			
		||||
<com\.airbnb\.epoxy\.EpoxyRecyclerView
 | 
			
		||||
 | 
			
		||||
@ -20,16 +20,22 @@ import android.os.Bundle
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import im.vector.matrix.android.api.crypto.getAllVerificationEmojis
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.*
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
 | 
			
		||||
 | 
			
		||||
class DebugSasEmojiActivity : AppCompatActivity() {
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        setContentView(R.layout.fragment_generic_recycler_epoxy)
 | 
			
		||||
 | 
			
		||||
        setContentView(R.layout.fragment_generic_recycler)
 | 
			
		||||
        val controller = SasEmojiController()
 | 
			
		||||
        epoxyRecyclerView.setController(controller)
 | 
			
		||||
        recyclerView.configureWith(controller)
 | 
			
		||||
        controller.setData(SasState(getAllVerificationEmojis()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroy()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ import im.vector.riotx.core.di.DaggerVectorComponent
 | 
			
		||||
import im.vector.riotx.core.di.HasVectorInjector
 | 
			
		||||
import im.vector.riotx.core.di.VectorComponent
 | 
			
		||||
import im.vector.riotx.core.extensions.configureAndStart
 | 
			
		||||
import im.vector.riotx.core.utils.initKnownEmojiHashSet
 | 
			
		||||
import im.vector.riotx.core.rx.setupRxPlugin
 | 
			
		||||
import im.vector.riotx.features.configuration.VectorConfiguration
 | 
			
		||||
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
 | 
			
		||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
 | 
			
		||||
@ -55,8 +55,7 @@ import im.vector.riotx.features.version.VersionProvider
 | 
			
		||||
import im.vector.riotx.push.fcm.FcmHelper
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Date
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
import java.util.*
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
 | 
			
		||||
@ -79,14 +78,13 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
 | 
			
		||||
    lateinit var vectorComponent: VectorComponent
 | 
			
		||||
    private var fontThreadHandler: Handler? = null
 | 
			
		||||
 | 
			
		||||
//    var slowMode = false
 | 
			
		||||
 | 
			
		||||
    override fun onCreate() {
 | 
			
		||||
        super.onCreate()
 | 
			
		||||
        appContext = this
 | 
			
		||||
        vectorComponent = DaggerVectorComponent.factory().create(this)
 | 
			
		||||
        vectorComponent.inject(this)
 | 
			
		||||
        vectorUncaughtExceptionHandler.activate(this)
 | 
			
		||||
        setupRxPlugin()
 | 
			
		||||
 | 
			
		||||
        if (BuildConfig.DEBUG) {
 | 
			
		||||
            Timber.plant(Timber.DebugTree())
 | 
			
		||||
@ -138,7 +136,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
 | 
			
		||||
        })
 | 
			
		||||
        ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
 | 
			
		||||
        // This should be done as early as possible
 | 
			
		||||
        initKnownEmojiHashSet(appContext)
 | 
			
		||||
        // initKnownEmojiHashSet(appContext)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)
 | 
			
		||||
 | 
			
		||||
@ -38,6 +38,7 @@ import im.vector.riotx.features.home.room.detail.RoomDetailFragment
 | 
			
		||||
import im.vector.riotx.features.home.room.list.RoomListFragment
 | 
			
		||||
import im.vector.riotx.features.login.*
 | 
			
		||||
import im.vector.riotx.features.login.terms.LoginTermsFragment
 | 
			
		||||
import im.vector.riotx.features.reactions.EmojiChooserFragment
 | 
			
		||||
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
 | 
			
		||||
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
 | 
			
		||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
 | 
			
		||||
@ -255,4 +256,9 @@ interface FragmentModule {
 | 
			
		||||
    @IntoMap
 | 
			
		||||
    @FragmentKey(BreadcrumbsFragment::class)
 | 
			
		||||
    fun bindBreadcrumbsFragment(fragment: BreadcrumbsFragment): Fragment
 | 
			
		||||
 | 
			
		||||
    @Binds
 | 
			
		||||
    @IntoMap
 | 
			
		||||
    @FragmentKey(EmojiChooserFragment::class)
 | 
			
		||||
    fun bindEmojiChooserFragment(fragment: EmojiChooserFragment): Fragment
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.core.extensions
 | 
			
		||||
 | 
			
		||||
import androidx.recyclerview.widget.DividerItemDecoration
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import com.airbnb.epoxy.EpoxyController
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Apply a Vertical LinearLayout Manager to the recyclerView and set the adapter from the epoxy controller
 | 
			
		||||
 */
 | 
			
		||||
fun RecyclerView.configureWith(epoxyController: EpoxyController,
 | 
			
		||||
                               itemAnimator: RecyclerView.ItemAnimator? = null,
 | 
			
		||||
                               showDivider: Boolean = false,
 | 
			
		||||
                               hasFixedSize: Boolean = true) {
 | 
			
		||||
    layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
 | 
			
		||||
    itemAnimator?.let { this.itemAnimator = it }
 | 
			
		||||
    if (showDivider) {
 | 
			
		||||
        addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
 | 
			
		||||
    }
 | 
			
		||||
    setHasFixedSize(hasFixedSize)
 | 
			
		||||
    adapter = epoxyController.adapter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * To call from Fragment.onDestroyView()
 | 
			
		||||
 */
 | 
			
		||||
fun RecyclerView.cleanup() {
 | 
			
		||||
    adapter = null
 | 
			
		||||
}
 | 
			
		||||
@ -51,7 +51,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        View.inflate(context, R.layout.view_state, this)
 | 
			
		||||
        layoutParams = LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
 | 
			
		||||
        layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
 | 
			
		||||
        errorRetryView.setOnClickListener {
 | 
			
		||||
            eventCallback?.onRetryClicked()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -73,7 +73,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
 | 
			
		||||
        injectWith(screenComponent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected open fun injectWith(screenComponent: ScreenComponent) = Unit
 | 
			
		||||
    protected open fun injectWith(injector: ScreenComponent) = Unit
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        mvrxViewIdProperty.restoreFrom(savedInstanceState)
 | 
			
		||||
 | 
			
		||||
@ -135,6 +135,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
 | 
			
		||||
    override fun onSaveInstanceState(outState: Bundle) {
 | 
			
		||||
        super.onSaveInstanceState(outState)
 | 
			
		||||
        restorables.forEach { it.onSaveInstanceState(outState) }
 | 
			
		||||
        restorables.clear()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onViewStateRestored(savedInstanceState: Bundle?) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								vector/src/main/java/im/vector/riotx/core/rx/Rx.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								vector/src/main/java/im/vector/riotx/core/rx/Rx.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.core.rx
 | 
			
		||||
 | 
			
		||||
import im.vector.riotx.BuildConfig
 | 
			
		||||
import io.reactivex.plugins.RxJavaPlugins
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Make sure unhandled Rx error does not crash the app in production
 | 
			
		||||
 */
 | 
			
		||||
fun setupRxPlugin() {
 | 
			
		||||
    RxJavaPlugins.setErrorHandler { throwable ->
 | 
			
		||||
        Timber.e(throwable, "RxError")
 | 
			
		||||
 | 
			
		||||
        // Avoid crash in production
 | 
			
		||||
        if (BuildConfig.DEBUG) {
 | 
			
		||||
            throw throwable
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -16,13 +16,6 @@
 | 
			
		||||
 | 
			
		||||
package im.vector.riotx.core.utils
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import com.squareup.moshi.Moshi
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.features.reactions.EmojiDataSource
 | 
			
		||||
import kotlinx.coroutines.GlobalScope
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import java.util.regex.Pattern
 | 
			
		||||
 | 
			
		||||
private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
 | 
			
		||||
@ -49,6 +42,7 @@ private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
 | 
			
		||||
        "|\uD83C\uDCCF\uFE0F?" +
 | 
			
		||||
        "|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))")
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
// A hashset from all supported emoji
 | 
			
		||||
private var knownEmojiSet: HashSet<String>? = null
 | 
			
		||||
 | 
			
		||||
@ -56,7 +50,7 @@ fun initKnownEmojiHashSet(context: Context, done: (() -> Unit)? = null) {
 | 
			
		||||
    GlobalScope.launch {
 | 
			
		||||
        context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
 | 
			
		||||
            val moshi = Moshi.Builder().build()
 | 
			
		||||
            val jsonAdapter = moshi.adapter(EmojiDataSource.EmojiData::class.java)
 | 
			
		||||
            val jsonAdapter = moshi.adapter(EmojiData::class.java)
 | 
			
		||||
            val inputAsString = input.bufferedReader().use { it.readText() }
 | 
			
		||||
            val source = jsonAdapter.fromJson(inputAsString)
 | 
			
		||||
            knownEmojiSet = HashSet<String>().also {
 | 
			
		||||
@ -77,6 +71,7 @@ fun isSingleEmoji(string: String): Boolean {
 | 
			
		||||
    }
 | 
			
		||||
    return knownEmojiSet?.contains(string) ?: false
 | 
			
		||||
}
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test if a string contains emojis.
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,8 @@ import androidx.appcompat.app.AlertDialog
 | 
			
		||||
import com.airbnb.mvrx.activityViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
 | 
			
		||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity
 | 
			
		||||
@ -37,12 +39,16 @@ class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSetti
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        keysBackupSettingsRecyclerView.setController(keysBackupSettingsRecyclerViewController)
 | 
			
		||||
 | 
			
		||||
        keysBackupSettingsRecyclerView.configureWith(keysBackupSettingsRecyclerViewController)
 | 
			
		||||
        keysBackupSettingsRecyclerViewController.listener = this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        keysBackupSettingsRecyclerViewController.listener = null
 | 
			
		||||
        keysBackupSettingsRecyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun invalidate() = withState(viewModel) { state ->
 | 
			
		||||
        keysBackupSettingsRecyclerViewController.setData(state)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@ import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import com.google.android.material.bottomnavigation.BottomNavigationItemView
 | 
			
		||||
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
 | 
			
		||||
import im.vector.matrix.android.api.session.Session
 | 
			
		||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
 | 
			
		||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
@ -46,7 +45,6 @@ private const val INDEX_PEOPLE = 1
 | 
			
		||||
private const val INDEX_ROOMS = 2
 | 
			
		||||
 | 
			
		||||
class HomeDetailFragment @Inject constructor(
 | 
			
		||||
        private val session: Session,
 | 
			
		||||
        val homeDetailViewModelFactory: HomeDetailViewModel.Factory,
 | 
			
		||||
        private val avatarRenderer: AvatarRenderer
 | 
			
		||||
) : VectorBaseFragment(), KeysBackupBanner.Delegate {
 | 
			
		||||
@ -56,9 +54,7 @@ class HomeDetailFragment @Inject constructor(
 | 
			
		||||
    private val viewModel: HomeDetailViewModel by fragmentViewModel()
 | 
			
		||||
    private lateinit var sharedActionViewModel: HomeSharedActionViewModel
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutResId(): Int {
 | 
			
		||||
        return R.layout.fragment_home_detail
 | 
			
		||||
    }
 | 
			
		||||
    override fun getLayoutResId() = R.layout.fragment_home_detail
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
@ -23,9 +23,7 @@ import com.airbnb.mvrx.withState
 | 
			
		||||
import com.jakewharton.rxbinding3.widget.textChanges
 | 
			
		||||
import im.vector.matrix.android.api.session.user.model.User
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.hideKeyboard
 | 
			
		||||
import im.vector.riotx.core.extensions.setupAsSearch
 | 
			
		||||
import im.vector.riotx.core.extensions.showKeyboard
 | 
			
		||||
import im.vector.riotx.core.extensions.*
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
@ -48,10 +46,15 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor(
 | 
			
		||||
        setupCloseView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        directRoomController.callback = null
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupRecyclerView() {
 | 
			
		||||
        recyclerView.setHasFixedSize(true)
 | 
			
		||||
        directRoomController.callback = this
 | 
			
		||||
        recyclerView.setController(directRoomController)
 | 
			
		||||
        recyclerView.configureWith(directRoomController)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupSearchByMatrixIdView() {
 | 
			
		||||
 | 
			
		||||
@ -31,9 +31,7 @@ import com.google.android.material.chip.ChipGroup
 | 
			
		||||
import com.jakewharton.rxbinding3.widget.textChanges
 | 
			
		||||
import im.vector.matrix.android.api.session.user.model.User
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.hideKeyboard
 | 
			
		||||
import im.vector.riotx.core.extensions.observeEvent
 | 
			
		||||
import im.vector.riotx.core.extensions.setupAsSearch
 | 
			
		||||
import im.vector.riotx.core.extensions.*
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.core.utils.DimensionConverter
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_create_direct_room.*
 | 
			
		||||
@ -67,6 +65,12 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        knownUsersController.callback = null
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPrepareOptionsMenu(menu: Menu) {
 | 
			
		||||
        withState(viewModel) {
 | 
			
		||||
            val createMenuItem = menu.findItem(R.id.action_create_direct_room)
 | 
			
		||||
@ -94,11 +98,10 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupRecyclerView() {
 | 
			
		||||
        recyclerView.setHasFixedSize(true)
 | 
			
		||||
        // Don't activate animation as we might have way to much item animation when filtering
 | 
			
		||||
        recyclerView.itemAnimator = null
 | 
			
		||||
        knownUsersController.callback = this
 | 
			
		||||
        recyclerView.setController(knownUsersController)
 | 
			
		||||
        recyclerView.configureWith(knownUsersController)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupFilterView() {
 | 
			
		||||
 | 
			
		||||
@ -23,11 +23,13 @@ import com.airbnb.mvrx.Success
 | 
			
		||||
import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.extensions.observeEvent
 | 
			
		||||
import im.vector.riotx.core.platform.StateView
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.features.home.HomeSharedActionViewModel
 | 
			
		||||
import im.vector.riotx.features.home.HomeActivitySharedAction
 | 
			
		||||
import im.vector.riotx.features.home.HomeSharedActionViewModel
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_group_list.*
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
@ -45,14 +47,20 @@ class GroupListFragment @Inject constructor(
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
        sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
 | 
			
		||||
        groupController.callback = this
 | 
			
		||||
        stateView.contentView = groupListEpoxyRecyclerView
 | 
			
		||||
        groupListEpoxyRecyclerView.setController(groupController)
 | 
			
		||||
        stateView.contentView = groupListView
 | 
			
		||||
        groupListView.configureWith(groupController)
 | 
			
		||||
        viewModel.subscribe { renderState(it) }
 | 
			
		||||
        viewModel.openGroupLiveData.observeEvent(this) {
 | 
			
		||||
            sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        groupController.callback = null
 | 
			
		||||
        groupListView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun renderState(state: GroupListViewState) {
 | 
			
		||||
        when (state.asyncGroups) {
 | 
			
		||||
            is Incomplete -> stateView.state = StateView.State.Loading
 | 
			
		||||
 | 
			
		||||
@ -18,9 +18,10 @@ package im.vector.riotx.features.home.room.breadcrumbs
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.View
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.features.home.room.detail.RoomDetailSharedAction
 | 
			
		||||
import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel
 | 
			
		||||
@ -46,16 +47,13 @@ class BreadcrumbsFragment @Inject constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        breadcrumbsRecyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
        breadcrumbsRecyclerView.adapter = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupRecyclerView() {
 | 
			
		||||
        val layoutManager = LinearLayoutManager(context)
 | 
			
		||||
        breadcrumbsRecyclerView.layoutManager = layoutManager
 | 
			
		||||
        breadcrumbsRecyclerView.itemAnimator = BreadcrumbsAnimator()
 | 
			
		||||
        breadcrumbsRecyclerView.configureWith(breadcrumbsController, BreadcrumbsAnimator(), hasFixedSize = false)
 | 
			
		||||
        breadcrumbsController.listener = this
 | 
			
		||||
        breadcrumbsRecyclerView.setController(breadcrumbsController)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun renderState(state: BreadcrumbsViewState) {
 | 
			
		||||
 | 
			
		||||
@ -46,6 +46,7 @@ import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import butterknife.BindView
 | 
			
		||||
import com.airbnb.epoxy.EpoxyModel
 | 
			
		||||
import com.airbnb.epoxy.EpoxyVisibilityTracker
 | 
			
		||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
 | 
			
		||||
import com.airbnb.mvrx.*
 | 
			
		||||
import com.github.piasy.biv.BigImageViewer
 | 
			
		||||
import com.github.piasy.biv.loader.ImageLoader
 | 
			
		||||
@ -69,10 +70,7 @@ import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.dialogs.withColoredButton
 | 
			
		||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
 | 
			
		||||
import im.vector.riotx.core.error.ErrorFormatter
 | 
			
		||||
import im.vector.riotx.core.extensions.hideKeyboard
 | 
			
		||||
import im.vector.riotx.core.extensions.observeEvent
 | 
			
		||||
import im.vector.riotx.core.extensions.setTextOrHide
 | 
			
		||||
import im.vector.riotx.core.extensions.showKeyboard
 | 
			
		||||
import im.vector.riotx.core.extensions.*
 | 
			
		||||
import im.vector.riotx.core.files.addEntryToDownloadManager
 | 
			
		||||
import im.vector.riotx.core.glide.GlideApp
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
@ -193,6 +191,8 @@ class RoomDetailFragment @Inject constructor(
 | 
			
		||||
 | 
			
		||||
    private lateinit var sharedActionViewModel: MessageSharedActionViewModel
 | 
			
		||||
    private lateinit var layoutManager: LinearLayoutManager
 | 
			
		||||
    private var modelBuildListener: OnModelBuildFinishedListener? = null
 | 
			
		||||
 | 
			
		||||
    private lateinit var attachmentsHelper: AttachmentsHelper
 | 
			
		||||
    private lateinit var keyboardStateUtils: KeyboardStateUtils
 | 
			
		||||
 | 
			
		||||
@ -286,13 +286,16 @@ class RoomDetailFragment @Inject constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        timelineEventController.callback = null
 | 
			
		||||
        timelineEventController.removeModelBuildListener(modelBuildListener)
 | 
			
		||||
        modelBuildListener = null
 | 
			
		||||
        debouncer.cancelAll()
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
        recyclerView.adapter = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
        roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
 | 
			
		||||
        debouncer.cancelAll()
 | 
			
		||||
        super.onDestroy()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -447,11 +450,7 @@ class RoomDetailFragment @Inject constructor(
 | 
			
		||||
        if (!hasBeenHandled && resultCode == RESULT_OK && data != null) {
 | 
			
		||||
            when (requestCode) {
 | 
			
		||||
                REACTION_SELECT_REQUEST_CODE -> {
 | 
			
		||||
                    val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
 | 
			
		||||
                            ?: return
 | 
			
		||||
                    val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
 | 
			
		||||
                            ?: return
 | 
			
		||||
                    // TODO check if already reacted with that?
 | 
			
		||||
                    val (eventId, reaction) = EmojiReactionPickerActivity.getOutput(data) ?: return
 | 
			
		||||
                    roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -470,13 +469,14 @@ class RoomDetailFragment @Inject constructor(
 | 
			
		||||
        recyclerView.layoutManager = layoutManager
 | 
			
		||||
        recyclerView.itemAnimator = null
 | 
			
		||||
        recyclerView.setHasFixedSize(true)
 | 
			
		||||
        timelineEventController.addModelBuildListener {
 | 
			
		||||
        modelBuildListener = OnModelBuildFinishedListener {
 | 
			
		||||
            it.dispatchTo(stateRestorer)
 | 
			
		||||
            it.dispatchTo(scrollOnNewMessageCallback)
 | 
			
		||||
            it.dispatchTo(scrollOnHighlightedEventCallback)
 | 
			
		||||
            updateJumpToReadMarkerViewVisibility()
 | 
			
		||||
            updateJumpToBottomViewVisibility()
 | 
			
		||||
        }
 | 
			
		||||
        timelineEventController.addModelBuildListener(modelBuildListener)
 | 
			
		||||
        recyclerView.adapter = timelineEventController.adapter
 | 
			
		||||
 | 
			
		||||
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
 | 
			
		||||
@ -521,7 +521,8 @@ class RoomDetailFragment @Inject constructor(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateJumpToReadMarkerViewVisibility() = jumpToReadMarkerView.post {
 | 
			
		||||
    private fun updateJumpToReadMarkerViewVisibility() {
 | 
			
		||||
        jumpToReadMarkerView?.post {
 | 
			
		||||
            withState(roomDetailViewModel) {
 | 
			
		||||
                val showJumpToUnreadBanner = when (it.unreadState) {
 | 
			
		||||
                    UnreadState.Unknown,
 | 
			
		||||
@ -544,6 +545,7 @@ class RoomDetailFragment @Inject constructor(
 | 
			
		||||
                jumpToReadMarkerView.isVisible = showJumpToUnreadBanner
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateJumpToBottomViewVisibility() {
 | 
			
		||||
        debouncer.debounce("jump_to_bottom_visibility", 250, Runnable {
 | 
			
		||||
 | 
			
		||||
@ -863,7 +863,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
 | 
			
		||||
 | 
			
		||||
    override fun onCleared() {
 | 
			
		||||
        timeline.dispose()
 | 
			
		||||
        timeline.removeAllListeners()
 | 
			
		||||
        timeline.removeListener(this)
 | 
			
		||||
        super.onCleared()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,6 @@ import android.os.Parcelable
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import butterknife.BindView
 | 
			
		||||
import butterknife.ButterKnife
 | 
			
		||||
@ -29,6 +28,8 @@ import com.airbnb.mvrx.MvRx
 | 
			
		||||
import com.airbnb.mvrx.args
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ScreenComponent
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
 | 
			
		||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
 | 
			
		||||
import kotlinx.android.parcel.Parcelize
 | 
			
		||||
@ -52,8 +53,8 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
 | 
			
		||||
 | 
			
		||||
    private val displayReadReceiptArgs: DisplayReadReceiptArgs by args()
 | 
			
		||||
 | 
			
		||||
    override fun injectWith(screenComponent: ScreenComponent) {
 | 
			
		||||
        screenComponent.inject(this)
 | 
			
		||||
    override fun injectWith(injector: ScreenComponent) {
 | 
			
		||||
        injector.inject(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
 | 
			
		||||
@ -64,12 +65,16 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
 | 
			
		||||
 | 
			
		||||
    override fun onActivityCreated(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onActivityCreated(savedInstanceState)
 | 
			
		||||
        recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
 | 
			
		||||
        recyclerView.adapter = epoxyController.adapter
 | 
			
		||||
        recyclerView.configureWith(epoxyController, hasFixedSize = false)
 | 
			
		||||
        bottomSheetTitle.text = getString(R.string.seen_by)
 | 
			
		||||
        epoxyController.setData(displayReadReceiptArgs.readReceipts)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // we are not using state for this one as it's static, so no need to override invalidate()
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@ import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import com.airbnb.epoxy.EpoxyController
 | 
			
		||||
import com.airbnb.epoxy.EpoxyModel
 | 
			
		||||
import com.airbnb.epoxy.VisibilityState
 | 
			
		||||
import im.vector.matrix.android.api.session.Session
 | 
			
		||||
import im.vector.matrix.android.api.session.room.model.message.*
 | 
			
		||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
 | 
			
		||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
 | 
			
		||||
@ -44,7 +43,7 @@ import org.threeten.bp.LocalDateTime
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
 | 
			
		||||
                                                  private val session: Session,
 | 
			
		||||
                                                  private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
 | 
			
		||||
                                                  private val timelineItemFactory: TimelineItemFactory,
 | 
			
		||||
                                                  private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
 | 
			
		||||
                                                  private val mergedHeaderItemFactory: MergedHeaderItemFactory,
 | 
			
		||||
@ -209,6 +208,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
 | 
			
		||||
        timelineMediaSizeProvider.recyclerView = recyclerView
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
 | 
			
		||||
        timelineMediaSizeProvider.recyclerView = null
 | 
			
		||||
        contentUploadStateTrackerBinder.clear()
 | 
			
		||||
        timeline?.removeListener(this)
 | 
			
		||||
        super.onDetachedFromRecyclerView(recyclerView)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun buildModels() {
 | 
			
		||||
        val timestamp = System.currentTimeMillis()
 | 
			
		||||
        showingForwardLoader = LoadingItem_()
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.features.home.room.detail.timeline.action
 | 
			
		||||
 | 
			
		||||
import androidx.recyclerview.widget.DefaultItemAnimator
 | 
			
		||||
 | 
			
		||||
private const val ANIM_DURATION_IN_MILLIS = 300L
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * We only want to animate the expand of the "Report content" submenu
 | 
			
		||||
 */
 | 
			
		||||
class MessageActionsAnimator : DefaultItemAnimator() {
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        addDuration = ANIM_DURATION_IN_MILLIS
 | 
			
		||||
        removeDuration = 0
 | 
			
		||||
        moveDuration = 0
 | 
			
		||||
        changeDuration = 0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -19,7 +19,6 @@ import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import butterknife.BindView
 | 
			
		||||
import butterknife.ButterKnife
 | 
			
		||||
@ -27,6 +26,8 @@ import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ScreenComponent
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
 | 
			
		||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
@ -48,8 +49,8 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
 | 
			
		||||
 | 
			
		||||
    private lateinit var sharedActionViewModel: MessageSharedActionViewModel
 | 
			
		||||
 | 
			
		||||
    override fun injectWith(screenComponent: ScreenComponent) {
 | 
			
		||||
        screenComponent.inject(this)
 | 
			
		||||
    override fun injectWith(injector: ScreenComponent) {
 | 
			
		||||
        injector.inject(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
 | 
			
		||||
@ -61,13 +62,17 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
 | 
			
		||||
    override fun onActivityCreated(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onActivityCreated(savedInstanceState)
 | 
			
		||||
        sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
 | 
			
		||||
        recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
 | 
			
		||||
        recyclerView.adapter = messageActionsEpoxyController.adapter
 | 
			
		||||
        recyclerView.configureWith(messageActionsEpoxyController, hasFixedSize = false)
 | 
			
		||||
        // Disable item animation
 | 
			
		||||
        recyclerView.itemAnimator = null
 | 
			
		||||
        messageActionsEpoxyController.listener = this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUrlClicked(url: String): Boolean {
 | 
			
		||||
        sharedActionViewModel.post(EventSharedAction.OnUrlClicked(url))
 | 
			
		||||
        // Always consume
 | 
			
		||||
@ -83,6 +88,10 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
 | 
			
		||||
    override fun didSelectMenuAction(eventAction: EventSharedAction) {
 | 
			
		||||
        if (eventAction is EventSharedAction.ReportContent) {
 | 
			
		||||
            // Toggle report menu
 | 
			
		||||
            // Enable item animation
 | 
			
		||||
            if (recyclerView.itemAnimator == null) {
 | 
			
		||||
                recyclerView.itemAnimator = MessageActionsAnimator()
 | 
			
		||||
            }
 | 
			
		||||
            viewModel.handle(MessageActionsAction.ToggleReportMenu)
 | 
			
		||||
        } else {
 | 
			
		||||
            sharedActionViewModel.post(eventAction)
 | 
			
		||||
 | 
			
		||||
@ -19,9 +19,6 @@ import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.LinearLayout
 | 
			
		||||
import androidx.recyclerview.widget.DividerItemDecoration
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import butterknife.BindView
 | 
			
		||||
import butterknife.ButterKnife
 | 
			
		||||
@ -30,6 +27,8 @@ import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ScreenComponent
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
 | 
			
		||||
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
 | 
			
		||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
 | 
			
		||||
@ -54,8 +53,8 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
 | 
			
		||||
        ViewEditHistoryEpoxyController(requireContext(), viewModel.dateFormatter, eventHtmlRenderer)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun injectWith(screenComponent: ScreenComponent) {
 | 
			
		||||
        screenComponent.inject(this)
 | 
			
		||||
    override fun injectWith(injector: ScreenComponent) {
 | 
			
		||||
        injector.inject(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
 | 
			
		||||
@ -66,13 +65,18 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
 | 
			
		||||
 | 
			
		||||
    override fun onActivityCreated(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onActivityCreated(savedInstanceState)
 | 
			
		||||
        recyclerView.adapter = epoxyController.adapter
 | 
			
		||||
        recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
 | 
			
		||||
        val dividerItemDecoration = DividerItemDecoration(requireContext(), LinearLayout.VERTICAL)
 | 
			
		||||
        recyclerView.addItemDecoration(dividerItemDecoration)
 | 
			
		||||
        recyclerView.configureWith(
 | 
			
		||||
                epoxyController,
 | 
			
		||||
                showDivider = true,
 | 
			
		||||
                hasFixedSize = false)
 | 
			
		||||
        bottomSheetTitle.text = context?.getString(R.string.message_edits)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun invalidate() = withState(viewModel) {
 | 
			
		||||
        epoxyController.setData(it)
 | 
			
		||||
        super.invalidate()
 | 
			
		||||
 | 
			
		||||
@ -28,17 +28,16 @@ import im.vector.matrix.android.api.session.events.model.toModel
 | 
			
		||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
 | 
			
		||||
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.date.VectorDateFormatter
 | 
			
		||||
import im.vector.riotx.core.extensions.localDateTime
 | 
			
		||||
import im.vector.riotx.core.ui.list.genericFooterItem
 | 
			
		||||
import im.vector.riotx.core.ui.list.genericItem
 | 
			
		||||
import im.vector.riotx.core.ui.list.genericItemHeader
 | 
			
		||||
import im.vector.riotx.core.ui.list.genericLoaderItem
 | 
			
		||||
import im.vector.riotx.core.date.VectorDateFormatter
 | 
			
		||||
import im.vector.riotx.features.html.EventHtmlRenderer
 | 
			
		||||
import me.gujun.android.span.span
 | 
			
		||||
import name.fraser.neil.plaintext.diff_match_patch
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import java.util.Calendar
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Epoxy controller for edit history list
 | 
			
		||||
@ -104,9 +103,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
 | 
			
		||||
                            ?: nContent.first
 | 
			
		||||
                    val dmp = diff_match_patch()
 | 
			
		||||
                    val diff = dmp.diff_main(nextBody.toString(), body.toString())
 | 
			
		||||
                    Timber.e("#### Diff: $diff")
 | 
			
		||||
                    dmp.diff_cleanupSemantic(diff)
 | 
			
		||||
                    Timber.e("#### Diff: $diff")
 | 
			
		||||
                    spannedDiff = span {
 | 
			
		||||
                        diff.map {
 | 
			
		||||
                            when (it.operation) {
 | 
			
		||||
 | 
			
		||||
@ -25,12 +25,14 @@ import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
 | 
			
		||||
import im.vector.matrix.android.api.session.room.send.SendState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ActiveSessionHolder
 | 
			
		||||
import im.vector.riotx.core.di.ScreenScope
 | 
			
		||||
import im.vector.riotx.core.error.ErrorFormatter
 | 
			
		||||
import im.vector.riotx.core.resources.ColorProvider
 | 
			
		||||
import im.vector.riotx.core.utils.TextUtils
 | 
			
		||||
import im.vector.riotx.features.ui.getMessageTextColor
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
@ScreenScope
 | 
			
		||||
class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
 | 
			
		||||
                                                          private val colorProvider: ColorProvider,
 | 
			
		||||
                                                          private val errorFormatter: ErrorFormatter) {
 | 
			
		||||
@ -40,7 +42,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
 | 
			
		||||
    fun bind(eventId: String,
 | 
			
		||||
             isLocalFile: Boolean,
 | 
			
		||||
             progressLayout: ViewGroup) {
 | 
			
		||||
        activeSessionHolder.getActiveSession().also { session ->
 | 
			
		||||
        activeSessionHolder.getSafeActiveSession()?.also { session ->
 | 
			
		||||
            val uploadStateTracker = session.contentUploadProgressTracker()
 | 
			
		||||
            val updateListener = ContentMediaProgressUpdater(progressLayout, isLocalFile, colorProvider, errorFormatter)
 | 
			
		||||
            updateListeners[eventId] = updateListener
 | 
			
		||||
@ -49,13 +51,19 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun unbind(eventId: String) {
 | 
			
		||||
        activeSessionHolder.getActiveSession().also { session ->
 | 
			
		||||
        activeSessionHolder.getSafeActiveSession()?.also { session ->
 | 
			
		||||
            val uploadStateTracker = session.contentUploadProgressTracker()
 | 
			
		||||
            updateListeners[eventId]?.also {
 | 
			
		||||
                uploadStateTracker.untrack(eventId, it)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun clear() {
 | 
			
		||||
        activeSessionHolder.getSafeActiveSession()?.also {
 | 
			
		||||
            it.contentUploadProgressTracker().clear()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup,
 | 
			
		||||
 | 
			
		||||
@ -19,11 +19,12 @@ package im.vector.riotx.features.home.room.detail.timeline.helper
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import im.vector.riotx.core.di.ScreenScope
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
import kotlin.math.roundToInt
 | 
			
		||||
 | 
			
		||||
@ScreenScope
 | 
			
		||||
class TimelineMediaSizeProvider @Inject constructor() {
 | 
			
		||||
 | 
			
		||||
    lateinit var recyclerView: RecyclerView
 | 
			
		||||
    var recyclerView: RecyclerView? = null
 | 
			
		||||
    private var cachedSize: Pair<Int, Int>? = null
 | 
			
		||||
 | 
			
		||||
    fun getMaxSize(): Pair<Int, Int> {
 | 
			
		||||
@ -31,17 +32,17 @@ class TimelineMediaSizeProvider @Inject constructor() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun computeMaxSize(): Pair<Int, Int> {
 | 
			
		||||
        val width = recyclerView.width
 | 
			
		||||
        val height = recyclerView.height
 | 
			
		||||
        val width = recyclerView?.width ?: 0
 | 
			
		||||
        val height = recyclerView?.height ?: 0
 | 
			
		||||
        val maxImageWidth: Int
 | 
			
		||||
        val maxImageHeight: Int
 | 
			
		||||
        // landscape / portrait
 | 
			
		||||
        if (width < height) {
 | 
			
		||||
            maxImageWidth = Math.round(width * 0.7f)
 | 
			
		||||
            maxImageHeight = Math.round(height * 0.5f)
 | 
			
		||||
            maxImageWidth = (width * 0.7f).roundToInt()
 | 
			
		||||
            maxImageHeight = (height * 0.5f).roundToInt()
 | 
			
		||||
        } else {
 | 
			
		||||
            maxImageWidth = Math.round(width * 0.5f)
 | 
			
		||||
            maxImageHeight = Math.round(height * 0.7f)
 | 
			
		||||
            maxImageWidth = (width * 0.5f).roundToInt()
 | 
			
		||||
            maxImageHeight = (height * 0.7f).roundToInt()
 | 
			
		||||
        }
 | 
			
		||||
        return Pair(maxImageWidth, maxImageHeight)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,6 @@ import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import butterknife.BindView
 | 
			
		||||
import butterknife.ButterKnife
 | 
			
		||||
@ -29,6 +28,8 @@ import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ScreenComponent
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
 | 
			
		||||
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
 | 
			
		||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
 | 
			
		||||
@ -49,8 +50,8 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
 | 
			
		||||
 | 
			
		||||
    @Inject lateinit var epoxyController: ViewReactionsEpoxyController
 | 
			
		||||
 | 
			
		||||
    override fun injectWith(screenComponent: ScreenComponent) {
 | 
			
		||||
        screenComponent.inject(this)
 | 
			
		||||
    override fun injectWith(injector: ScreenComponent) {
 | 
			
		||||
        injector.inject(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
 | 
			
		||||
@ -61,11 +62,15 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
 | 
			
		||||
 | 
			
		||||
    override fun onActivityCreated(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onActivityCreated(savedInstanceState)
 | 
			
		||||
        recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
 | 
			
		||||
        recyclerView.adapter = epoxyController.adapter
 | 
			
		||||
        recyclerView.configureWith(epoxyController, hasFixedSize = false)
 | 
			
		||||
        bottomSheetTitle.text = context?.getString(R.string.reactions)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun invalidate() = withState(viewModel) {
 | 
			
		||||
        epoxyController.setData(it)
 | 
			
		||||
        super.invalidate()
 | 
			
		||||
 | 
			
		||||
@ -36,9 +36,7 @@ class FilteredRoomsActivity : VectorBaseActivity() {
 | 
			
		||||
            return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomListFragment
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutRes(): Int {
 | 
			
		||||
        return R.layout.activity_filtered_rooms
 | 
			
		||||
    }
 | 
			
		||||
    override fun getLayoutRes() = R.layout.activity_filtered_rooms
 | 
			
		||||
 | 
			
		||||
    override fun injectWith(injector: ScreenComponent) {
 | 
			
		||||
        injector.inject(this)
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ import androidx.core.content.ContextCompat
 | 
			
		||||
import androidx.core.view.isVisible
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
 | 
			
		||||
import com.airbnb.mvrx.*
 | 
			
		||||
import com.google.android.material.snackbar.Snackbar
 | 
			
		||||
import im.vector.matrix.android.api.failure.Failure
 | 
			
		||||
@ -35,13 +36,13 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
 | 
			
		||||
import im.vector.riotx.core.error.ErrorFormatter
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.platform.OnBackPressed
 | 
			
		||||
import im.vector.riotx.core.platform.StateView
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
 | 
			
		||||
import im.vector.riotx.features.home.RoomListDisplayMode
 | 
			
		||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
 | 
			
		||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
 | 
			
		||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
 | 
			
		||||
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
 | 
			
		||||
import im.vector.riotx.features.home.room.list.widget.FabMenuView
 | 
			
		||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
 | 
			
		||||
@ -65,6 +66,7 @@ class RoomListFragment @Inject constructor(
 | 
			
		||||
 | 
			
		||||
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
 | 
			
		||||
 | 
			
		||||
    private var modelBuildListener: OnModelBuildFinishedListener? = null
 | 
			
		||||
    private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
 | 
			
		||||
    private val roomListParams: RoomListParams by args()
 | 
			
		||||
    private val roomListViewModel: RoomListViewModel by fragmentViewModel()
 | 
			
		||||
@ -118,8 +120,12 @@ class RoomListFragment @Inject constructor(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        roomController.removeModelBuildListener(modelBuildListener)
 | 
			
		||||
        modelBuildListener = null
 | 
			
		||||
        roomListView.cleanup()
 | 
			
		||||
        roomController.listener = null
 | 
			
		||||
        createChatFabMenu.listener = null
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
        roomListView.adapter = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun openSelectedRoom(event: RoomListViewEvents.SelectRoom) {
 | 
			
		||||
@ -198,7 +204,8 @@ class RoomListFragment @Inject constructor(
 | 
			
		||||
        roomListView.layoutManager = layoutManager
 | 
			
		||||
        roomListView.itemAnimator = RoomListAnimator()
 | 
			
		||||
        roomController.listener = this
 | 
			
		||||
        roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
 | 
			
		||||
        modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
 | 
			
		||||
        roomController.addModelBuildListener(modelBuildListener)
 | 
			
		||||
        roomListView.adapter = roomController.adapter
 | 
			
		||||
        stateView.contentView = roomListView
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,6 @@ import android.os.Parcelable
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import butterknife.BindView
 | 
			
		||||
import butterknife.ButterKnife
 | 
			
		||||
@ -29,6 +28,8 @@ import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ScreenComponent
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
 | 
			
		||||
import im.vector.riotx.features.navigation.Navigator
 | 
			
		||||
import kotlinx.android.parcel.Parcelize
 | 
			
		||||
@ -56,8 +57,8 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
 | 
			
		||||
 | 
			
		||||
    override val showExpanded = true
 | 
			
		||||
 | 
			
		||||
    override fun injectWith(screenComponent: ScreenComponent) {
 | 
			
		||||
        screenComponent.inject(this)
 | 
			
		||||
    override fun injectWith(injector: ScreenComponent) {
 | 
			
		||||
        injector.inject(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
 | 
			
		||||
@ -69,13 +70,17 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
 | 
			
		||||
    override fun onActivityCreated(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onActivityCreated(savedInstanceState)
 | 
			
		||||
        sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
 | 
			
		||||
        recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
 | 
			
		||||
        recyclerView.adapter = roomListActionsEpoxyController.adapter
 | 
			
		||||
        recyclerView.configureWith(roomListActionsEpoxyController, hasFixedSize = false)
 | 
			
		||||
        // Disable item animation
 | 
			
		||||
        recyclerView.itemAnimator = null
 | 
			
		||||
        roomListActionsEpoxyController.listener = this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun invalidate() = withState(viewModel) {
 | 
			
		||||
        roomListActionsEpoxyController.setData(it)
 | 
			
		||||
        super.invalidate()
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,8 @@ import butterknife.OnClick
 | 
			
		||||
import com.airbnb.mvrx.args
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.error.ErrorFormatter
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.utils.openUrlInExternalBrowser
 | 
			
		||||
import im.vector.riotx.features.login.AbstractLoginFragment
 | 
			
		||||
import im.vector.riotx.features.login.LoginAction
 | 
			
		||||
@ -55,8 +57,7 @@ class LoginTermsFragment @Inject constructor(
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        loginTermsPolicyList.setController(policyController)
 | 
			
		||||
        loginTermsPolicyList.configureWith(policyController)
 | 
			
		||||
        policyController.listener = this
 | 
			
		||||
 | 
			
		||||
        val list = ArrayList<LocalizedFlowDataLoginTermsChecked>()
 | 
			
		||||
@ -69,6 +70,12 @@ class LoginTermsFragment @Inject constructor(
 | 
			
		||||
        loginTermsViewState = LoginTermsViewState(list)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        loginTermsPolicyList.cleanup()
 | 
			
		||||
        policyController.listener = null
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun renderState() {
 | 
			
		||||
        policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,12 +17,18 @@ package im.vector.riotx.features.reactions
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.View
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import androidx.lifecycle.observe
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import kotlinx.android.synthetic.main.emoji_chooser_fragment.*
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class EmojiChooserFragment @Inject constructor() : VectorBaseFragment() {
 | 
			
		||||
class EmojiChooserFragment @Inject constructor(
 | 
			
		||||
        private val emojiRecyclerAdapter: EmojiRecyclerAdapter
 | 
			
		||||
) : VectorBaseFragment(),
 | 
			
		||||
        EmojiRecyclerAdapter.InteractionListener,
 | 
			
		||||
        ReactionClickListener {
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutResId() = R.layout.emoji_chooser_fragment
 | 
			
		||||
 | 
			
		||||
@ -31,10 +37,29 @@ class EmojiChooserFragment @Inject constructor() : VectorBaseFragment() {
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
        viewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
 | 
			
		||||
        viewModel.initWithContext(context!!)
 | 
			
		||||
        (view as? RecyclerView)?.let {
 | 
			
		||||
            it.adapter = viewModel.adapter
 | 
			
		||||
            it.adapter?.notifyDataSetChanged()
 | 
			
		||||
 | 
			
		||||
        emojiRecyclerAdapter.reactionClickListener = this
 | 
			
		||||
        emojiRecyclerAdapter.interactionListener = this
 | 
			
		||||
 | 
			
		||||
        emojiRecyclerView.adapter = emojiRecyclerAdapter
 | 
			
		||||
 | 
			
		||||
        viewModel.moveToSection.observe(viewLifecycleOwner) { section ->
 | 
			
		||||
            emojiRecyclerAdapter.scrollToSection(section)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun firstVisibleSectionChange(section: Int) {
 | 
			
		||||
        viewModel.setCurrentSection(section)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onReactionSelected(reaction: String) {
 | 
			
		||||
        viewModel.onReactionSelected(reaction)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        emojiRecyclerView.cleanup()
 | 
			
		||||
        emojiRecyclerAdapter.reactionClickListener = null
 | 
			
		||||
        emojiRecyclerAdapter.interactionListener = null
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,6 @@
 | 
			
		||||
 */
 | 
			
		||||
package im.vector.riotx.features.reactions
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import im.vector.riotx.core.utils.LiveEvent
 | 
			
		||||
@ -23,36 +22,26 @@ import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class EmojiChooserViewModel @Inject constructor() : ViewModel() {
 | 
			
		||||
 | 
			
		||||
    var adapter: EmojiRecyclerAdapter? = null
 | 
			
		||||
    val emojiSourceLiveData: MutableLiveData<EmojiDataSource> = MutableLiveData()
 | 
			
		||||
 | 
			
		||||
    val navigateEvent: MutableLiveData<LiveEvent<String>> = MutableLiveData()
 | 
			
		||||
    var selectedReaction: String? = null
 | 
			
		||||
    var eventId: String? = null
 | 
			
		||||
 | 
			
		||||
    val currentSection: MutableLiveData<Int> = MutableLiveData()
 | 
			
		||||
    val moveToSection: MutableLiveData<Int> = MutableLiveData()
 | 
			
		||||
 | 
			
		||||
    var reactionClickListener = object : ReactionClickListener {
 | 
			
		||||
        override fun onReactionSelected(reaction: String) {
 | 
			
		||||
    fun onReactionSelected(reaction: String) {
 | 
			
		||||
        selectedReaction = reaction
 | 
			
		||||
        navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun initWithContext(context: Context) {
 | 
			
		||||
        // TODO load async
 | 
			
		||||
        val emojiDataSource = EmojiDataSource(context)
 | 
			
		||||
        emojiSourceLiveData.value = emojiDataSource
 | 
			
		||||
        adapter = EmojiRecyclerAdapter(emojiDataSource, reactionClickListener)
 | 
			
		||||
        adapter?.interactionListener = object : EmojiRecyclerAdapter.InteractionListener {
 | 
			
		||||
            override fun firstVisibleSectionChange(section: Int) {
 | 
			
		||||
    // Called by the Fragment, when the List is scrolled
 | 
			
		||||
    fun setCurrentSection(section: Int) {
 | 
			
		||||
        currentSection.value = section
 | 
			
		||||
    }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun scrollToSection(sectionIndex: Int) {
 | 
			
		||||
        adapter?.scrollToSection(sectionIndex)
 | 
			
		||||
    // Called by the Activity, when a tab item is clicked
 | 
			
		||||
    fun scrollToSection(section: Int) {
 | 
			
		||||
        moveToSection.value = section
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
@ -1,91 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.features.reactions
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
import com.squareup.moshi.Moshi
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
 | 
			
		||||
class EmojiDataSource(val context: Context) {
 | 
			
		||||
 | 
			
		||||
    var rawData: EmojiData? = null
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
 | 
			
		||||
            val moshi = Moshi.Builder().build()
 | 
			
		||||
            val jsonAdapter = moshi.adapter(EmojiData::class.java)
 | 
			
		||||
            val inputAsString = input.bufferedReader().use { it.readText() }
 | 
			
		||||
            this.rawData = jsonAdapter.fromJson(inputAsString)
 | 
			
		||||
           // this.rawData = mb.fr(InputStreamReader(it), EmojiData::class.java)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    @JsonClass(generateAdapter = true)
 | 
			
		||||
    data class EmojiData(val categories: List<EmojiCategory>,
 | 
			
		||||
                         val emojis: Map<String, EmojiItem>,
 | 
			
		||||
                         val aliases: Map<String, String>)
 | 
			
		||||
 | 
			
		||||
    @JsonClass(generateAdapter = true)
 | 
			
		||||
    data class EmojiCategory(val id: String, val name: String, val emojis: List<String>)
 | 
			
		||||
 | 
			
		||||
    @JsonClass(generateAdapter = true)
 | 
			
		||||
    data class EmojiItem(
 | 
			
		||||
            @Json(name = "a") val name: String,
 | 
			
		||||
            @Json(name = "b") val unicode: String,
 | 
			
		||||
            @Json(name = "j") val keywords: List<String>?,
 | 
			
		||||
            val k: List<String>?) {
 | 
			
		||||
 | 
			
		||||
        var _emojiText: String? = null
 | 
			
		||||
 | 
			
		||||
        fun emojiString() : String {
 | 
			
		||||
            if (_emojiText == null) {
 | 
			
		||||
                val utf8Text = unicode.split("-").joinToString("") { "\\u$it" } // "\u0048\u0065\u006C\u006C\u006F World"
 | 
			
		||||
               _emojiText = fromUnicode(utf8Text)
 | 
			
		||||
            }
 | 
			
		||||
            return _emojiText!!
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun fromUnicode(unicode: String): String {
 | 
			
		||||
            val str = unicode.replace("\\", "")
 | 
			
		||||
            val arr = str.split("u".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
 | 
			
		||||
            val text = StringBuffer()
 | 
			
		||||
            for (i in 1 until arr.size) {
 | 
			
		||||
                val hexVal = Integer.parseInt(arr[i], 16)
 | 
			
		||||
                text.append(Character.toChars(hexVal))
 | 
			
		||||
            }
 | 
			
		||||
            return text.toString()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
//    name: 'a',
 | 
			
		||||
//    unified: 'b',
 | 
			
		||||
//    non_qualified: 'c',
 | 
			
		||||
//    has_img_apple: 'd',
 | 
			
		||||
//    has_img_google: 'e',
 | 
			
		||||
//    has_img_twitter: 'f',
 | 
			
		||||
//    has_img_emojione: 'g',
 | 
			
		||||
//    has_img_facebook: 'h',
 | 
			
		||||
//    has_img_messenger: 'i',
 | 
			
		||||
//    keywords: 'j',
 | 
			
		||||
//    sheet: 'k',
 | 
			
		||||
//    emoticons: 'l',
 | 
			
		||||
//    text: 'm',
 | 
			
		||||
//    short_names: 'n',
 | 
			
		||||
//    added_in: 'o',
 | 
			
		||||
}
 | 
			
		||||
@ -35,6 +35,7 @@ import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ScreenComponent
 | 
			
		||||
import im.vector.riotx.core.extensions.observeEvent
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseActivity
 | 
			
		||||
import im.vector.riotx.features.reactions.data.EmojiDataSource
 | 
			
		||||
import io.reactivex.android.schedulers.AndroidSchedulers
 | 
			
		||||
import kotlinx.android.synthetic.main.activity_emoji_reaction_picker.*
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
@ -44,7 +45,6 @@ import javax.inject.Inject
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * TODO: Loading indicator while getting emoji data source?
 | 
			
		||||
 * TODO: migrate to MvRx
 | 
			
		||||
 * TODO: Finish Refactor to vector base activity
 | 
			
		||||
 */
 | 
			
		||||
class EmojiReactionPickerActivity : VectorBaseActivity(),
 | 
			
		||||
@ -54,13 +54,15 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
 | 
			
		||||
 | 
			
		||||
    lateinit var viewModel: EmojiChooserViewModel
 | 
			
		||||
 | 
			
		||||
    override fun getMenuRes(): Int = R.menu.menu_emoji_reaction_picker
 | 
			
		||||
    override fun getMenuRes() = R.menu.menu_emoji_reaction_picker
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutRes(): Int = R.layout.activity_emoji_reaction_picker
 | 
			
		||||
    override fun getLayoutRes() = R.layout.activity_emoji_reaction_picker
 | 
			
		||||
 | 
			
		||||
    override fun getTitleRes(): Int = R.string.title_activity_emoji_reaction_picker
 | 
			
		||||
    override fun getTitleRes() = R.string.title_activity_emoji_reaction_picker
 | 
			
		||||
 | 
			
		||||
    @Inject lateinit var emojiSearchResultViewModelFactory: EmojiSearchResultViewModel.Factory
 | 
			
		||||
    @Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
 | 
			
		||||
    @Inject lateinit var emojiDataSource: EmojiDataSource
 | 
			
		||||
 | 
			
		||||
    private val searchResultViewModel: EmojiSearchResultViewModel by viewModel()
 | 
			
		||||
 | 
			
		||||
@ -93,13 +95,11 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
 | 
			
		||||
 | 
			
		||||
        viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID)
 | 
			
		||||
 | 
			
		||||
        viewModel.emojiSourceLiveData.observe(this, Observer {
 | 
			
		||||
            it.rawData?.categories?.let { categories ->
 | 
			
		||||
                for (category in categories) {
 | 
			
		||||
        emojiDataSource.rawData.categories.forEach { category ->
 | 
			
		||||
            val s = category.emojis[0]
 | 
			
		||||
            tabLayout.newTab()
 | 
			
		||||
                    .also { tab ->
 | 
			
		||||
                                tab.text = it.rawData!!.emojis[s]!!.emojiString()
 | 
			
		||||
                        tab.text = emojiDataSource.rawData.emojis[s]!!.emoji
 | 
			
		||||
                        tab.contentDescription = category.name
 | 
			
		||||
                    }
 | 
			
		||||
                    .also { tab ->
 | 
			
		||||
@ -107,8 +107,6 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
 | 
			
		||||
                    }
 | 
			
		||||
        }
 | 
			
		||||
        tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        viewModel.currentSection.observe(this, Observer { section ->
 | 
			
		||||
            section?.let {
 | 
			
		||||
@ -136,7 +134,6 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
 | 
			
		||||
 | 
			
		||||
    override fun compatibilityFontUpdate(typeface: Typeface?) {
 | 
			
		||||
        EmojiDrawView.configureTextPaint(this, typeface)
 | 
			
		||||
        searchResultViewModel.dataSource
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
@ -206,13 +203,19 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        const val EXTRA_EVENT_ID = "EXTRA_EVENT_ID"
 | 
			
		||||
        const val EXTRA_REACTION_RESULT = "EXTRA_REACTION_RESULT"
 | 
			
		||||
        private const val EXTRA_EVENT_ID = "EXTRA_EVENT_ID"
 | 
			
		||||
        private const val EXTRA_REACTION_RESULT = "EXTRA_REACTION_RESULT"
 | 
			
		||||
 | 
			
		||||
        fun intent(context: Context, eventId: String): Intent {
 | 
			
		||||
            val intent = Intent(context, EmojiReactionPickerActivity::class.java)
 | 
			
		||||
            intent.putExtra(EXTRA_EVENT_ID, eventId)
 | 
			
		||||
            return intent
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun getOutput(data: Intent): Pair<String, String>? {
 | 
			
		||||
            val eventId = data.getStringExtra(EXTRA_EVENT_ID) ?: return null
 | 
			
		||||
            val reaction = data.getStringExtra(EXTRA_REACTION_RESULT) ?: return null
 | 
			
		||||
            return eventId to reaction
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -30,22 +30,25 @@ import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import androidx.transition.AutoTransition
 | 
			
		||||
import androidx.transition.TransitionManager
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.features.reactions.data.EmojiDataSource
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.GlobalScope
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
import kotlin.math.abs
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * TODO: Configure Span using available width and emoji size
 | 
			
		||||
 * TODO: Search
 | 
			
		||||
 * TODO: Performances
 | 
			
		||||
 * TODO: Scroll to section - Find a way to snap section to the top
 | 
			
		||||
 */
 | 
			
		||||
class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
 | 
			
		||||
                           private var reactionClickListener: ReactionClickListener?) :
 | 
			
		||||
class EmojiRecyclerAdapter @Inject constructor(
 | 
			
		||||
        private val dataSource: EmojiDataSource
 | 
			
		||||
) :
 | 
			
		||||
        RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
 | 
			
		||||
 | 
			
		||||
    var reactionClickListener: ReactionClickListener? = null
 | 
			
		||||
    var interactionListener: InteractionListener? = null
 | 
			
		||||
    private var mRecyclerView: RecyclerView? = null
 | 
			
		||||
 | 
			
		||||
@ -66,13 +69,12 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
 | 
			
		||||
    private val itemClickListener = View.OnClickListener { view ->
 | 
			
		||||
        mRecyclerView?.getChildLayoutPosition(view)?.let { itemPosition ->
 | 
			
		||||
            if (itemPosition != RecyclerView.NO_POSITION) {
 | 
			
		||||
                val categories = dataSource?.rawData?.categories ?: return@OnClickListener
 | 
			
		||||
                val sectionNumber = getSectionForAbsoluteIndex(itemPosition)
 | 
			
		||||
                if (!isSection(itemPosition)) {
 | 
			
		||||
                    val sectionMojis = categories[sectionNumber].emojis
 | 
			
		||||
                    val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
 | 
			
		||||
                    val sectionOffset = getSectionOffset(sectionNumber)
 | 
			
		||||
                    val emoji = sectionMojis[itemPosition - sectionOffset]
 | 
			
		||||
                    val item = dataSource.rawData!!.emojis.getValue(emoji).emojiString()
 | 
			
		||||
                    val item = dataSource.rawData.emojis.getValue(emoji).emoji
 | 
			
		||||
                    reactionClickListener?.onReactionSelected(item)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -113,7 +115,7 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun scrollToSection(section: Int) {
 | 
			
		||||
        if (section < 0 || section >= dataSource?.rawData?.categories?.size ?: 0) {
 | 
			
		||||
        if (section < 0 || section >= dataSource.rawData.categories.size) {
 | 
			
		||||
            // ignore
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
@ -145,15 +147,13 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun isSection(position: Int): Boolean {
 | 
			
		||||
        dataSource?.rawData?.categories?.let { categories ->
 | 
			
		||||
        var sectionOffset = 1
 | 
			
		||||
        var lastItemInSection: Int
 | 
			
		||||
            for (category in categories) {
 | 
			
		||||
        dataSource.rawData.categories.forEach { category ->
 | 
			
		||||
            lastItemInSection = sectionOffset + category.emojis.size - 1
 | 
			
		||||
            if (position == sectionOffset - 1) return true
 | 
			
		||||
            sectionOffset = lastItemInSection + 2
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -161,14 +161,12 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
 | 
			
		||||
        var sectionOffset = 1
 | 
			
		||||
        var lastItemInSection: Int
 | 
			
		||||
        var index = 0
 | 
			
		||||
        dataSource?.rawData?.categories?.let {
 | 
			
		||||
            for (category in it) {
 | 
			
		||||
        dataSource.rawData.categories.forEach { category ->
 | 
			
		||||
            lastItemInSection = sectionOffset + category.emojis.size - 1
 | 
			
		||||
            if (position <= lastItemInSection) return index
 | 
			
		||||
            sectionOffset = lastItemInSection + 2
 | 
			
		||||
            index++
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
        return index
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -176,27 +174,24 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
 | 
			
		||||
        // Todo cache this for fast access
 | 
			
		||||
        var sectionOffset = 1
 | 
			
		||||
        var lastItemInSection: Int
 | 
			
		||||
        dataSource?.rawData?.categories?.let {
 | 
			
		||||
            for ((index, category) in it.withIndex()) {
 | 
			
		||||
        dataSource.rawData.categories.forEachIndexed { index, category ->
 | 
			
		||||
            lastItemInSection = sectionOffset + category.emojis.size - 1
 | 
			
		||||
            if (section == index) return sectionOffset
 | 
			
		||||
            sectionOffset = lastItemInSection + 2
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
        return sectionOffset
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 | 
			
		||||
        beginTraceSession("MyAdapter.onBindViewHolder")
 | 
			
		||||
        dataSource?.rawData?.categories?.let { categories ->
 | 
			
		||||
        val sectionNumber = getSectionForAbsoluteIndex(position)
 | 
			
		||||
        if (isSection(position)) {
 | 
			
		||||
                holder.bind(categories[sectionNumber].name)
 | 
			
		||||
            holder.bind(dataSource.rawData.categories[sectionNumber].name)
 | 
			
		||||
        } else {
 | 
			
		||||
                val sectionMojis = categories[sectionNumber].emojis
 | 
			
		||||
            val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
 | 
			
		||||
            val sectionOffset = getSectionOffset(sectionNumber)
 | 
			
		||||
            val emoji = sectionMojis[position - sectionOffset]
 | 
			
		||||
                val item = dataSource.rawData!!.emojis[emoji]!!.emojiString()
 | 
			
		||||
            val item = dataSource.rawData.emojis[emoji]!!.emoji
 | 
			
		||||
            (holder as EmojiViewHolder).data = item
 | 
			
		||||
            if (scrollState != ScrollState.SETTLING || !isFastScroll) {
 | 
			
		||||
//                    Log.i("PERF","Bind with draw at position:$position")
 | 
			
		||||
@ -207,7 +202,6 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
 | 
			
		||||
                holder.bind(null)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
        endTraceSession()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -226,15 +220,8 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
 | 
			
		||||
        super.onViewRecycled(holder)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getItemCount(): Int {
 | 
			
		||||
        return dataSource?.rawData?.categories?.let {
 | 
			
		||||
            var count = /*number of sections*/ it.size
 | 
			
		||||
            for (ad in it) {
 | 
			
		||||
                count += ad.emojis.size
 | 
			
		||||
            }
 | 
			
		||||
            count
 | 
			
		||||
        } ?: 0
 | 
			
		||||
    }
 | 
			
		||||
    override fun getItemCount() = dataSource.rawData.categories
 | 
			
		||||
            .sumBy { emojiCategory -> 1 /* Section */ + emojiCategory.emojis.size }
 | 
			
		||||
 | 
			
		||||
    abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
 | 
			
		||||
        abstract fun bind(s: String?)
 | 
			
		||||
 | 
			
		||||
@ -24,9 +24,10 @@ import im.vector.riotx.core.resources.StringProvider
 | 
			
		||||
import im.vector.riotx.core.ui.list.genericFooterItem
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class EmojiSearchResultController @Inject constructor(val stringProvider: StringProvider,
 | 
			
		||||
                                                      private val fontProvider: EmojiCompatFontProvider)
 | 
			
		||||
    : TypedEpoxyController<EmojiSearchResultViewState>() {
 | 
			
		||||
class EmojiSearchResultController @Inject constructor(
 | 
			
		||||
        private val stringProvider: StringProvider,
 | 
			
		||||
        private val fontProvider: EmojiCompatFontProvider
 | 
			
		||||
) : TypedEpoxyController<EmojiSearchResultViewState>() {
 | 
			
		||||
 | 
			
		||||
    var emojiTypeface: Typeface? = fontProvider.typeface
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,43 +17,42 @@ package im.vector.riotx.features.reactions
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.View
 | 
			
		||||
import androidx.recyclerview.widget.DividerItemDecoration
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import com.airbnb.mvrx.activityViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.core.utils.LiveEvent
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.*
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class EmojiSearchResultFragment @Inject constructor(
 | 
			
		||||
        private val epoxyController: EmojiSearchResultController
 | 
			
		||||
) : VectorBaseFragment() {
 | 
			
		||||
) : VectorBaseFragment(), ReactionClickListener {
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy
 | 
			
		||||
    override fun getLayoutResId() = R.layout.fragment_generic_recycler
 | 
			
		||||
 | 
			
		||||
    val viewModel: EmojiSearchResultViewModel by activityViewModel()
 | 
			
		||||
    private val viewModel: EmojiSearchResultViewModel by activityViewModel()
 | 
			
		||||
 | 
			
		||||
    var sharedViewModel: EmojiChooserViewModel? = null
 | 
			
		||||
    private lateinit var sharedViewModel: EmojiChooserViewModel
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
        sharedViewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java)
 | 
			
		||||
        epoxyController.listener = this
 | 
			
		||||
        recyclerView.configureWith(epoxyController, showDivider = true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        epoxyController.listener = null
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        epoxyController.listener = object : ReactionClickListener {
 | 
			
		||||
    override fun onReactionSelected(reaction: String) {
 | 
			
		||||
                sharedViewModel?.selectedReaction = reaction
 | 
			
		||||
                sharedViewModel?.navigateEvent?.value = LiveEvent(EmojiChooserViewModel.NAVIGATE_FINISH)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
 | 
			
		||||
        epoxyRecyclerView.layoutManager = lmgr
 | 
			
		||||
        val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, lmgr.orientation)
 | 
			
		||||
        epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
 | 
			
		||||
        epoxyRecyclerView.setController(epoxyController)
 | 
			
		||||
        sharedViewModel.selectedReaction = reaction
 | 
			
		||||
        sharedViewModel.navigateEvent.value = LiveEvent(EmojiChooserViewModel.NAVIGATE_FINISH)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun invalidate() = withState(viewModel) { state ->
 | 
			
		||||
 | 
			
		||||
@ -22,12 +22,14 @@ import com.airbnb.epoxy.EpoxyModelClass
 | 
			
		||||
import com.airbnb.epoxy.EpoxyModelWithHolder
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
 | 
			
		||||
import im.vector.riotx.core.extensions.setTextOrHide
 | 
			
		||||
import im.vector.riotx.features.reactions.data.EmojiItem
 | 
			
		||||
 | 
			
		||||
@EpoxyModelClass(layout = R.layout.item_emoji_result)
 | 
			
		||||
abstract class EmojiSearchResultItem : EpoxyModelWithHolder<EmojiSearchResultItem.Holder>() {
 | 
			
		||||
 | 
			
		||||
    @EpoxyAttribute
 | 
			
		||||
    lateinit var emojiItem: EmojiDataSource.EmojiItem
 | 
			
		||||
    lateinit var emojiItem: EmojiItem
 | 
			
		||||
 | 
			
		||||
    @EpoxyAttribute
 | 
			
		||||
    var currentQuery: String? = null
 | 
			
		||||
@ -41,12 +43,12 @@ abstract class EmojiSearchResultItem : EpoxyModelWithHolder<EmojiSearchResultIte
 | 
			
		||||
    override fun bind(holder: Holder) {
 | 
			
		||||
        super.bind(holder)
 | 
			
		||||
        // TODO use query string to highlight the matched query in name and keywords?
 | 
			
		||||
        holder.emojiText.text = emojiItem.emojiString()
 | 
			
		||||
        holder.emojiText.text = emojiItem.emoji
 | 
			
		||||
        holder.emojiText.typeface = emojiTypeFace ?: Typeface.DEFAULT
 | 
			
		||||
        holder.emojiNameText.text = emojiItem.name
 | 
			
		||||
        holder.emojiKeywordText.text = emojiItem.keywords?.joinToString(", ")
 | 
			
		||||
        holder.emojiKeywordText.setTextOrHide(emojiItem.keywords.joinToString())
 | 
			
		||||
        holder.view.setOnClickListener {
 | 
			
		||||
            onClickListener?.onReactionSelected(emojiItem.emojiString())
 | 
			
		||||
            onClickListener?.onReactionSelected(emojiItem.emoji)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,19 +15,39 @@
 | 
			
		||||
 */
 | 
			
		||||
package im.vector.riotx.features.reactions
 | 
			
		||||
 | 
			
		||||
import com.airbnb.mvrx.ActivityViewModelContext
 | 
			
		||||
import com.airbnb.mvrx.MvRxState
 | 
			
		||||
import com.airbnb.mvrx.MvRxViewModelFactory
 | 
			
		||||
import com.airbnb.mvrx.ViewModelContext
 | 
			
		||||
import com.squareup.inject.assisted.Assisted
 | 
			
		||||
import com.squareup.inject.assisted.AssistedInject
 | 
			
		||||
import im.vector.riotx.core.platform.VectorViewModel
 | 
			
		||||
import im.vector.riotx.features.reactions.data.EmojiDataSource
 | 
			
		||||
import im.vector.riotx.features.reactions.data.EmojiItem
 | 
			
		||||
 | 
			
		||||
data class EmojiSearchResultViewState(
 | 
			
		||||
        val query: String = "",
 | 
			
		||||
        val results: List<EmojiDataSource.EmojiItem> = emptyList()
 | 
			
		||||
        val results: List<EmojiItem> = emptyList()
 | 
			
		||||
) : MvRxState
 | 
			
		||||
 | 
			
		||||
class EmojiSearchResultViewModel(val dataSource: EmojiDataSource, initialState: EmojiSearchResultViewState)
 | 
			
		||||
class EmojiSearchResultViewModel @AssistedInject constructor(
 | 
			
		||||
        @Assisted initialState: EmojiSearchResultViewState,
 | 
			
		||||
        private val dataSource: EmojiDataSource)
 | 
			
		||||
    : VectorViewModel<EmojiSearchResultViewState, EmojiSearchAction>(initialState) {
 | 
			
		||||
 | 
			
		||||
    @AssistedInject.Factory
 | 
			
		||||
    interface Factory {
 | 
			
		||||
        fun create(initialState: EmojiSearchResultViewState): EmojiSearchResultViewModel
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object : MvRxViewModelFactory<EmojiSearchResultViewModel, EmojiSearchResultViewState> {
 | 
			
		||||
 | 
			
		||||
        override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? {
 | 
			
		||||
            val activity: EmojiReactionPickerActivity = (viewModelContext as ActivityViewModelContext).activity()
 | 
			
		||||
            return activity.emojiSearchResultViewModelFactory.create(state)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun handle(action: EmojiSearchAction) {
 | 
			
		||||
        when (action) {
 | 
			
		||||
            is EmojiSearchAction.UpdateQuery -> updateQuery(action)
 | 
			
		||||
@ -35,26 +55,27 @@ class EmojiSearchResultViewModel(val dataSource: EmojiDataSource, initialState:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateQuery(action: EmojiSearchAction.UpdateQuery) {
 | 
			
		||||
        val words = action.queryString.split("\\s".toRegex())
 | 
			
		||||
        setState {
 | 
			
		||||
            copy(
 | 
			
		||||
                    query = action.queryString,
 | 
			
		||||
                    results = dataSource.rawData?.emojis?.toList()
 | 
			
		||||
                            ?.map { it.second }
 | 
			
		||||
                            ?.filter {
 | 
			
		||||
                                it.name.contains(action.queryString, true)
 | 
			
		||||
                                        || action.queryString.split("\\s".toRegex()).fold(true, { prev, q ->
 | 
			
		||||
                                    prev && (it.keywords?.any { it.contains(q, true) } ?: false)
 | 
			
		||||
                    // First add emojis with name matching query, sorted by name
 | 
			
		||||
                    // Then emojis with keyword matching any of the word in the query, sorted by name
 | 
			
		||||
                    results = dataSource.rawData.emojis
 | 
			
		||||
                            .values
 | 
			
		||||
                            .filter { emojiItem ->
 | 
			
		||||
                                emojiItem.name.contains(action.queryString, true)
 | 
			
		||||
                            }
 | 
			
		||||
                            .sortedBy { it.name }
 | 
			
		||||
                            + dataSource.rawData.emojis
 | 
			
		||||
                            .values
 | 
			
		||||
                            .filter { emojiItem ->
 | 
			
		||||
                                words.fold(true, { prev, word ->
 | 
			
		||||
                                    prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) }
 | 
			
		||||
                                })
 | 
			
		||||
                            } ?: emptyList()
 | 
			
		||||
                            }
 | 
			
		||||
                            .sortedBy { it.name }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object : MvRxViewModelFactory<EmojiSearchResultViewModel, EmojiSearchResultViewState> {
 | 
			
		||||
 | 
			
		||||
        override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? {
 | 
			
		||||
            // TODO get the data source from activity? share it with other fragment
 | 
			
		||||
            return EmojiSearchResultViewModel(EmojiDataSource(viewModelContext.activity), state)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.features.reactions.data
 | 
			
		||||
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
 | 
			
		||||
@JsonClass(generateAdapter = true)
 | 
			
		||||
data class EmojiCategory(
 | 
			
		||||
        @Json(name = "id") val id: String,
 | 
			
		||||
        @Json(name = "name") val name: String,
 | 
			
		||||
        @Json(name = "emojis") val emojis: List<String>
 | 
			
		||||
)
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.features.reactions.data
 | 
			
		||||
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
 | 
			
		||||
@JsonClass(generateAdapter = true)
 | 
			
		||||
data class EmojiData(
 | 
			
		||||
        @Json(name = "categories") val categories: List<EmojiCategory>,
 | 
			
		||||
        @Json(name = "emojis") val emojis: Map<String, EmojiItem>,
 | 
			
		||||
        @Json(name = "aliases") val aliases: Map<String, String>
 | 
			
		||||
)
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.features.reactions.data
 | 
			
		||||
 | 
			
		||||
import android.content.res.Resources
 | 
			
		||||
import com.squareup.moshi.Moshi
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.di.ScreenScope
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
@ScreenScope
 | 
			
		||||
class EmojiDataSource @Inject constructor(
 | 
			
		||||
        resources: Resources
 | 
			
		||||
) {
 | 
			
		||||
    val rawData = resources.openRawResource(R.raw.emoji_picker_datasource)
 | 
			
		||||
            .use { input ->
 | 
			
		||||
                Moshi.Builder()
 | 
			
		||||
                        .build()
 | 
			
		||||
                        .adapter(EmojiData::class.java)
 | 
			
		||||
                        .fromJson(input.bufferedReader().use { it.readText() })
 | 
			
		||||
            }
 | 
			
		||||
            ?: EmojiData(emptyList(), emptyMap(), emptyMap())
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,74 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.features.reactions.data
 | 
			
		||||
 | 
			
		||||
import com.squareup.moshi.Json
 | 
			
		||||
import com.squareup.moshi.JsonClass
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *  name: 'a',
 | 
			
		||||
 *  unified: 'b',
 | 
			
		||||
 *  non_qualified: 'c',
 | 
			
		||||
 *  has_img_apple: 'd',
 | 
			
		||||
 *  has_img_google: 'e',
 | 
			
		||||
 *  has_img_twitter: 'f',
 | 
			
		||||
 *  has_img_emojione: 'g',
 | 
			
		||||
 *  has_img_facebook: 'h',
 | 
			
		||||
 *  has_img_messenger: 'i',
 | 
			
		||||
 *  keywords: 'j',
 | 
			
		||||
 *  sheet: 'k',
 | 
			
		||||
 *  emoticons: 'l',
 | 
			
		||||
 *  text: 'm',
 | 
			
		||||
 *  short_names: 'n',
 | 
			
		||||
 *  added_in: 'o'
 | 
			
		||||
 */
 | 
			
		||||
@JsonClass(generateAdapter = true)
 | 
			
		||||
data class EmojiItem(
 | 
			
		||||
        @Json(name = "a") val name: String,
 | 
			
		||||
        @Json(name = "b") val unicode: String,
 | 
			
		||||
        @Json(name = "j") val keywords: List<String> = emptyList()
 | 
			
		||||
) {
 | 
			
		||||
    // Cannot be private...
 | 
			
		||||
    var cache: String? = null
 | 
			
		||||
 | 
			
		||||
    val emoji: String
 | 
			
		||||
        get() {
 | 
			
		||||
            cache?.let { return it }
 | 
			
		||||
 | 
			
		||||
            // "\u0048\u0065\u006C\u006C\u006F World"
 | 
			
		||||
            val utf8Text = unicode
 | 
			
		||||
                    .split("-")
 | 
			
		||||
                    .joinToString("") { "\\u$it" }
 | 
			
		||||
            return fromUnicode(utf8Text)
 | 
			
		||||
                    .also { cache = it }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private fun fromUnicode(unicode: String): String {
 | 
			
		||||
            val arr = unicode
 | 
			
		||||
                    .replace("\\", "")
 | 
			
		||||
                    .split("u".toRegex())
 | 
			
		||||
                    .dropLastWhile { it.isEmpty() }
 | 
			
		||||
            return buildString {
 | 
			
		||||
                for (i in 1 until arr.size) {
 | 
			
		||||
                    val hexVal = Integer.parseInt(arr[i], 16)
 | 
			
		||||
                    append(Character.toChars(hexVal))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -19,7 +19,6 @@ package im.vector.riotx.features.roomdirectory
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import android.view.View
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import com.airbnb.epoxy.EpoxyVisibilityTracker
 | 
			
		||||
import com.airbnb.mvrx.activityViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
@ -28,6 +27,8 @@ import com.jakewharton.rxbinding3.appcompat.queryTextChanges
 | 
			
		||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.error.ErrorFormatter
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.extensions.observeEvent
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import io.reactivex.rxkotlin.subscribeBy
 | 
			
		||||
@ -62,6 +63,9 @@ class PublicRoomsFragment @Inject constructor(
 | 
			
		||||
            it.setDisplayHomeAsUpEnabled(true)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
 | 
			
		||||
        setupRecyclerView()
 | 
			
		||||
 | 
			
		||||
        publicRoomsFilter.queryTextChanges()
 | 
			
		||||
                .debounce(500, TimeUnit.MILLISECONDS)
 | 
			
		||||
                .subscribeBy {
 | 
			
		||||
@ -79,6 +83,12 @@ class PublicRoomsFragment @Inject constructor(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        publicRoomsController.callback = null
 | 
			
		||||
        publicRoomsList.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
			
		||||
        return when (item.itemId) {
 | 
			
		||||
            R.id.menu_room_directory_change_protocol -> {
 | 
			
		||||
@ -90,22 +100,11 @@ class PublicRoomsFragment @Inject constructor(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityCreated(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onActivityCreated(savedInstanceState)
 | 
			
		||||
        sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
 | 
			
		||||
        setupRecyclerView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupRecyclerView() {
 | 
			
		||||
        val epoxyVisibilityTracker = EpoxyVisibilityTracker()
 | 
			
		||||
        epoxyVisibilityTracker.attach(publicRoomsList)
 | 
			
		||||
 | 
			
		||||
        val layoutManager = LinearLayoutManager(context)
 | 
			
		||||
 | 
			
		||||
        publicRoomsList.layoutManager = layoutManager
 | 
			
		||||
        publicRoomsList.configureWith(publicRoomsController)
 | 
			
		||||
        publicRoomsController.callback = this
 | 
			
		||||
 | 
			
		||||
        publicRoomsList.setController(publicRoomsController)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) {
 | 
			
		||||
 | 
			
		||||
@ -19,11 +19,12 @@ package im.vector.riotx.features.roomdirectory.createroom
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import android.view.View
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import com.airbnb.mvrx.Success
 | 
			
		||||
import com.airbnb.mvrx.activityViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedAction
 | 
			
		||||
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel
 | 
			
		||||
@ -50,6 +51,12 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        createRoomForm.cleanup()
 | 
			
		||||
        createRoomController.listener = null
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
			
		||||
        return when (item.itemId) {
 | 
			
		||||
            R.id.action_create_room -> {
 | 
			
		||||
@ -62,12 +69,8 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupRecyclerView() {
 | 
			
		||||
        val layoutManager = LinearLayoutManager(context)
 | 
			
		||||
 | 
			
		||||
        createRoomForm.layoutManager = layoutManager
 | 
			
		||||
        createRoomForm.configureWith(createRoomController)
 | 
			
		||||
        createRoomController.listener = this
 | 
			
		||||
 | 
			
		||||
        createRoomForm.setController(createRoomController)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onNameChange(newName: String) {
 | 
			
		||||
 | 
			
		||||
@ -19,12 +19,13 @@ package im.vector.riotx.features.roomdirectory.picker
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
import android.view.View
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import com.airbnb.mvrx.activityViewModel
 | 
			
		||||
import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryAction
 | 
			
		||||
import im.vector.riotx.features.roomdirectory.RoomDirectorySharedAction
 | 
			
		||||
@ -60,6 +61,12 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
 | 
			
		||||
        setupRecyclerView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        roomDirectoryPickerList.cleanup()
 | 
			
		||||
        roomDirectoryPickerController.callback = null
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getMenuRes() = R.menu.menu_directory_server_picker
 | 
			
		||||
 | 
			
		||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
			
		||||
@ -73,12 +80,8 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupRecyclerView() {
 | 
			
		||||
        val layoutManager = LinearLayoutManager(context)
 | 
			
		||||
 | 
			
		||||
        roomDirectoryPickerList.layoutManager = layoutManager
 | 
			
		||||
        roomDirectoryPickerList.configureWith(roomDirectoryPickerController)
 | 
			
		||||
        roomDirectoryPickerController.callback = this
 | 
			
		||||
 | 
			
		||||
        roomDirectoryPickerList.setController(roomDirectoryPickerController)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData) {
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import androidx.transition.TransitionManager
 | 
			
		||||
import butterknife.BindView
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseActivity
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.features.rageshake.BugReporter
 | 
			
		||||
@ -136,6 +137,11 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
 | 
			
		||||
        testManager?.runDiagnostic()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        mRecyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
 | 
			
		||||
        if (resultCode == Activity.RESULT_OK && requestCode == NotificationTroubleshootTestManager.REQ_CODE_FIX) {
 | 
			
		||||
            testManager?.retry()
 | 
			
		||||
 | 
			
		||||
@ -26,10 +26,12 @@ import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.error.ErrorFormatter
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.extensions.observeEvent
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseActivity
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.*
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
 | 
			
		||||
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
@ -39,7 +41,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor(
 | 
			
		||||
        private val errorFormatter: ErrorFormatter
 | 
			
		||||
) : VectorBaseFragment(), IgnoredUsersController.Callback {
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutResId() = R.layout.fragment_generic_recycler_epoxy
 | 
			
		||||
    override fun getLayoutResId() = R.layout.fragment_generic_recycler
 | 
			
		||||
 | 
			
		||||
    private val ignoredUsersViewModel: IgnoredUsersViewModel by fragmentViewModel()
 | 
			
		||||
 | 
			
		||||
@ -49,12 +51,18 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor(
 | 
			
		||||
        waiting_view_status_text.setText(R.string.please_wait)
 | 
			
		||||
        waiting_view_status_text.isVisible = true
 | 
			
		||||
        ignoredUsersController.callback = this
 | 
			
		||||
        epoxyRecyclerView.setController(ignoredUsersController)
 | 
			
		||||
        recyclerView.configureWith(ignoredUsersController)
 | 
			
		||||
        ignoredUsersViewModel.requestErrorLiveData.observeEvent(this) {
 | 
			
		||||
            displayErrorDialog(it)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        ignoredUsersController.callback = null
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onResume() {
 | 
			
		||||
        super.onResume()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2019 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.riotx.features.settings.push
 | 
			
		||||
 | 
			
		||||
import com.airbnb.epoxy.TypedEpoxyController
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.resources.StringProvider
 | 
			
		||||
import im.vector.riotx.core.ui.list.genericFooterItem
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class PushGateWayController @Inject constructor(
 | 
			
		||||
        private val stringProvider: StringProvider
 | 
			
		||||
) : TypedEpoxyController<PushGatewayViewState>() {
 | 
			
		||||
 | 
			
		||||
    override fun buildModels(data: PushGatewayViewState?) {
 | 
			
		||||
        data?.pushGateways?.invoke()?.let { pushers ->
 | 
			
		||||
            if (pushers.isEmpty()) {
 | 
			
		||||
                genericFooterItem {
 | 
			
		||||
                    id("footer")
 | 
			
		||||
                    text(stringProvider.getString(R.string.settings_push_gateway_no_pushers))
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                pushers.forEach {
 | 
			
		||||
                    pushGatewayItem {
 | 
			
		||||
                        id("${it.pushKey}_${it.appId}")
 | 
			
		||||
                        pusher(it)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } ?: run {
 | 
			
		||||
            genericFooterItem {
 | 
			
		||||
                id("loading")
 | 
			
		||||
                text(stringProvider.getString(R.string.loading))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -17,71 +17,43 @@
 | 
			
		||||
package im.vector.riotx.features.settings.push
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.recyclerview.widget.DividerItemDecoration
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import com.airbnb.epoxy.TypedEpoxyController
 | 
			
		||||
import android.view.View
 | 
			
		||||
import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseActivity
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.core.resources.StringProvider
 | 
			
		||||
import im.vector.riotx.core.ui.list.genericFooterItem
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.*
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
// Referenced in vector_settings_notifications.xml
 | 
			
		||||
class PushGatewaysFragment @Inject constructor(
 | 
			
		||||
        val pushGatewaysViewModelFactory: PushGatewaysViewModel.Factory
 | 
			
		||||
        val pushGatewaysViewModelFactory: PushGatewaysViewModel.Factory,
 | 
			
		||||
        private val epoxyController: PushGateWayController
 | 
			
		||||
) : VectorBaseFragment() {
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy
 | 
			
		||||
    override fun getLayoutResId() = R.layout.fragment_generic_recycler
 | 
			
		||||
 | 
			
		||||
    private val viewModel: PushGatewaysViewModel by fragmentViewModel(PushGatewaysViewModel::class)
 | 
			
		||||
    private val epoxyController by lazy { PushGateWayController(StringProvider(requireContext().resources)) }
 | 
			
		||||
 | 
			
		||||
    override fun onResume() {
 | 
			
		||||
        super.onResume()
 | 
			
		||||
        (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_notifications_targets)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityCreated(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onActivityCreated(savedInstanceState)
 | 
			
		||||
        val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
 | 
			
		||||
        epoxyRecyclerView.layoutManager = lmgr
 | 
			
		||||
        val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
 | 
			
		||||
                lmgr.orientation)
 | 
			
		||||
        epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
 | 
			
		||||
        epoxyRecyclerView.setController(epoxyController)
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
        recyclerView.configureWith(epoxyController, showDivider = true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun invalidate() = withState(viewModel) { state ->
 | 
			
		||||
        epoxyController.setData(state)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class PushGateWayController(private val stringProvider: StringProvider) : TypedEpoxyController<PushGatewayViewState>() {
 | 
			
		||||
        override fun buildModels(data: PushGatewayViewState?) {
 | 
			
		||||
            data?.pushGateways?.invoke()?.let { pushers ->
 | 
			
		||||
                if (pushers.isEmpty()) {
 | 
			
		||||
                    genericFooterItem {
 | 
			
		||||
                        id("footer")
 | 
			
		||||
                        text(stringProvider.getString(R.string.settings_push_gateway_no_pushers))
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    pushers.forEach {
 | 
			
		||||
                        pushGatewayItem {
 | 
			
		||||
                            id("${it.pushKey}_${it.appId}")
 | 
			
		||||
                            pusher(it)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } ?: run {
 | 
			
		||||
                genericFooterItem {
 | 
			
		||||
                    id("footer")
 | 
			
		||||
                    text(stringProvider.getString(R.string.loading))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,23 +16,23 @@
 | 
			
		||||
package im.vector.riotx.features.settings.push
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.recyclerview.widget.DividerItemDecoration
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import android.view.View
 | 
			
		||||
import com.airbnb.epoxy.TypedEpoxyController
 | 
			
		||||
import com.airbnb.mvrx.fragmentViewModel
 | 
			
		||||
import com.airbnb.mvrx.withState
 | 
			
		||||
import im.vector.riotx.R
 | 
			
		||||
import im.vector.riotx.core.extensions.cleanup
 | 
			
		||||
import im.vector.riotx.core.extensions.configureWith
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseActivity
 | 
			
		||||
import im.vector.riotx.core.platform.VectorBaseFragment
 | 
			
		||||
import im.vector.riotx.core.resources.StringProvider
 | 
			
		||||
import im.vector.riotx.core.ui.list.genericFooterItem
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.*
 | 
			
		||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
 | 
			
		||||
 | 
			
		||||
// Referenced in vector_settings_notifications.xml
 | 
			
		||||
class PushRulesFragment : VectorBaseFragment() {
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy
 | 
			
		||||
    override fun getLayoutResId() = R.layout.fragment_generic_recycler
 | 
			
		||||
 | 
			
		||||
    private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class)
 | 
			
		||||
 | 
			
		||||
@ -43,14 +43,14 @@ class PushRulesFragment : VectorBaseFragment() {
 | 
			
		||||
        (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_rules)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onActivityCreated(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onActivityCreated(savedInstanceState)
 | 
			
		||||
        val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
 | 
			
		||||
        epoxyRecyclerView.layoutManager = lmgr
 | 
			
		||||
        val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
 | 
			
		||||
                lmgr.orientation)
 | 
			
		||||
        epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
 | 
			
		||||
        epoxyRecyclerView.setController(epoxyController)
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
        recyclerView.configureWith(epoxyController, showDivider = true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
        recyclerView.cleanup()
 | 
			
		||||
        super.onDestroyView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun invalidate() = withState(viewModel) { state ->
 | 
			
		||||
 | 
			
		||||
@ -50,9 +50,7 @@ class IncomingShareActivity :
 | 
			
		||||
            return supportFragmentManager.findFragmentById(R.id.shareRoomListFragmentContainer) as? RoomListFragment
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutRes(): Int {
 | 
			
		||||
        return R.layout.activity_incoming_share
 | 
			
		||||
    }
 | 
			
		||||
    override fun getLayoutRes() = R.layout.activity_incoming_share
 | 
			
		||||
 | 
			
		||||
    override fun injectWith(injector: ScreenComponent) {
 | 
			
		||||
        injector.inject(this)
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:id="@+id/emoji_recycler_view"
 | 
			
		||||
    android:id="@+id/emojiRecyclerView"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    android:scrollbars="vertical"
 | 
			
		||||
@ -9,6 +9,4 @@
 | 
			
		||||
    tools:itemCount="100"
 | 
			
		||||
    tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
 | 
			
		||||
    tools:listitem="@layout/grid_item_emoji"
 | 
			
		||||
    tools:spanCount="10">
 | 
			
		||||
 | 
			
		||||
</androidx.recyclerview.widget.RecyclerView>
 | 
			
		||||
    tools:spanCount="10" />
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<com.airbnb.epoxy.EpoxyRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:id="@+id/breadcrumbsRecyclerView"
 | 
			
		||||
    android:layout_width="wrap_content"
 | 
			
		||||
 | 
			
		||||
@ -122,7 +122,7 @@
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@id/createDirectRoomFilterDivider" />
 | 
			
		||||
 | 
			
		||||
        <com.airbnb.epoxy.EpoxyRecyclerView
 | 
			
		||||
        <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
            android:id="@+id/recyclerView"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="0dp"
 | 
			
		||||
 | 
			
		||||
@ -89,7 +89,7 @@
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/createDirectRoomSearchByIdContainer" />
 | 
			
		||||
 | 
			
		||||
        <com.airbnb.epoxy.EpoxyRecyclerView
 | 
			
		||||
        <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
            android:id="@+id/recyclerView"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="0dp"
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,7 @@
 | 
			
		||||
 | 
			
		||||
        </androidx.appcompat.widget.Toolbar>
 | 
			
		||||
 | 
			
		||||
        <com.airbnb.epoxy.EpoxyRecyclerView
 | 
			
		||||
        <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
            android:id="@+id/createRoomForm"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="0dp"
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,8 @@
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
    <com.airbnb.epoxy.EpoxyRecyclerView
 | 
			
		||||
        android:id="@+id/epoxyRecyclerView"
 | 
			
		||||
    <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
        android:id="@+id/recyclerView"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        app:itemSpacing="1dp"
 | 
			
		||||
@ -5,8 +5,8 @@
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
    <com.airbnb.epoxy.EpoxyRecyclerView
 | 
			
		||||
        android:id="@+id/groupListEpoxyRecyclerView"
 | 
			
		||||
    <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
        android:id="@+id/groupListView"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        android:overScrollMode="always"
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<com.airbnb.epoxy.EpoxyRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:id="@+id/keysBackupSettingsRecyclerView"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@
 | 
			
		||||
            android:text="@string/auth_accept_policies"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/loginTermsTitle" />
 | 
			
		||||
 | 
			
		||||
        <com.airbnb.epoxy.EpoxyRecyclerView
 | 
			
		||||
        <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
            android:id="@+id/loginTermsPolicyList"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="0dp"
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
    <com.airbnb.epoxy.EpoxyRecyclerView
 | 
			
		||||
    <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
        android:id="@+id/publicRoomsList"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent" />
 | 
			
		||||
 | 
			
		||||
        <com.airbnb.epoxy.EpoxyRecyclerView
 | 
			
		||||
        <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
            android:id="@+id/roomDirectoryPickerList"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="0dp"
 | 
			
		||||
 | 
			
		||||
@ -3,49 +3,50 @@
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:background="?riotx_background"
 | 
			
		||||
    android:foreground="?attr/selectableItemBackground"
 | 
			
		||||
    android:gravity="center_vertical"
 | 
			
		||||
    android:minHeight="48dp"
 | 
			
		||||
    android:orientation="horizontal"
 | 
			
		||||
    android:paddingEnd="8dp"
 | 
			
		||||
    android:paddingStart="8dp"
 | 
			
		||||
    android:minHeight="44dp">
 | 
			
		||||
    android:paddingStart="@dimen/layout_horizontal_margin"
 | 
			
		||||
    android:paddingEnd="@dimen/layout_horizontal_margin">
 | 
			
		||||
 | 
			
		||||
    <!-- size in dp, because we do not want the display to be impacted by font size setting -->
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/item_emoji_tv"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center"
 | 
			
		||||
        android:layout_marginEnd="8dp"
 | 
			
		||||
        android:textSize="25sp"
 | 
			
		||||
        android:textColor="@color/black"
 | 
			
		||||
        android:textSize="25dp"
 | 
			
		||||
        tools:ignore="SpUsage"
 | 
			
		||||
        android:textColor="?android:textColorPrimary"
 | 
			
		||||
        tools:text="@sample/reactions.json/data/reaction" />
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
        android:layout_width="0dp"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:orientation="vertical"
 | 
			
		||||
        android:layout_weight="1">
 | 
			
		||||
        android:layout_weight="1"
 | 
			
		||||
        android:orientation="vertical">
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/item_emoji_name"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center"
 | 
			
		||||
            android:textStyle="bold"
 | 
			
		||||
            android:textColor="?riotx_text_primary"
 | 
			
		||||
            android:textSize="16sp"
 | 
			
		||||
            android:textColor="?android:textColorPrimary"
 | 
			
		||||
            android:textStyle="bold"
 | 
			
		||||
            tools:text="Smiley Face" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/item_emoji_keyword"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center"
 | 
			
		||||
            android:textSize="14sp"
 | 
			
		||||
            android:maxLines="2"
 | 
			
		||||
            android:textColor="?android:textColorPrimary"
 | 
			
		||||
            tools:text="Smile, foo, bar" />
 | 
			
		||||
            android:textColor="?riotx_text_secondary"
 | 
			
		||||
            android:textSize="14sp"
 | 
			
		||||
            android:visibility="gone"
 | 
			
		||||
            tools:text="Smile, foo, bar"
 | 
			
		||||
            tools:visibility="visible" />
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user