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 {
|
||||
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)
|
||||
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)
|
||||
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()
|
||||
}
|
||||
|
@ -494,9 +497,9 @@ internal class DefaultTimeline(
|
|||
return
|
||||
}
|
||||
val params = PaginationTask.Params(roomId = roomId,
|
||||
from = token,
|
||||
direction = direction.toPaginationDirection(),
|
||||
limit = limit)
|
||||
from = token,
|
||||
direction = direction.toPaginationDirection(),
|
||||
limit = limit)
|
||||
|
||||
Timber.v("Should fetch $limit items $direction")
|
||||
cancelableBag += paginationTask
|
||||
|
@ -571,7 +574,7 @@ internal class DefaultTimeline(
|
|||
val timelineEvent = buildTimelineEvent(eventEntity)
|
||||
|
||||
if (timelineEvent.isEncrypted()
|
||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
|
||||
}
|
||||
|
||||
|
|
|
@ -81,4 +81,7 @@ layout_constraintLeft_
|
|||
|
||||
### Will crash on API < 21. Use ?colorAccent instead
|
||||
\?android:colorAccent
|
||||
\?android:attr/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?) {
|
||||
|
|
|
@ -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,27 +521,29 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateJumpToReadMarkerViewVisibility() = jumpToReadMarkerView.post {
|
||||
withState(roomDetailViewModel) {
|
||||
val showJumpToUnreadBanner = when (it.unreadState) {
|
||||
UnreadState.Unknown,
|
||||
UnreadState.HasNoUnread -> false
|
||||
is UnreadState.ReadMarkerNotLoaded -> true
|
||||
is UnreadState.HasUnread -> {
|
||||
if (it.canShowJumpToReadMarker) {
|
||||
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
|
||||
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
|
||||
if (positionOfReadMarker == null) {
|
||||
false
|
||||
private fun updateJumpToReadMarkerViewVisibility() {
|
||||
jumpToReadMarkerView?.post {
|
||||
withState(roomDetailViewModel) {
|
||||
val showJumpToUnreadBanner = when (it.unreadState) {
|
||||
UnreadState.Unknown,
|
||||
UnreadState.HasNoUnread -> false
|
||||
is UnreadState.ReadMarkerNotLoaded -> true
|
||||
is UnreadState.HasUnread -> {
|
||||
if (it.canShowJumpToReadMarker) {
|
||||
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
|
||||
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
|
||||
if (positionOfReadMarker == null) {
|
||||
false
|
||||
} else {
|
||||
positionOfReadMarker > lastVisibleItem
|
||||
}
|
||||
} else {
|
||||
positionOfReadMarker > lastVisibleItem
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
jumpToReadMarkerView.isVisible = showJumpToUnreadBanner
|
||||
}
|
||||
jumpToReadMarkerView.isVisible = showJumpToUnreadBanner
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
selectedReaction = reaction
|
||||
navigateEvent.value = LiveEvent(NAVIGATE_FINISH)
|
||||
}
|
||||
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) {
|
||||
currentSection.value = section
|
||||
}
|
||||
}
|
||||
// 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,22 +95,18 @@ class EmojiReactionPickerActivity : VectorBaseActivity(),
|
|||
|
||||
viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID)
|
||||
|
||||
viewModel.emojiSourceLiveData.observe(this, Observer {
|
||||
it.rawData?.categories?.let { categories ->
|
||||
for (category in categories) {
|
||||
val s = category.emojis[0]
|
||||
tabLayout.newTab()
|
||||
.also { tab ->
|
||||
tab.text = it.rawData!!.emojis[s]!!.emojiString()
|
||||
tab.contentDescription = category.name
|
||||
}
|
||||
.also { tab ->
|
||||
tabLayout.addTab(tab)
|
||||
}
|
||||
}
|
||||
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
|
||||
}
|
||||
})
|
||||
emojiDataSource.rawData.categories.forEach { category ->
|
||||
val s = category.emojis[0]
|
||||
tabLayout.newTab()
|
||||
.also { tab ->
|
||||
tab.text = emojiDataSource.rawData.emojis[s]!!.emoji
|
||||
tab.contentDescription = category.name
|
||||
}
|
||||
.also { tab ->
|
||||
tabLayout.addTab(tab)
|
||||
}
|
||||
}
|
||||
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,14 +147,12 @@ 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) {
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position == sectionOffset - 1) return true
|
||||
sectionOffset = lastItemInSection + 2
|
||||
}
|
||||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
dataSource.rawData.categories.forEach { category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position == sectionOffset - 1) return true
|
||||
sectionOffset = lastItemInSection + 2
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -161,13 +161,11 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null,
|
|||
var sectionOffset = 1
|
||||
var lastItemInSection: Int
|
||||
var index = 0
|
||||
dataSource?.rawData?.categories?.let {
|
||||
for (category in it) {
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position <= lastItemInSection) return index
|
||||
sectionOffset = lastItemInSection + 2
|
||||
index++
|
||||
}
|
||||
dataSource.rawData.categories.forEach { category ->
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (position <= lastItemInSection) return index
|
||||
sectionOffset = lastItemInSection + 2
|
||||
index++
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
@ -176,36 +174,32 @@ 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()) {
|
||||
lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||
if (section == index) return sectionOffset
|
||||
sectionOffset = lastItemInSection + 2
|
||||
}
|
||||
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)
|
||||
} else {
|
||||
val sectionMojis = categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[position - sectionOffset]
|
||||
val item = dataSource.rawData!!.emojis[emoji]!!.emojiString()
|
||||
(holder as EmojiViewHolder).data = item
|
||||
if (scrollState != ScrollState.SETTLING || !isFastScroll) {
|
||||
val sectionNumber = getSectionForAbsoluteIndex(position)
|
||||
if (isSection(position)) {
|
||||
holder.bind(dataSource.rawData.categories[sectionNumber].name)
|
||||
} else {
|
||||
val sectionMojis = dataSource.rawData.categories[sectionNumber].emojis
|
||||
val sectionOffset = getSectionOffset(sectionNumber)
|
||||
val emoji = sectionMojis[position - sectionOffset]
|
||||
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")
|
||||
holder.bind(item)
|
||||
} else {
|
||||
holder.bind(item)
|
||||
} else {
|
||||
// Log.i("PERF","Bind without draw at position:$position")
|
||||
toUpdateWhenNotBusy.add(item to holder)
|
||||
holder.bind(null)
|
||||
}
|
||||
toUpdateWhenNotBusy.add(item to holder)
|
||||
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)
|
||||
}
|
||||
|
||||
epoxyController.listener = object : ReactionClickListener {
|
||||
override fun onReactionSelected(reaction: String) {
|
||||
sharedViewModel?.selectedReaction = reaction
|
||||
sharedViewModel?.navigateEvent?.value = LiveEvent(EmojiChooserViewModel.NAVIGATE_FINISH)
|
||||
}
|
||||
}
|
||||
override fun onDestroyView() {
|
||||
epoxyController.listener = null
|
||||
recyclerView.cleanup()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
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 onReactionSelected(reaction: String) {
|
||||
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…
Reference in New Issue