From cec08a20e5619b0d3b5ba333c3018b125a8a9b58 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 12:13:45 +0100 Subject: [PATCH 01/51] Handle breadcrumbs from account data --- .../java/im/vector/matrix/rx/RxSession.kt | 4 ++ .../android/api/session/room/RoomService.kt | 6 +++ .../database/model/BreadcrumbsEntity.kt | 25 ++++++++++ .../database/model/SessionRealmModule.kt | 1 + .../android/internal/di/MoshiProvider.kt | 2 + .../session/room/DefaultRoomService.kt | 14 ++++++ .../sync/UserAccountDataSyncHandler.kt | 9 ++++ .../sync/model/accountdata/UserAccountData.kt | 1 + .../accountdata/UserAccountDataBreadcrumbs.kt | 34 ++++++++++++++ .../user/accountdata/AccountDataModule.kt | 3 ++ .../user/accountdata/SaveBreadcrumbsTask.kt | 46 +++++++++++++++++++ 11 files changed, 145 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/BreadcrumbsEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataBreadcrumbs.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 1572851d3a..1964d05a1b 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -38,6 +38,10 @@ class RxSession(private val session: Session) { return session.liveGroupSummaries().asObservable() } + fun liveBreadcrumbs(): Observable> { + return session.liveBreadcrumbs().asObservable() + } + fun liveSyncState(): Observable { return session.syncState().asObservable() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index c7fedb2627..9b0bae9e08 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -54,6 +54,12 @@ interface RoomService { */ fun liveRoomSummaries(): LiveData> + /** + * Get a live list of Breadcrumbs + * @return the [LiveData] of [RoomSummary] + */ + fun liveBreadcrumbs(): LiveData> + /** * Mark all rooms as read */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/BreadcrumbsEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/BreadcrumbsEntity.kt new file mode 100644 index 0000000000..e430c194c5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/BreadcrumbsEntity.kt @@ -0,0 +1,25 @@ +/* + * 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.matrix.android.internal.database.model + +import io.realm.RealmList +import io.realm.RealmObject + +internal open class BreadcrumbsEntity(var roomIds: RealmList = RealmList()) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt index 76b355b064..6059d3faf7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt @@ -36,6 +36,7 @@ import io.realm.annotations.RealmModule SyncEntity::class, UserEntity::class, IgnoredUserEntity::class, + BreadcrumbsEntity::class, EventAnnotationsSummaryEntity::class, ReactionAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index 96cdf29226..793be10880 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers @@ -34,6 +35,7 @@ object MoshiProvider { .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) .registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST) .registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES) + .registerSubtype(UserAccountDataBreadcrumbs::class.java, UserAccountData.TYPE_BREADCRUMBS) ) .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java) .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 962b7b54d6..9863d12ee5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.VersioningState import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper +import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields @@ -36,6 +37,7 @@ import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import io.realm.Realm +import io.realm.RealmList import javax.inject.Inject internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy, @@ -75,6 +77,18 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona ) } + override fun liveBreadcrumbs(): LiveData> { + return monarchy.findAllMappedWithChanges( + { realm -> + // TODO Improve this query, it's not live when breadcrumbs changes + realm.where(RoomSummaryEntity::class.java) + .`in`(RoomSummaryEntityFields.ROOM_ID, + (realm.where(BreadcrumbsEntity::class.java).findFirst()?.roomIds ?: RealmList()).toTypedArray()) + }, + { roomSummaryMapper.map(it) } + ) + } + override fun joinRoom(roomId: String, viaServers: List, callback: MatrixCallback): Cancelable { return joinRoomTask .configureWith(JoinRoomTask.Params(roomId, viaServers)) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 56bc005805..0436afd05f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -29,6 +29,7 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync import im.vector.matrix.android.internal.session.sync.model.accountdata.* import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper +import im.vector.matrix.android.internal.session.user.accountdata.SaveBreadcrumbsTask import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import im.vector.matrix.android.internal.task.TaskExecutor @@ -44,6 +45,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val savePushRulesTask: SavePushRulesTask, private val saveIgnoredUsersTask: SaveIgnoredUsersTask, + private val saveBreadcrumbsTask: SaveBreadcrumbsTask, private val taskExecutor: TaskExecutor) { suspend fun handle(accountData: UserAccountDataSync?, invites: Map?) { @@ -52,6 +54,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc is UserAccountDataDirectMessages -> handleDirectChatRooms(it) is UserAccountDataPushRules -> handlePushRules(it) is UserAccountDataIgnoredUsers -> handleIgnoredUsers(it) + is UserAccountDataBreadcrumbs -> handleBreadcrumbs(it) is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}") else -> error("Missing code here!") } @@ -130,4 +133,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc .executeBy(taskExecutor) // TODO If not initial sync, we should execute a init sync } + + private fun handleBreadcrumbs(userAccountDataBreadcrumbs: UserAccountDataBreadcrumbs) { + saveBreadcrumbsTask + .configureWith(SaveBreadcrumbsTask.Params(userAccountDataBreadcrumbs.content.roomIds)) + .executeBy(taskExecutor) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt index 55dbad6099..accc9c900f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt @@ -25,6 +25,7 @@ internal abstract class UserAccountData { companion object { const val TYPE_IGNORED_USER_LIST = "m.ignored_user_list" const val TYPE_DIRECT_MESSAGES = "m.direct" + const val TYPE_BREADCRUMBS = "im.vector.setting.breadcrumbs" // Was previously "im.vector.riot.breadcrumb_rooms" const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls" const val TYPE_WIDGETS = "m.widgets" const val TYPE_PUSH_RULES = "m.push_rules" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataBreadcrumbs.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataBreadcrumbs.kt new file mode 100644 index 0000000000..c12ab126b9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataBreadcrumbs.kt @@ -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.matrix.android.internal.session.sync.model.accountdata + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class UserAccountDataBreadcrumbs( + @Json(name = "type") override val type: String = TYPE_BREADCRUMBS, + @Json(name = "content") val content: BreadcrumbsContent +) : UserAccountData() + +@JsonClass(generateAdapter = true) +internal data class BreadcrumbsContent( + @Json(name = "rooms") val roomIds: List = emptyList(), + + // We also have "recent_rooms", I do not know what to do with that list + @Json(name = "recent_rooms") val recentRoomIds: List = emptyList() +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt index 49fe8caf8e..5889ab4847 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt @@ -36,4 +36,7 @@ internal abstract class AccountDataModule { @Binds abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask + + @Binds + abstract fun bindSaveBreadcrumbsTask(saveBreadcrumbsTask: DefaultSaveBreadcrumbsTask): SaveBreadcrumbsTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt new file mode 100644 index 0000000000..7cabb2772e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt @@ -0,0 +1,46 @@ +/* + * 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.matrix.android.internal.session.user.accountdata + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.awaitTransaction +import io.realm.RealmList +import javax.inject.Inject + +/** + * Save the Breadcrumbs roomId list in DB + */ +internal interface SaveBreadcrumbsTask : Task { + data class Params( + val roomIds: List + ) +} + +internal class DefaultSaveBreadcrumbsTask @Inject constructor(private val monarchy: Monarchy) : SaveBreadcrumbsTask { + + override suspend fun execute(params: SaveBreadcrumbsTask.Params) { + monarchy.awaitTransaction { realm -> + // Get or create a breadcrumbs entity + val entity = realm.where(BreadcrumbsEntity::class.java).findFirst() + ?: realm.createObject(BreadcrumbsEntity::class.java) + + // And save the new received list + entity.roomIds = RealmList().apply { addAll(params.roomIds) } + } + } +} From 7c561ae622fe231693f9d3b609bd3a52262cff07 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 14:51:12 +0100 Subject: [PATCH 02/51] Breadcrumbs simple UI --- .../im/vector/riotx/core/di/FragmentModule.kt | 6 ++ .../vector/riotx/core/di/ViewModelModule.kt | 6 ++ .../room/breadcrumbs/BreadcrumbsAnimator.kt | 31 ++++++++ .../room/breadcrumbs/BreadcrumbsController.kt | 70 ++++++++++++++++++ .../room/breadcrumbs/BreadcrumbsFragment.kt | 71 +++++++++++++++++++ .../home/room/breadcrumbs/BreadcrumbsItem.kt | 53 ++++++++++++++ .../room/breadcrumbs/BreadcrumbsViewModel.kt | 66 +++++++++++++++++ .../room/breadcrumbs/BreadcrumbsViewState.kt | 26 +++++++ .../home/room/detail/RoomDetailActivity.kt | 55 +++++++++++++- .../room/detail/RoomDetailSharedAction.kt | 26 +++++++ .../detail/RoomDetailSharedActionViewModel.kt | 24 +++++++ vector/src/main/res/layout/activity_home.xml | 1 + .../main/res/layout/activity_room_detail.xml | 38 +++++++--- .../main/res/layout/fragment_breadcrumbs.xml | 7 ++ .../src/main/res/layout/item_breadcrumbs.xml | 22 ++++++ 15 files changed, 491 insertions(+), 11 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsAnimator.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewState.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedAction.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedActionViewModel.kt create mode 100644 vector/src/main/res/layout/fragment_breadcrumbs.xml create mode 100644 vector/src/main/res/layout/item_breadcrumbs.xml diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 208246aa68..1a7a07bee2 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -33,6 +33,7 @@ import im.vector.riotx.features.home.LoadingFragment import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsersFragment import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment import im.vector.riotx.features.home.group.GroupListFragment +import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.riotx.features.home.room.detail.RoomDetailFragment import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.login.* @@ -249,4 +250,9 @@ interface FragmentModule { @IntoMap @FragmentKey(PublicRoomsFragment::class) fun bindPublicRoomsFragment(fragment: PublicRoomsFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(BreadcrumbsFragment::class) + fun bindBreadcrumbsFragment(fragment: BreadcrumbsFragment): Fragment } diff --git a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt index 0876701504..4b136c557b 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt @@ -29,6 +29,7 @@ import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedVie import im.vector.riotx.features.crypto.verification.SasVerificationViewModel import im.vector.riotx.features.home.HomeSharedActionViewModel import im.vector.riotx.features.home.createdirect.CreateDirectRoomSharedActionViewModel +import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel import im.vector.riotx.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.riotx.features.login.LoginSharedActionViewModel @@ -118,4 +119,9 @@ interface ViewModelModule { @IntoMap @ViewModelKey(LoginSharedActionViewModel::class) fun bindLoginSharedActionViewModel(viewModel: LoginSharedActionViewModel): ViewModel + + @Binds + @IntoMap + @ViewModelKey(RoomDetailSharedActionViewModel::class) + fun bindRoomDetailSharedActionViewModel(viewModel: RoomDetailSharedActionViewModel): ViewModel } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsAnimator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsAnimator.kt new file mode 100644 index 0000000000..2e849dfe38 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsAnimator.kt @@ -0,0 +1,31 @@ +/* + * 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.breadcrumbs + +import androidx.recyclerview.widget.DefaultItemAnimator + +private const val ANIM_DURATION_IN_MILLIS = 200L + +class BreadcrumbsAnimator : DefaultItemAnimator() { + + init { + addDuration = ANIM_DURATION_IN_MILLIS + removeDuration = ANIM_DURATION_IN_MILLIS + moveDuration = ANIM_DURATION_IN_MILLIS + changeDuration = ANIM_DURATION_IN_MILLIS + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt new file mode 100644 index 0000000000..eb97b91cbe --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt @@ -0,0 +1,70 @@ +/* + * 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.breadcrumbs + +import android.view.View +import com.airbnb.epoxy.EpoxyController +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.riotx.core.utils.DebouncedClickListener +import im.vector.riotx.features.home.AvatarRenderer +import javax.inject.Inject + +class BreadcrumbsController @Inject constructor( + private val avatarRenderer: AvatarRenderer +) : EpoxyController() { + + var listener: Listener? = null + + private var viewState: BreadcrumbsViewState? = null + + init { + // We are requesting a model build directly as the first build of epoxy is on the main thread. + // It avoids to build the the whole list of rooms on the main thread. + requestModelBuild() + } + + fun update(viewState: BreadcrumbsViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val nonNullViewState = viewState ?: return + + // TODO Display a loading, or an empty state + + nonNullViewState.asyncRooms.invoke() + ?.forEach { + breadcrumbsItem { + id(it.roomId) + avatarRenderer(avatarRenderer) + roomId(it.roomId) + roomName(it.displayName) + avatarUrl(it.avatarUrl) + itemClickListener( + DebouncedClickListener(View.OnClickListener { _ -> + listener?.onRoomClicked(it) + }) + ) + } + } + } + + interface Listener { + fun onRoomClicked(room: RoomSummary) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt new file mode 100644 index 0000000000..036208ec6a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt @@ -0,0 +1,71 @@ +/* + * 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.breadcrumbs + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import com.airbnb.mvrx.fragmentViewModel +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.riotx.R +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 +import kotlinx.android.synthetic.main.fragment_breadcrumbs.* +import javax.inject.Inject + +class BreadcrumbsFragment @Inject constructor( + private val breadcrumbsController: BreadcrumbsController, + val breadcrumbsViewModelFactory: BreadcrumbsViewModel.Factory +) : VectorBaseFragment(), BreadcrumbsController.Listener { + + private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel + private val breadcrumbsViewModel: BreadcrumbsViewModel by fragmentViewModel() + + override fun getLayoutResId() = R.layout.fragment_breadcrumbs + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRecyclerView() + sharedActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java) + + breadcrumbsViewModel.subscribe { renderState(it) } + } + + override fun onDestroyView() { + super.onDestroyView() + breadcrumbsRecyclerView.adapter = null + } + + private fun setupRecyclerView() { + val layoutManager = LinearLayoutManager(context) + breadcrumbsRecyclerView.layoutManager = layoutManager + breadcrumbsRecyclerView.itemAnimator = BreadcrumbsAnimator() + breadcrumbsController.listener = this + breadcrumbsRecyclerView.setController(breadcrumbsController) + } + + private fun renderState(state: BreadcrumbsViewState) { + breadcrumbsController.update(state) + } + + // BreadcrumbsController.Listener ************************************************************** + + override fun onRoomClicked(room: RoomSummary) { + sharedActionViewModel.post(RoomDetailSharedAction.OpenRoom(room.roomId)) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt new file mode 100644 index 0000000000..d8d1acb6d3 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt @@ -0,0 +1,53 @@ +/* + * 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.breadcrumbs + +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.features.home.AvatarRenderer + +@EpoxyModelClass(layout = R.layout.item_breadcrumbs) +abstract class BreadcrumbsItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var roomId: String + @EpoxyAttribute lateinit var roomName: CharSequence + @EpoxyAttribute var avatarUrl: String? = null + // TODO @EpoxyAttribute var unreadNotificationCount: Int = 0 + // TODO @EpoxyAttribute var hasUnreadMessage: Boolean = false + // TODO @EpoxyAttribute var showHighlighted: Boolean = false + @EpoxyAttribute var itemClickListener: View.OnClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.rootView.setOnClickListener(itemClickListener) + avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) + } + + class Holder : VectorEpoxyHolder() { + // TODO val unreadCounterBadgeView by bind(R.id.roomUnreadCounterBadgeView) + // TODO val unreadIndentIndicator by bind(R.id.roomUnreadIndicator) + val avatarImageView by bind(R.id.breadcrumbsImageView) + val rootView by bind(R.id.breadcrumbsRoot) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt new file mode 100644 index 0000000000..4b462a05a6 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -0,0 +1,66 @@ +/* + * 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.breadcrumbs + +import com.airbnb.mvrx.FragmentViewModelContext +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.matrix.android.api.session.Session +import im.vector.matrix.rx.rx +import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.VectorViewModel +import io.reactivex.schedulers.Schedulers + +class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState, + private val session: Session) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: BreadcrumbsViewState): BreadcrumbsViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: BreadcrumbsViewState): BreadcrumbsViewModel? { + val fragment: BreadcrumbsFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.breadcrumbsViewModelFactory.create(state) + } + } + + init { + observeBreadcrumbs() + } + + override fun handle(action: EmptyAction) { + // No op + } + + // PRIVATE METHODS ***************************************************************************** + + private fun observeBreadcrumbs() { + session.rx() + .liveBreadcrumbs() + .observeOn(Schedulers.computation()) + .execute { asyncRooms -> + copy(asyncRooms = asyncRooms) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewState.kt new file mode 100644 index 0000000000..cb00db4c9f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewState.kt @@ -0,0 +1,26 @@ +/* + * 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.breadcrumbs + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.room.model.RoomSummary + +data class BreadcrumbsViewState( + val asyncRooms: Async> = Uninitialized +) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt index eb8118a0c9..bdc3b59eff 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt @@ -20,17 +20,25 @@ import android.content.Context import android.content.Intent import android.os.Bundle import androidx.appcompat.widget.Toolbar +import androidx.core.view.GravityCompat +import androidx.drawerlayout.widget.DrawerLayout import im.vector.riotx.R +import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.replaceFragment import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity +import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment +import kotlinx.android.synthetic.main.activity_room_detail.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable { - override fun getLayoutRes(): Int { - return R.layout.activity_room_detail - } + override fun getLayoutRes() = R.layout.activity_room_detail + + private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel + + // Simple filter + private var currentRoomId: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -38,14 +46,55 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable { if (isFirstCreation()) { val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) ?: return + currentRoomId = roomDetailArgs.roomId replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, roomDetailArgs) + replaceFragment(R.id.roomDetailDrawerContainer, BreadcrumbsFragment::class.java) } + + sharedActionViewModel = viewModelProvider.get(RoomDetailSharedActionViewModel::class.java) + + sharedActionViewModel + .observe() + .subscribe { sharedAction -> + when (sharedAction) { + is RoomDetailSharedAction.OpenRoom -> { + drawerLayout.closeDrawer(GravityCompat.START) + // Do not replace the Fragment if it's the same roomId + if (currentRoomId != sharedAction.roomId) { + currentRoomId = sharedAction.roomId + replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, RoomDetailArgs(sharedAction.roomId)) + } + } + } + } + .disposeOnDestroy() + + drawerLayout.addDrawerListener(drawerListener) + } + + override fun onDestroy() { + drawerLayout.removeDrawerListener(drawerListener) + super.onDestroy() } override fun configure(toolbar: Toolbar) { configureToolbar(toolbar) } + private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { + override fun onDrawerStateChanged(newState: Int) { + hideKeyboard() + } + } + + override fun onBackPressed() { + if (drawerLayout.isDrawerOpen(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START) + } else { + super.onBackPressed() + } + } + companion object { private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS" diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedAction.kt new file mode 100644 index 0000000000..6a88bb1f13 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedAction.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import im.vector.riotx.core.platform.VectorSharedAction + +/** + * Supported navigation actions for [RoomDetailActivity] + */ +sealed class RoomDetailSharedAction : VectorSharedAction { + data class OpenRoom(val roomId: String) : RoomDetailSharedAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedActionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedActionViewModel.kt new file mode 100644 index 0000000000..6f2162bebc --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedActionViewModel.kt @@ -0,0 +1,24 @@ +/* + * 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 + +import im.vector.riotx.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +/** + * Activity shared view model + */ +class RoomDetailSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/res/layout/activity_home.xml b/vector/src/main/res/layout/activity_home.xml index 0ce124b787..61fb1b5ad4 100644 --- a/vector/src/main/res/layout/activity_home.xml +++ b/vector/src/main/res/layout/activity_home.xml @@ -17,6 +17,7 @@ android:layout_height="match_parent" /> + diff --git a/vector/src/main/res/layout/activity_room_detail.xml b/vector/src/main/res/layout/activity_room_detail.xml index 4d8fc23f24..14fda5e9c6 100644 --- a/vector/src/main/res/layout/activity_room_detail.xml +++ b/vector/src/main/res/layout/activity_room_detail.xml @@ -1,14 +1,36 @@ - - + android:layout_height="match_parent" + tools:openDrawer="start"> + + + + + + + + + + + + + + android:layout_height="match_parent" + android:layout_gravity="start" /> - - - \ No newline at end of file + diff --git a/vector/src/main/res/layout/fragment_breadcrumbs.xml b/vector/src/main/res/layout/fragment_breadcrumbs.xml new file mode 100644 index 0000000000..780a1a5cb2 --- /dev/null +++ b/vector/src/main/res/layout/fragment_breadcrumbs.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_breadcrumbs.xml b/vector/src/main/res/layout/item_breadcrumbs.xml new file mode 100644 index 0000000000..0f8b535b7b --- /dev/null +++ b/vector/src/main/res/layout/item_breadcrumbs.xml @@ -0,0 +1,22 @@ + + + + + + From 4ff12605e911c40ba785d46ea1379a78e51adf75 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 16:06:47 +0100 Subject: [PATCH 03/51] Breadcrumbs: notify viewed rooms --- .../android/api/session/room/RoomService.kt | 6 ++ .../database/model/BreadcrumbsEntity.kt | 4 +- .../session/room/DefaultRoomService.kt | 10 ++- .../sync/UserAccountDataSyncHandler.kt | 2 +- .../accountdata/UserAccountDataBreadcrumbs.kt | 3 - .../user/accountdata/AccountDataModule.kt | 3 + .../user/accountdata/SaveBreadcrumbsTask.kt | 10 +-- .../user/accountdata/UpdateBreadcrumbsTask.kt | 68 +++++++++++++++++++ .../accountdata/UpdateUserAccountDataTask.kt | 10 +++ .../home/room/detail/RoomDetailViewModel.kt | 3 + 10 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 9b0bae9e08..2dbb580a4f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -60,6 +60,12 @@ interface RoomService { */ fun liveBreadcrumbs(): LiveData> + /** + * Inform the Matrix SDK that a room is displayed. + * The SDK will update the breadcrumbs in the user account data + */ + fun onRoomDisplayed(roomId: String): Cancelable + /** * Mark all rooms as read */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/BreadcrumbsEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/BreadcrumbsEntity.kt index e430c194c5..c396795421 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/BreadcrumbsEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/BreadcrumbsEntity.kt @@ -19,7 +19,9 @@ package im.vector.matrix.android.internal.database.model import io.realm.RealmList import io.realm.RealmObject -internal open class BreadcrumbsEntity(var roomIds: RealmList = RealmList()) : RealmObject() { +internal open class BreadcrumbsEntity( + var recentRoomIds: RealmList = RealmList() +) : RealmObject() { companion object } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 9863d12ee5..268d625622 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask +import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import io.realm.Realm @@ -45,6 +46,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona private val createRoomTask: CreateRoomTask, private val joinRoomTask: JoinRoomTask, private val markAllRoomsReadTask: MarkAllRoomsReadTask, + private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, private val roomFactory: RoomFactory, private val taskExecutor: TaskExecutor) : RoomService { @@ -83,12 +85,18 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona // TODO Improve this query, it's not live when breadcrumbs changes realm.where(RoomSummaryEntity::class.java) .`in`(RoomSummaryEntityFields.ROOM_ID, - (realm.where(BreadcrumbsEntity::class.java).findFirst()?.roomIds ?: RealmList()).toTypedArray()) + (realm.where(BreadcrumbsEntity::class.java).findFirst()?.recentRoomIds ?: RealmList()).toTypedArray()) }, { roomSummaryMapper.map(it) } ) } + override fun onRoomDisplayed(roomId: String): Cancelable { + return updateBreadcrumbsTask + .configureWith(UpdateBreadcrumbsTask.Params(roomId)) + .executeBy(taskExecutor) + } + override fun joinRoom(roomId: String, viaServers: List, callback: MatrixCallback): Cancelable { return joinRoomTask .configureWith(JoinRoomTask.Params(roomId, viaServers)) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 0436afd05f..9cc3a5a3c6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -136,7 +136,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc private fun handleBreadcrumbs(userAccountDataBreadcrumbs: UserAccountDataBreadcrumbs) { saveBreadcrumbsTask - .configureWith(SaveBreadcrumbsTask.Params(userAccountDataBreadcrumbs.content.roomIds)) + .configureWith(SaveBreadcrumbsTask.Params(userAccountDataBreadcrumbs.content.recentRoomIds)) .executeBy(taskExecutor) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataBreadcrumbs.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataBreadcrumbs.kt index c12ab126b9..cf5ee9c5e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataBreadcrumbs.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataBreadcrumbs.kt @@ -27,8 +27,5 @@ internal data class UserAccountDataBreadcrumbs( @JsonClass(generateAdapter = true) internal data class BreadcrumbsContent( - @Json(name = "rooms") val roomIds: List = emptyList(), - - // We also have "recent_rooms", I do not know what to do with that list @Json(name = "recent_rooms") val recentRoomIds: List = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt index 5889ab4847..37b63dcd07 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt @@ -39,4 +39,7 @@ internal abstract class AccountDataModule { @Binds abstract fun bindSaveBreadcrumbsTask(saveBreadcrumbsTask: DefaultSaveBreadcrumbsTask): SaveBreadcrumbsTask + + @Binds + abstract fun bindUpdateBreadcrumsTask(saveBreadcrumbsTask: DefaultUpdateBreadcrumbsTask): UpdateBreadcrumbsTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt index 7cabb2772e..97cfba0549 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt @@ -23,15 +23,17 @@ import io.realm.RealmList import javax.inject.Inject /** - * Save the Breadcrumbs roomId list in DB + * Save the Breadcrumbs roomId list in DB, either from the sync, or updated locally */ internal interface SaveBreadcrumbsTask : Task { data class Params( - val roomIds: List + val recentRoomIds: List ) } -internal class DefaultSaveBreadcrumbsTask @Inject constructor(private val monarchy: Monarchy) : SaveBreadcrumbsTask { +internal class DefaultSaveBreadcrumbsTask @Inject constructor( + private val monarchy: Monarchy +) : SaveBreadcrumbsTask { override suspend fun execute(params: SaveBreadcrumbsTask.Params) { monarchy.awaitTransaction { realm -> @@ -40,7 +42,7 @@ internal class DefaultSaveBreadcrumbsTask @Inject constructor(private val monarc ?: realm.createObject(BreadcrumbsEntity::class.java) // And save the new received list - entity.roomIds = RealmList().apply { addAll(params.roomIds) } + entity.recentRoomIds = RealmList().apply { addAll(params.recentRoomIds) } } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt new file mode 100644 index 0000000000..0f2f5f9a3d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt @@ -0,0 +1,68 @@ +/* + * 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.matrix.android.internal.session.user.accountdata + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity +import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.fetchCopied +import javax.inject.Inject + +internal interface UpdateBreadcrumbsTask : Task { + data class Params( + // Last seen roomId + val roomId: String + ) +} + +internal class DefaultUpdateBreadcrumbsTask @Inject constructor( + private val saveBreadcrumbsTask: SaveBreadcrumbsTask, + private val updateUserAccountDataTask: UpdateUserAccountDataTask, + private val monarchy: Monarchy +) : UpdateBreadcrumbsTask { + + override suspend fun execute(params: UpdateBreadcrumbsTask.Params) { + // Get the current breadcrumbs in DB + val bc = monarchy.fetchCopied { realm -> + // Get the breadcrumbs entity, if any + realm.where(BreadcrumbsEntity::class.java).findFirst() + + } + + // Modify the list to add the roomId first + val newRecentRoomIds = if (bc != null) { + // Ensure the roomId is not already in the list + bc.recentRoomIds.remove(params.roomId) + // Add the room at first position + bc.recentRoomIds.add(0, params.roomId) + bc.recentRoomIds.toList() + } else { + listOf(params.roomId) + } + + // Update the DB locally, do not wait for the sync + saveBreadcrumbsTask.execute(SaveBreadcrumbsTask.Params(newRecentRoomIds)) + + // And update account data + updateUserAccountDataTask.execute(UpdateUserAccountDataTask.BreadcrumbsParams( + breadcrumbsContent = BreadcrumbsContent( + newRecentRoomIds + ) + )) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index 9fa71005ff..4c4f40add5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.task.Task import javax.inject.Inject @@ -38,6 +39,15 @@ internal interface UpdateUserAccountDataTask : Task Date: Thu, 5 Dec 2019 17:43:23 +0100 Subject: [PATCH 04/51] Breadcrumbs: limit number to 20 --- .../session/user/accountdata/UpdateBreadcrumbsTask.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt index 0f2f5f9a3d..faf0325220 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt @@ -23,6 +23,9 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.fetchCopied import javax.inject.Inject +// Use same arbitrary value than Riot-Web +private const val MAX_BREADCRUMBS_ROOMS_NUMBER = 20 + internal interface UpdateBreadcrumbsTask : Task { data class Params( // Last seen roomId @@ -50,7 +53,7 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor( bc.recentRoomIds.remove(params.roomId) // Add the room at first position bc.recentRoomIds.add(0, params.roomId) - bc.recentRoomIds.toList() + bc.recentRoomIds.take(MAX_BREADCRUMBS_ROOMS_NUMBER) } else { listOf(params.roomId) } From 5373d9aa21493f7745b706264eff5177e52211a9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 17:49:45 +0100 Subject: [PATCH 05/51] Breadcrumbs: fix layout issue --- vector/src/main/res/layout/activity_room_detail.xml | 2 +- vector/src/main/res/layout/fragment_breadcrumbs.xml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/activity_room_detail.xml b/vector/src/main/res/layout/activity_room_detail.xml index 14fda5e9c6..cc10341d2f 100644 --- a/vector/src/main/res/layout/activity_room_detail.xml +++ b/vector/src/main/res/layout/activity_room_detail.xml @@ -29,7 +29,7 @@ diff --git a/vector/src/main/res/layout/fragment_breadcrumbs.xml b/vector/src/main/res/layout/fragment_breadcrumbs.xml index 780a1a5cb2..c17b35fdee 100644 --- a/vector/src/main/res/layout/fragment_breadcrumbs.xml +++ b/vector/src/main/res/layout/fragment_breadcrumbs.xml @@ -2,6 +2,7 @@ \ No newline at end of file From 4234c27af96f320e2bf49d1e008251120e2fddf4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 18:19:20 +0100 Subject: [PATCH 06/51] Version++ --- CHANGES.md | 21 +++++++++++++++++++++ vector/build.gradle | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9b4e022d5e..869038a776 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,24 @@ +Changes in RiotX 0.10.0 (2019-XX-XX) +=================================================== + +Features ✨: + - + +Improvements 🙌: + - + +Other changes: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +Build 🧱: + - + Changes in RiotX 0.9.1 (2019-12-05) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index 55625d4466..d7f4a8b453 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -15,8 +15,8 @@ androidExtensions { } ext.versionMajor = 0 -ext.versionMinor = 9 -ext.versionPatch = 1 +ext.versionMinor = 10 +ext.versionPatch = 0 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 60169d53d72094ba00a5e97d07b05f7169790495 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 20:28:07 +0100 Subject: [PATCH 07/51] Breadcrumbs: add some visual attributes --- .../room/breadcrumbs/BreadcrumbsController.kt | 4 ++ .../home/room/breadcrumbs/BreadcrumbsItem.kt | 17 +++++-- .../src/main/res/layout/item_breadcrumbs.xml | 50 ++++++++++++++++++- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt index eb97b91cbe..ea359ee2f7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt @@ -55,6 +55,10 @@ class BreadcrumbsController @Inject constructor( roomId(it.roomId) roomName(it.displayName) avatarUrl(it.avatarUrl) + unreadNotificationCount(it.notificationCount) + showHighlighted(it.highlightCount > 0) + hasUnreadMessage(it.hasUnreadMessages) + hasDraft(it.userDrafts.isNotEmpty()) itemClickListener( DebouncedClickListener(View.OnClickListener { _ -> listener?.onRoomClicked(it) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt index d8d1acb6d3..074c35af00 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt @@ -19,12 +19,14 @@ package im.vector.riotx.features.home.room.breadcrumbs import android.view.View import android.view.ViewGroup import android.widget.ImageView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView @EpoxyModelClass(layout = R.layout.item_breadcrumbs) abstract class BreadcrumbsItem : VectorEpoxyModel() { @@ -33,20 +35,25 @@ abstract class BreadcrumbsItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var roomId: String @EpoxyAttribute lateinit var roomName: CharSequence @EpoxyAttribute var avatarUrl: String? = null - // TODO @EpoxyAttribute var unreadNotificationCount: Int = 0 - // TODO @EpoxyAttribute var hasUnreadMessage: Boolean = false - // TODO @EpoxyAttribute var showHighlighted: Boolean = false + @EpoxyAttribute var unreadNotificationCount: Int = 0 + @EpoxyAttribute var showHighlighted: Boolean = false + @EpoxyAttribute var hasUnreadMessage: Boolean = false + @EpoxyAttribute var hasDraft: Boolean = false @EpoxyAttribute var itemClickListener: View.OnClickListener? = null override fun bind(holder: Holder) { super.bind(holder) holder.rootView.setOnClickListener(itemClickListener) + holder.unreadIndentIndicator.isVisible = hasUnreadMessage avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) + holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) + holder.draftIndentIndicator.isVisible = hasDraft } class Holder : VectorEpoxyHolder() { - // TODO val unreadCounterBadgeView by bind(R.id.roomUnreadCounterBadgeView) - // TODO val unreadIndentIndicator by bind(R.id.roomUnreadIndicator) + val unreadCounterBadgeView by bind(R.id.breadcrumbsUnreadCounterBadgeView) + val unreadIndentIndicator by bind(R.id.breadcrumbsUnreadIndicator) + val draftIndentIndicator by bind(R.id.breadcrumbsDraftBadge) val avatarImageView by bind(R.id.breadcrumbsImageView) val rootView by bind(R.id.breadcrumbsRoot) } diff --git a/vector/src/main/res/layout/item_breadcrumbs.xml b/vector/src/main/res/layout/item_breadcrumbs.xml index 0f8b535b7b..4d9616ab1b 100644 --- a/vector/src/main/res/layout/item_breadcrumbs.xml +++ b/vector/src/main/res/layout/item_breadcrumbs.xml @@ -1,5 +1,6 @@ - + + - + + + + + From 849e7c613c621aa6869481769b2eabbbd16214cb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 21:27:56 +0100 Subject: [PATCH 08/51] Breadcrumbs: live update in correct order --- .../database/model/RoomSummaryEntity.kt | 7 +++++-- .../session/room/DefaultRoomService.kt | 11 +++++------ .../user/accountdata/SaveBreadcrumbsTask.kt | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index f414325aed..47904380a0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -38,7 +38,8 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", var readMarkerId: String? = null, var hasUnreadMessages: Boolean = false, var tags: RealmList = RealmList(), - var userDrafts: UserDraftsEntity? = null + var userDrafts: UserDraftsEntity? = null, + var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS ) : RealmObject() { private var membershipStr: String = Membership.NONE.name @@ -59,5 +60,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", versioningStateStr = value.name } - companion object + companion object { + const val NOT_IN_BREADCRUMBS = -1 + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 268d625622..be2a588510 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.room.model.VersioningState import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper -import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields @@ -38,7 +37,6 @@ import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcru import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import io.realm.Realm -import io.realm.RealmList import javax.inject.Inject internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy, @@ -82,10 +80,11 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona override fun liveBreadcrumbs(): LiveData> { return monarchy.findAllMappedWithChanges( { realm -> - // TODO Improve this query, it's not live when breadcrumbs changes - realm.where(RoomSummaryEntity::class.java) - .`in`(RoomSummaryEntityFields.ROOM_ID, - (realm.where(BreadcrumbsEntity::class.java).findFirst()?.recentRoomIds ?: RealmList()).toTypedArray()) + RoomSummaryEntity.where(realm) + .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) + .notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) + .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) + .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) }, { roomSummaryMapper.map(it) } ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt index 97cfba0549..be694290c3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt @@ -17,6 +17,9 @@ package im.vector.matrix.android.internal.session.user.accountdata import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields +import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.RealmList @@ -43,6 +46,22 @@ internal class DefaultSaveBreadcrumbsTask @Inject constructor( // And save the new received list entity.recentRoomIds = RealmList().apply { addAll(params.recentRoomIds) } + + // Update the room summaries + // Reset all the indexes... + RoomSummaryEntity.where(realm) + .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) + .findAll() + .forEach { + it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS + } + + // ...and apply new indexes + params.recentRoomIds.forEachIndexed { index, roomId -> + RoomSummaryEntity.where(realm, roomId) + .findFirst() + ?.breadcrumbsIndex = index + } } } } From 65333e603196c19352db33995d22b5ec8c21b12a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 21:49:01 +0100 Subject: [PATCH 09/51] Cleanup some Realm queries --- .../store/db/query/CryptoRoomEntityQueries.kt | 6 ++-- .../store/db/query/DeviceInfoEntityQueries.kt | 14 +++++---- .../store/db/query/UserEntitiesQueries.kt | 5 ++-- .../database/query/BreadcrumbsEntityQuery.kt | 30 +++++++++++++++++++ .../database/query/ReadMarkerEntityQueries.kt | 4 +-- .../query/ReadReceiptEntityQueries.kt | 10 ++++--- .../query/RoomSummaryEntityQueries.kt | 4 +-- .../user/accountdata/SaveBreadcrumbsTask.kt | 4 +-- .../user/accountdata/UpdateBreadcrumbsTask.kt | 5 ++-- 9 files changed, 57 insertions(+), 25 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/BreadcrumbsEntityQuery.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/CryptoRoomEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/CryptoRoomEntityQueries.kt index d7ab87a401..1e902f5133 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/CryptoRoomEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/CryptoRoomEntityQueries.kt @@ -19,16 +19,14 @@ package im.vector.matrix.android.internal.crypto.store.db.query import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntityFields import io.realm.Realm +import io.realm.kotlin.createObject import io.realm.kotlin.where /** * Get or create a room */ internal fun CryptoRoomEntity.Companion.getOrCreate(realm: Realm, roomId: String): CryptoRoomEntity { - return getById(realm, roomId) - ?: let { - realm.createObject(CryptoRoomEntity::class.java, roomId) - } + return getById(realm, roomId) ?: realm.createObject(roomId) } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/DeviceInfoEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/DeviceInfoEntityQueries.kt index 706815ff76..08bbb8f920 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/DeviceInfoEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/DeviceInfoEntityQueries.kt @@ -20,18 +20,20 @@ import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.createPrimaryKey import io.realm.Realm +import io.realm.kotlin.createObject import io.realm.kotlin.where /** * Get or create a device info */ internal fun DeviceInfoEntity.Companion.getOrCreate(realm: Realm, userId: String, deviceId: String): DeviceInfoEntity { + val key = DeviceInfoEntity.createPrimaryKey(userId, deviceId) + return realm.where() - .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) + .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, key) .findFirst() - ?: let { - realm.createObject(DeviceInfoEntity::class.java, DeviceInfoEntity.createPrimaryKey(userId, deviceId)).apply { - this.deviceId = deviceId - } - } + ?: realm.createObject(key) + .apply { + this.deviceId = deviceId + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/UserEntitiesQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/UserEntitiesQueries.kt index 8088a14825..b7f75cfead 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/UserEntitiesQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/UserEntitiesQueries.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.store.db.query import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields import io.realm.Realm +import io.realm.kotlin.createObject import io.realm.kotlin.where /** @@ -28,9 +29,7 @@ internal fun UserEntity.Companion.getOrCreate(realm: Realm, userId: String): Use return realm.where() .equalTo(UserEntityFields.USER_ID, userId) .findFirst() - ?: let { - realm.createObject(UserEntity::class.java, userId) - } + ?: realm.createObject(userId) } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/BreadcrumbsEntityQuery.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/BreadcrumbsEntityQuery.kt new file mode 100644 index 0000000000..60ed8aae7c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/BreadcrumbsEntityQuery.kt @@ -0,0 +1,30 @@ +/* + * 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.matrix.android.internal.database.query + +import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity +import io.realm.Realm +import io.realm.kotlin.createObject +import io.realm.kotlin.where + +internal fun BreadcrumbsEntity.Companion.get(realm: Realm): BreadcrumbsEntity? { + return realm.where().findFirst() +} + +internal fun BreadcrumbsEntity.Companion.getOrCreate(realm: Realm): BreadcrumbsEntity { + return get(realm) ?: realm.createObject() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadMarkerEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadMarkerEntityQueries.kt index d95dc58574..5f96c12953 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadMarkerEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadMarkerEntityQueries.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.ReadMarkerEntity import im.vector.matrix.android.internal.database.model.ReadMarkerEntityFields import io.realm.Realm import io.realm.RealmQuery +import io.realm.kotlin.createObject import io.realm.kotlin.where internal fun ReadMarkerEntity.Companion.where(realm: Realm, roomId: String): RealmQuery { @@ -28,6 +29,5 @@ internal fun ReadMarkerEntity.Companion.where(realm: Realm, roomId: String): Rea } internal fun ReadMarkerEntity.Companion.getOrCreate(realm: Realm, roomId: String): ReadMarkerEntity { - return where(realm, roomId).findFirst() - ?: realm.createObject(ReadMarkerEntity::class.java, roomId) + return where(realm, roomId).findFirst() ?: realm.createObject(roomId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptEntityQueries.kt index 6b996d1285..2aa40dd2a1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReadReceiptEntityQueries.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.ReadReceiptEntity import im.vector.matrix.android.internal.database.model.ReadReceiptEntityFields import io.realm.Realm import io.realm.RealmQuery +import io.realm.kotlin.createObject import io.realm.kotlin.where internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery { @@ -44,10 +45,11 @@ internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity { return ReadReceiptEntity.where(realm, roomId, userId).findFirst() - ?: realm.createObject(ReadReceiptEntity::class.java, buildPrimaryKey(roomId, userId)).apply { - this.roomId = roomId - this.userId = userId - } + ?: realm.createObject(buildPrimaryKey(roomId, userId)) + .apply { + this.roomId = roomId + this.userId = userId + } } private fun buildPrimaryKey(roomId: String, userId: String) = "${roomId}_$userId" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt index a92d81b54c..79473f3b10 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/RoomSummaryEntityQueries.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import io.realm.Realm import io.realm.RealmQuery import io.realm.RealmResults +import io.realm.kotlin.createObject import io.realm.kotlin.where internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { @@ -32,8 +33,7 @@ internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = n } internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomSummaryEntity { - return where(realm, roomId).findFirst() - ?: realm.createObject(RoomSummaryEntity::class.java, roomId) + return where(realm, roomId).findFirst() ?: realm.createObject(roomId) } internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt index be694290c3..008dd1d652 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt @@ -19,6 +19,7 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields +import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.awaitTransaction @@ -41,8 +42,7 @@ internal class DefaultSaveBreadcrumbsTask @Inject constructor( override suspend fun execute(params: SaveBreadcrumbsTask.Params) { monarchy.awaitTransaction { realm -> // Get or create a breadcrumbs entity - val entity = realm.where(BreadcrumbsEntity::class.java).findFirst() - ?: realm.createObject(BreadcrumbsEntity::class.java) + val entity = BreadcrumbsEntity.getOrCreate(realm) // And save the new received list entity.recentRoomIds = RealmList().apply { addAll(params.recentRoomIds) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt index faf0325220..e2fbf67301 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity +import im.vector.matrix.android.internal.database.query.get import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.fetchCopied @@ -43,8 +44,7 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor( // Get the current breadcrumbs in DB val bc = monarchy.fetchCopied { realm -> // Get the breadcrumbs entity, if any - realm.where(BreadcrumbsEntity::class.java).findFirst() - + BreadcrumbsEntity.get(realm) } // Modify the list to add the roomId first @@ -55,6 +55,7 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor( bc.recentRoomIds.add(0, params.roomId) bc.recentRoomIds.take(MAX_BREADCRUMBS_ROOMS_NUMBER) } else { + // FIXME It can remove the previous breadcrumbs, if not synced yet listOf(params.roomId) } From 0768bd5c884cf6df7cecfdda5a37eb3e7eaeecb0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 22:05:37 +0100 Subject: [PATCH 10/51] Breadcrumbs: nicer algorithm --- .../user/accountdata/UpdateBreadcrumbsTask.kt | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt index e2fbf67301..bde4d138af 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt @@ -24,13 +24,12 @@ import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.fetchCopied import javax.inject.Inject -// Use same arbitrary value than Riot-Web +// Use the same arbitrary value than Riot-Web private const val MAX_BREADCRUMBS_ROOMS_NUMBER = 20 internal interface UpdateBreadcrumbsTask : Task { data class Params( - // Last seen roomId - val roomId: String + val newTopRoomId: String ) } @@ -41,32 +40,27 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor( ) : UpdateBreadcrumbsTask { override suspend fun execute(params: UpdateBreadcrumbsTask.Params) { - // Get the current breadcrumbs in DB - val bc = monarchy.fetchCopied { realm -> - // Get the breadcrumbs entity, if any - BreadcrumbsEntity.get(realm) - } - - // Modify the list to add the roomId first - val newRecentRoomIds = if (bc != null) { - // Ensure the roomId is not already in the list - bc.recentRoomIds.remove(params.roomId) - // Add the room at first position - bc.recentRoomIds.add(0, params.roomId) - bc.recentRoomIds.take(MAX_BREADCRUMBS_ROOMS_NUMBER) - } else { - // FIXME It can remove the previous breadcrumbs, if not synced yet - listOf(params.roomId) - } + val newBreadcrumbs = + // Get the breadcrumbs entity, if any + monarchy.fetchCopied { BreadcrumbsEntity.get(it) } + ?.recentRoomIds + ?.apply { + // Modify the list to add the newTopRoomId first + // Ensure the roomId is not already in the list + remove(params.newTopRoomId) + // Add the room at first position + add(0, params.newTopRoomId) + } + ?.take(MAX_BREADCRUMBS_ROOMS_NUMBER) + ?: listOf(params.newTopRoomId) // Update the DB locally, do not wait for the sync - saveBreadcrumbsTask.execute(SaveBreadcrumbsTask.Params(newRecentRoomIds)) + saveBreadcrumbsTask.execute(SaveBreadcrumbsTask.Params(newBreadcrumbs)) + // FIXME It can remove the previous breadcrumbs, if not synced yet // And update account data updateUserAccountDataTask.execute(UpdateUserAccountDataTask.BreadcrumbsParams( - breadcrumbsContent = BreadcrumbsContent( - newRecentRoomIds - ) + breadcrumbsContent = BreadcrumbsContent(newBreadcrumbs) )) } } From aef76241a39b1d27c7de6c8069d00dba552bcd86 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 22:09:55 +0100 Subject: [PATCH 11/51] Breadcrumbs: changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 553091bd67..d6f6801257 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.10.0 (2019-XX-XX) =================================================== Features ✨: - - + - Breadcrumbs: switch from one room to another quickly (#571) Improvements 🙌: - From 9a4d8f87f66f2955d4279925968553c69b52368d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 22:38:49 +0100 Subject: [PATCH 12/51] Breadcrumbs: auto-review --- docs/signup.md | 2 +- .../user/accountdata/AccountDataModule.kt | 6 +++--- .../user/accountdata/UpdateBreadcrumbsTask.kt | 4 ++-- .../room/breadcrumbs/BreadcrumbsController.kt | 14 +++++++------- .../room/breadcrumbs/BreadcrumbsFragment.kt | 5 ++--- .../room/breadcrumbs/BreadcrumbsViewModel.kt | 4 ++-- .../room/breadcrumbs/BreadcrumbsViewState.kt | 2 +- .../home/room/detail/RoomDetailActivity.kt | 18 ++++++++++-------- .../home/room/detail/RoomDetailSharedAction.kt | 2 +- .../home/room/list/RoomSummaryController.kt | 2 +- .../main/res/layout/fragment_breadcrumbs.xml | 2 +- 11 files changed, 31 insertions(+), 30 deletions(-) diff --git a/docs/signup.md b/docs/signup.md index 7372ad2204..995f5d50a6 100644 --- a/docs/signup.md +++ b/docs/signup.md @@ -17,7 +17,7 @@ Client request the sign-up flows, once the homeserver is chosen by the user and } ``` -We get the flows with a 401, which also means the the registration is possible on this homeserver. +We get the flows with a 401, which also means that the registration is possible on this homeserver. ```json { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt index 37b63dcd07..1fd4162d0a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/AccountDataModule.kt @@ -35,11 +35,11 @@ internal abstract class AccountDataModule { } @Binds - abstract fun bindUpdateUserAccountDataTask(updateUserAccountDataTask: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask + abstract fun bindUpdateUserAccountDataTask(task: DefaultUpdateUserAccountDataTask): UpdateUserAccountDataTask @Binds - abstract fun bindSaveBreadcrumbsTask(saveBreadcrumbsTask: DefaultSaveBreadcrumbsTask): SaveBreadcrumbsTask + abstract fun bindSaveBreadcrumbsTask(task: DefaultSaveBreadcrumbsTask): SaveBreadcrumbsTask @Binds - abstract fun bindUpdateBreadcrumsTask(saveBreadcrumbsTask: DefaultUpdateBreadcrumbsTask): UpdateBreadcrumbsTask + abstract fun bindUpdateBreadcrumsTask(task: DefaultUpdateBreadcrumbsTask): UpdateBreadcrumbsTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt index bde4d138af..b11072a0bd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt @@ -46,9 +46,9 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor( ?.recentRoomIds ?.apply { // Modify the list to add the newTopRoomId first - // Ensure the roomId is not already in the list + // Ensure the newTopRoomId is not already in the list remove(params.newTopRoomId) - // Add the room at first position + // Add the newTopRoomId at first position add(0, params.newTopRoomId) } ?.take(MAX_BREADCRUMBS_ROOMS_NUMBER) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt index ea359ee2f7..3e400b37ea 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt @@ -18,7 +18,6 @@ package im.vector.riotx.features.home.room.breadcrumbs import android.view.View import com.airbnb.epoxy.EpoxyController -import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer import javax.inject.Inject @@ -33,7 +32,7 @@ class BreadcrumbsController @Inject constructor( init { // We are requesting a model build directly as the first build of epoxy is on the main thread. - // It avoids to build the the whole list of rooms on the main thread. + // It avoids to build the whole list of breadcrumbs on the main thread. requestModelBuild() } @@ -43,11 +42,12 @@ class BreadcrumbsController @Inject constructor( } override fun buildModels() { - val nonNullViewState = viewState ?: return + val safeViewState = viewState ?: return - // TODO Display a loading, or an empty state + // An empty breadcrumbs list can only be temporary because when entering in a room, + // this one is added to the breadcrumbs - nonNullViewState.asyncRooms.invoke() + safeViewState.asyncBreadcrumbs.invoke() ?.forEach { breadcrumbsItem { id(it.roomId) @@ -61,7 +61,7 @@ class BreadcrumbsController @Inject constructor( hasDraft(it.userDrafts.isNotEmpty()) itemClickListener( DebouncedClickListener(View.OnClickListener { _ -> - listener?.onRoomClicked(it) + listener?.onBreadcrumbClicked(it.roomId) }) ) } @@ -69,6 +69,6 @@ class BreadcrumbsController @Inject constructor( } interface Listener { - fun onRoomClicked(room: RoomSummary) + fun onBreadcrumbClicked(roomId: String) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt index 036208ec6a..e57a2ddac4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt @@ -20,7 +20,6 @@ import android.os.Bundle import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.fragmentViewModel -import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.room.detail.RoomDetailSharedAction @@ -65,7 +64,7 @@ class BreadcrumbsFragment @Inject constructor( // BreadcrumbsController.Listener ************************************************************** - override fun onRoomClicked(room: RoomSummary) { - sharedActionViewModel.post(RoomDetailSharedAction.OpenRoom(room.roomId)) + override fun onBreadcrumbClicked(roomId: String) { + sharedActionViewModel.post(RoomDetailSharedAction.SwitchToRoom(roomId)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt index 4b462a05a6..83e9e0fb3f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -59,8 +59,8 @@ class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: B session.rx() .liveBreadcrumbs() .observeOn(Schedulers.computation()) - .execute { asyncRooms -> - copy(asyncRooms = asyncRooms) + .execute { asyncBreadcrumbs -> + copy(asyncBreadcrumbs = asyncBreadcrumbs) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewState.kt index cb00db4c9f..7cc634c8b0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewState.kt @@ -22,5 +22,5 @@ import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.room.model.RoomSummary data class BreadcrumbsViewState( - val asyncRooms: Async> = Uninitialized + val asyncBreadcrumbs: Async> = Uninitialized ) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt index bdc3b59eff..431c9e6395 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt @@ -57,14 +57,7 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable { .observe() .subscribe { sharedAction -> when (sharedAction) { - is RoomDetailSharedAction.OpenRoom -> { - drawerLayout.closeDrawer(GravityCompat.START) - // Do not replace the Fragment if it's the same roomId - if (currentRoomId != sharedAction.roomId) { - currentRoomId = sharedAction.roomId - replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, RoomDetailArgs(sharedAction.roomId)) - } - } + is RoomDetailSharedAction.SwitchToRoom -> switchToRoom(sharedAction) } } .disposeOnDestroy() @@ -72,6 +65,15 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable { drawerLayout.addDrawerListener(drawerListener) } + private fun switchToRoom(switchToRoom: RoomDetailSharedAction.SwitchToRoom) { + drawerLayout.closeDrawer(GravityCompat.START) + // Do not replace the Fragment if it's the same roomId + if (currentRoomId != switchToRoom.roomId) { + currentRoomId = switchToRoom.roomId + replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, RoomDetailArgs(switchToRoom.roomId)) + } + } + override fun onDestroy() { drawerLayout.removeDrawerListener(drawerListener) super.onDestroy() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedAction.kt index 6a88bb1f13..95dd34ebb8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailSharedAction.kt @@ -22,5 +22,5 @@ import im.vector.riotx.core.platform.VectorSharedAction * Supported navigation actions for [RoomDetailActivity] */ sealed class RoomDetailSharedAction : VectorSharedAction { - data class OpenRoom(val roomId: String) : RoomDetailSharedAction() + data class SwitchToRoom(val roomId: String) : RoomDetailSharedAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt index 4107bf01b2..6ffe37cb15 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt @@ -42,7 +42,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri init { // We are requesting a model build directly as the first build of epoxy is on the main thread. - // It avoids to build the the whole list of rooms on the main thread. + // It avoids to build the whole list of rooms on the main thread. requestModelBuild() } diff --git a/vector/src/main/res/layout/fragment_breadcrumbs.xml b/vector/src/main/res/layout/fragment_breadcrumbs.xml index c17b35fdee..22cceadc03 100644 --- a/vector/src/main/res/layout/fragment_breadcrumbs.xml +++ b/vector/src/main/res/layout/fragment_breadcrumbs.xml @@ -5,4 +5,4 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:background="?riotx_background" - tools:listitem="@layout/item_breadcrumbs" /> \ No newline at end of file + tools:listitem="@layout/item_breadcrumbs" /> From 8e3e9876b81152e0ae82756db9075305d02900f8 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 6 Dec 2019 10:28:50 +0100 Subject: [PATCH 13/51] Fixes #739 --- CHANGES.md | 2 +- .../android/internal/session/room/send/LocalEchoEventFactory.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 869038a776..d6e107299f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,7 +11,7 @@ Other changes: - Bugfix 🐛: - - + - When automardown is ON, pills are sent as MD in body (#739) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index b773d1f892..0fed1ca6f5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -78,7 +78,7 @@ internal class LocalEchoEventFactory @Inject constructor( val htmlText = renderer.render(document) if (isFormattedTextPertinent(source, htmlText)) { - return TextContent(source, htmlText) + return TextContent(text.toString(), htmlText) } } else { // Try to detect pills From a41617e8aad41e64c29e03b84976b38a77cdc48c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Dec 2019 22:00:49 +0100 Subject: [PATCH 14/51] Fix lint false positive issue --- vector/src/main/res/layout/item_breadcrumbs.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/res/layout/item_breadcrumbs.xml b/vector/src/main/res/layout/item_breadcrumbs.xml index 4d9616ab1b..f364ad8211 100644 --- a/vector/src/main/res/layout/item_breadcrumbs.xml +++ b/vector/src/main/res/layout/item_breadcrumbs.xml @@ -49,6 +49,7 @@ app:layout_constraintCircleAngle="45" app:layout_constraintCircleRadius="28dp" tools:background="@drawable/bg_unread_highlight" + tools:ignore="MissingConstraints" tools:text="24" tools:visibility="visible" /> @@ -63,6 +64,7 @@ app:layout_constraintCircle="@+id/breadcrumbsImageView" app:layout_constraintCircleAngle="135" app:layout_constraintCircleRadius="28dp" + tools:ignore="MissingConstraints" tools:visibility="visible" /> From 5f540a5b45dd1fe32354ece35c2c00a3bafe6aa0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Dec 2019 23:46:32 +0100 Subject: [PATCH 15/51] Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744) --- CHANGES.md | 2 +- .../android/api/auth/data/LoginFlowResult.kt | 3 +- .../matrix/android/api/failure/Failure.kt | 2 +- .../matrix/android/internal/auth/AuthAPI.kt | 7 ++ .../auth/DefaultAuthenticationService.kt | 74 +++++++++++++++++-- .../android/internal/auth/data/RiotConfig.kt | 28 +++++++ .../internal/network/RetrofitExtensions.kt | 4 + .../vector/riotx/core/error/ErrorFormatter.kt | 10 +++ .../riotx/features/login/LoginViewModel.kt | 2 +- vector/src/main/res/values/strings.xml | 1 + 10 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/RiotConfig.kt diff --git a/CHANGES.md b/CHANGES.md index d6e107299f..4e59c0484f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Support entering a RiotWeb client URL instead of the homeserver URL during connection (#744) Other changes: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/LoginFlowResult.kt index f0d0c61d58..dd0c93a41c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/LoginFlowResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/LoginFlowResult.kt @@ -22,7 +22,8 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowResponse sealed class LoginFlowResult { data class Success( val loginFlowResponse: LoginFlowResponse, - val isLoginAndRegistrationSupported: Boolean + val isLoginAndRegistrationSupported: Boolean, + val homeServerUrl: String ) : LoginFlowResult() object OutdatedHomeserver : LoginFlowResult() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt index 9d42e8388c..4d44e3346b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt @@ -36,7 +36,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString())) object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false"))) // When server send an error, but it cannot be interpreted as a MatrixError - data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody)) + data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody")) data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString())) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt index a1c746a299..306a3846bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/AuthAPI.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Versions import im.vector.matrix.android.internal.auth.data.LoginFlowResponse import im.vector.matrix.android.internal.auth.data.PasswordLoginParams +import im.vector.matrix.android.internal.auth.data.RiotConfig import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed import im.vector.matrix.android.internal.auth.registration.* import im.vector.matrix.android.internal.network.NetworkConstants @@ -31,6 +32,12 @@ import retrofit2.http.* */ internal interface AuthAPI { + /** + * Get a Riot config file + */ + @GET("config.json") + fun getRiotConfig(): Call + /** * Get the version information of the homeserver */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index e7cf999820..b10ed7217f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -16,16 +16,19 @@ package im.vector.matrix.android.internal.auth +import android.net.Uri import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.data.* import im.vector.matrix.android.api.auth.login.LoginWizard import im.vector.matrix.android.api.auth.registration.RegistrationWizard +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.auth.data.LoginFlowResponse +import im.vector.matrix.android.internal.auth.data.RiotConfig import im.vector.matrix.android.internal.auth.db.PendingSessionData import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard @@ -40,6 +43,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import javax.inject.Inject +import javax.net.ssl.HttpsURLConnection internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated private val okHttpClient: Lazy, @@ -84,7 +88,12 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated { if (it is LoginFlowResult.Success) { // The homeserver exists and up to date, keep the config - pendingSessionData = PendingSessionData(homeServerConnectionConfig) + // Homeserver url may have been changed, if it was a Riot url + val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( + homeServerUri = Uri.parse(it.homeServerUrl) + ) + + pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) .also { data -> pendingSessionStore.savePendingSessionData(data) } } callback.onSuccess(it) @@ -97,20 +106,71 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated .toCancelable() } - private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig) = withContext(coroutineDispatchers.io) { + private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { + return withContext(coroutineDispatchers.io) { + val authAPI = buildAuthAPI(homeServerConnectionConfig) + + // First check the homeserver version + runCatching { + executeRequest { + apiCall = authAPI.versions() + } + } + .map { versions -> + // Ok, it seems that the homeserver url is valid + getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString()) + } + .fold( + { + it + }, + { + if (it is Failure.OtherServerError + && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // It's maybe a Riot url? + getRiotLoginFlowInternal(homeServerConnectionConfig) + } else { + throw it + } + } + ) + } + } + + private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val authAPI = buildAuthAPI(homeServerConnectionConfig) - // First check the homeserver version - val versions = executeRequest { - apiCall = authAPI.versions() + // Ok, try to get the config.json file of a RiotWeb client + val riotConfig = executeRequest { + apiCall = authAPI.getRiotConfig() } - if (versions.isSupportedBySdk()) { + if (riotConfig.defaultHomeServerUrl?.isNotBlank() == true) { + // Ok, good sign, we got a default hs url + val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( + homeServerUri = Uri.parse(riotConfig.defaultHomeServerUrl) + ) + + val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) + + val versions = executeRequest { + apiCall = newAuthAPI.versions() + } + + return getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl) + } else { + // Config exists, but we cannot retrieve a default homeserver url + throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) + } + } + + private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult { + return if (versions.isSupportedBySdk()) { // Get the login flow val loginFlowResponse = executeRequest { apiCall = authAPI.getLoginFlows() } - LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk()) + LoginFlowResult.Success(loginFlowResponse, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl) } else { // Not supported LoginFlowResult.OutdatedHomeserver diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/RiotConfig.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/RiotConfig.kt new file mode 100644 index 0000000000..aebcfe0305 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/data/RiotConfig.kt @@ -0,0 +1,28 @@ +/* + * 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.matrix.android.internal.auth.data + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RiotConfig( + // There are plenty of other elements in the file config.json of a RiotWeb client, but for the moment only one is interesting + // Ex: "brand", "branding", etc. + @Json(name = "default_hs_url") + val defaultHomeServerUrl: String? +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index 29b20f9739..fa0b9a1f1c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.network import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonEncodingException import im.vector.matrix.android.api.failure.ConsentNotGivenError import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError @@ -106,6 +107,9 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { } catch (ex: JsonDataException) { // This is not a MatrixError Timber.w("The error returned by the server is not a MatrixError") + } catch (ex: JsonEncodingException) { + // This is not a MatrixError, HTML code? + Timber.w("The error returned by the server is not a MatrixError, probably HTML string") } return Failure.OtherServerError(errorBodyStr, httpCode) diff --git a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt index 621031f166..6f34415d9a 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/core/error/ErrorFormatter.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.riotx.R import im.vector.riotx.core.resources.StringProvider +import java.net.HttpURLConnection import java.net.SocketTimeoutException import java.net.UnknownHostException import javax.inject.Inject @@ -76,6 +77,15 @@ class ErrorFormatter @Inject constructor(private val stringProvider: StringProvi } } } + is Failure.OtherServerError -> { + when (throwable.httpCode) { + HttpURLConnection.HTTP_NOT_FOUND -> + // homeserver not found + stringProvider.getString(R.string.login_error_no_homeserver_found) + else -> + throwable.localizedMessage + } + } else -> throwable.localizedMessage } ?: stringProvider.getString(R.string.unknown_error) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index de76f6b416..00207cbfbf 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -539,7 +539,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi setState { copy( asyncHomeServerLoginFlowRequest = Uninitialized, - homeServerUrl = action.homeServerUrl, + homeServerUrl = data.homeServerUrl, loginMode = loginMode, loginModeSupportedTypes = data.loginFlowResponse.flows.mapNotNull { it.type }.toList() ) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 57e9ca35d6..2e4d04354b 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -284,6 +284,7 @@ Unable to register : email ownership failure Please enter a valid URL This URL is not reachable, please check it + This is not a valid Matrix server address Cannot reach a homeserver at this URL, please check it Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect Mobile From 94afd3e66da4125a540546a5035ad1f9a5663f66 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 7 Dec 2019 11:05:18 +0100 Subject: [PATCH 16/51] Add example of config without default homeserver url --- .../android/internal/auth/DefaultAuthenticationService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt index b10ed7217f..93349f4bbc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/DefaultAuthenticationService.kt @@ -159,7 +159,7 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated return getLoginFlowResult(newAuthAPI, versions, riotConfig.defaultHomeServerUrl) } else { - // Config exists, but we cannot retrieve a default homeserver url + // Config exists, but there is no default homeserver url (ex: https://riot.im/app) throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) } } From 18beef14cfd77979e21860aa529b62ca109f334b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 18:33:23 +0100 Subject: [PATCH 17/51] "ban" event are not rendered correctly (#716) --- CHANGES.md | 1 + .../api/session/room/model/RoomMember.kt | 1 + .../src/main/res/values/strings_RiotX.xml | 2 +- .../timeline/format/NoticeEventFormatter.kt | 27 +++++++------------ 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7e386e0194..0b0cf05a66 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -45,6 +45,7 @@ Bugfix 🐛: - Fix emoji filtering not working - Fix issue of closing Realm in another thread (#725) - Attempt to properly cancel the crypto module when user signs out (#724) + - "ban" event are not rendered correctly (#716) Changes in RiotX 0.8.0 (2019-11-19) =================================================== diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt index aa73727685..2d96cef43d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.events.model.UnsignedData @JsonClass(generateAdapter = true) data class RoomMember( @Json(name = "membership") val membership: Membership, + @Json(name = "reason") val reason: String?, @Json(name = "displayname") val displayName: String? = null, @Json(name = "avatar_url") val avatarUrl: String? = null, @Json(name = "is_direct") val isDirect: Boolean = false, diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index a588bb36fd..7972e63e5a 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -2,7 +2,7 @@ - + %1$s banned %2$s. Reason: %3$s There is no network connection right now diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index a3910664a2..701d58eb2f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -19,14 +19,7 @@ package im.vector.riotx.features.home.room.detail.timeline.format import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent -import im.vector.matrix.android.api.session.room.model.RoomJoinRules -import im.vector.matrix.android.api.session.room.model.RoomJoinRulesContent -import im.vector.matrix.android.api.session.room.model.RoomMember -import im.vector.matrix.android.api.session.room.model.RoomNameContent -import im.vector.matrix.android.api.session.room.model.RoomTopicContent +import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.R @@ -104,8 +97,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active } private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? { - val historyVisibility = event.getClearContent().toModel()?.historyVisibility - ?: return null + val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null val formattedVisibility = when (historyVisibility) { RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) @@ -154,8 +146,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active eventContent?.displayName.isNullOrEmpty() -> stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName) else -> - stringProvider.getString(R.string.notice_display_name_changed_from, - event.senderId, prevEventContent?.displayName, eventContent?.displayName) + stringProvider.getString(R.string.notice_display_name_changed_from, event.senderId, prevEventContent?.displayName, eventContent?.displayName) } displayText.append(displayNameText) } @@ -179,16 +170,14 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { val senderDisplayName = senderName ?: event.senderId - val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: "" + val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: "" return when { Membership.INVITE == eventContent?.membership -> { val selfUserId = sessionHolder.getSafeActiveSession()?.myUserId when { eventContent.thirdPartyInvite != null -> { - val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid - ?: event.stateKey - stringProvider.getString(R.string.notice_room_third_party_registered_invite, - userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName) + val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey + stringProvider.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName) } event.stateKey == selfUserId -> stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) @@ -218,7 +207,9 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active null } Membership.BAN == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) + eventContent.reason?.takeIf { it.isNotBlank() } + ?.let { reason -> stringProvider.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) Membership.KNOCK == eventContent?.membership -> stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) else -> null From d28700e2bf08b6097678209c3d3314457daa1796 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 18:51:56 +0100 Subject: [PATCH 18/51] Add reason for all membership events (https://github.com/matrix-org/matrix-doc/pull/2367) --- CHANGES.md | 1 + .../api/session/room/model/RoomMember.kt | 5 +- .../src/main/res/values/strings_RiotX.xml | 13 +++++- .../timeline/format/NoticeEventFormatter.kt | 46 ++++++++++++++----- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0b0cf05a66..0b8ecbba7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -39,6 +39,7 @@ Improvements 🙌: Other changes: - Fix a small grammatical error when an empty room list is shown. + - Add reason for all membership events (https://github.com/matrix-org/matrix-doc/pull/2367) Bugfix 🐛: - Do not show long click help if only invitation are displayed diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt index 2d96cef43d..e28ba90b52 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt @@ -32,4 +32,7 @@ data class RoomMember( @Json(name = "is_direct") val isDirect: Boolean = false, @Json(name = "third_party_invite") val thirdPartyInvite: Invite? = null, @Json(name = "unsigned") val unsignedData: UnsignedData? = null -) +) { + val safeReason + get() = reason?.takeIf { it.isNotBlank() } +} diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index 7972e63e5a..a22533c6d1 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -2,8 +2,19 @@ + %1$s\'s invitation. Reason: %2$s + %1$s invited %2$s. Reason: %3$s + %1$s invited you. Reason: %2$s + %1$s joined. Reason: %2$s + %1$s left. Reason: %2$s + %1$s rejected the invitation. Reason: %2$s + %1$s kicked %2$s. Reason: %3$s + %1$s unbanned %2$s. Reason: %3$s %1$s banned %2$s. Reason: %3$s - + %1$s sent an invitation to %2$s to join the room. Reason: %3$s + %1$s revoked the invitation for %2$s to join the room. Reason: %3$s + %1$s accepted the invitation for %2$s. Reason: %3$s + %1$s withdrew %2$s\'s invitation. Reason: %3$s There is no network connection right now \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 701d58eb2f..07d240171e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -177,41 +177,63 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active when { eventContent.thirdPartyInvite != null -> { val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey - stringProvider.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName, reason) } + ?: stringProvider.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName) } event.stateKey == selfUserId -> - stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_invite_you_with_reason, senderDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) event.stateKey.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_invite_no_invitee_with_reason, senderDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) else -> - stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) } } Membership.JOIN == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_join, senderDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_join, senderDisplayName) Membership.LEAVE == eventContent?.membership -> // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked return if (event.senderId == event.stateKey) { if (prevEventContent?.membership == Membership.INVITE) { - stringProvider.getString(R.string.notice_room_reject, senderDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_reject_with_reason, senderDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_reject, senderDisplayName) } else { - stringProvider.getString(R.string.notice_room_leave, senderDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_leave, senderDisplayName) } } else if (prevEventContent?.membership == Membership.INVITE) { - stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_withdraw_with_reason, senderDisplayName, targetDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) } else if (prevEventContent?.membership == Membership.JOIN) { - stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) } else if (prevEventContent?.membership == Membership.BAN) { - stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_unban_with_reason, senderDisplayName, targetDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) } else { null } Membership.BAN == eventContent?.membership -> - eventContent.reason?.takeIf { it.isNotBlank() } + eventContent.safeReason ?.let { reason -> stringProvider.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, reason) } ?: stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) Membership.KNOCK == eventContent?.membership -> - stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + eventContent.safeReason + ?.let { reason -> stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) else -> null } } From 872b14373b575f631cce3948ed75b283bd1fe24e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 19:02:16 +0100 Subject: [PATCH 19/51] Better code --- .../timeline/format/NoticeEventFormatter.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 07d240171e..11d9d8bd0c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -171,8 +171,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { val senderDisplayName = senderName ?: event.senderId val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: "" - return when { - Membership.INVITE == eventContent?.membership -> { + return when (eventContent?.membership) { + Membership.INVITE -> { val selfUserId = sessionHolder.getSafeActiveSession()?.myUserId when { eventContent.thirdPartyInvite != null -> { @@ -195,13 +195,13 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active ?: stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) } } - Membership.JOIN == eventContent?.membership -> + Membership.JOIN -> eventContent.safeReason ?.let { reason -> stringProvider.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) } ?: stringProvider.getString(R.string.notice_room_join, senderDisplayName) - Membership.LEAVE == eventContent?.membership -> + Membership.LEAVE -> // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked - return if (event.senderId == event.stateKey) { + if (event.senderId == event.stateKey) { if (prevEventContent?.membership == Membership.INVITE) { eventContent.safeReason ?.let { reason -> stringProvider.getString(R.string.notice_room_reject_with_reason, senderDisplayName, reason) } @@ -226,15 +226,15 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active } else { null } - Membership.BAN == eventContent?.membership -> + Membership.BAN -> eventContent.safeReason ?.let { reason -> stringProvider.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, reason) } ?: stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) - Membership.KNOCK == eventContent?.membership -> + Membership.KNOCK -> eventContent.safeReason ?.let { reason -> stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) } ?: stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) - else -> null + else -> null } } From b9efc9f4bdfc34bcbd1d164eea495b24d3e20fcc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 19:05:57 +0100 Subject: [PATCH 20/51] Ensure user will never see 'null' in a String --- .../room/detail/timeline/format/NoticeEventFormatter.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 11d9d8bd0c..979104110e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -169,7 +169,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active } private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { - val senderDisplayName = senderName ?: event.senderId + val senderDisplayName = senderName ?: event.senderId ?: "" val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: "" return when (eventContent?.membership) { Membership.INVITE -> { @@ -177,9 +177,10 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active when { eventContent.thirdPartyInvite != null -> { val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey + val threePidDisplayName = eventContent.thirdPartyInvite?.displayName ?: "" eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName, reason) } - ?: stringProvider.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName) + ?.let { reason -> stringProvider.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, threePidDisplayName, reason) } + ?: stringProvider.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, threePidDisplayName) } event.stateKey == selfUserId -> eventContent.safeReason From e0e778909dd46b05d68c12dab585a429365a503d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 19:09:10 +0100 Subject: [PATCH 21/51] Better formatting --- .../timeline/format/NoticeEventFormatter.kt | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 979104110e..8f2f885775 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -178,63 +178,74 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active eventContent.thirdPartyInvite != null -> { val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey val threePidDisplayName = eventContent.thirdPartyInvite?.displayName ?: "" - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, threePidDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, threePidDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, threePidDisplayName) } event.stateKey == selfUserId -> - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_invite_you_with_reason, senderDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_invite_you_with_reason, senderDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) event.stateKey.isNullOrEmpty() -> - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_invite_no_invitee_with_reason, senderDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_invite_no_invitee_with_reason, senderDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) else -> - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) } } Membership.JOIN -> - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_join, senderDisplayName) Membership.LEAVE -> // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked if (event.senderId == event.stateKey) { if (prevEventContent?.membership == Membership.INVITE) { - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_reject_with_reason, senderDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_reject_with_reason, senderDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_reject, senderDisplayName) } else { - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_leave, senderDisplayName) } } else if (prevEventContent?.membership == Membership.INVITE) { - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_withdraw_with_reason, senderDisplayName, targetDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_withdraw_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) } else if (prevEventContent?.membership == Membership.JOIN) { - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) } else if (prevEventContent?.membership == Membership.BAN) { - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_unban_with_reason, senderDisplayName, targetDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_unban_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) } else { null } Membership.BAN -> - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, reason) } + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) Membership.KNOCK -> - eventContent.safeReason - ?.let { reason -> stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) } - ?: stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + eventContent.safeReason?.let { reason -> + stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) else -> null } } From 415511f3e08b5e8849e96c27e691d4e017fef59c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 19:17:23 +0100 Subject: [PATCH 22/51] Shortened lines --- .../timeline/format/NoticeEventFormatter.kt | 106 ++++++++---------- 1 file changed, 48 insertions(+), 58 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 8f2f885775..8cdb93a99d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -29,7 +29,7 @@ import timber.log.Timber import javax.inject.Inject class NoticeEventFormatter @Inject constructor(private val sessionHolder: ActiveSessionHolder, - private val stringProvider: StringProvider) { + private val sp: StringProvider) { fun format(timelineEvent: TimelineEvent): CharSequence? { return when (val type = timelineEvent.root.getClearType()) { @@ -77,22 +77,22 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return if (content.name.isNullOrBlank()) { - stringProvider.getString(R.string.notice_room_name_removed, senderName) + sp.getString(R.string.notice_room_name_removed, senderName) } else { - stringProvider.getString(R.string.notice_room_name_changed, senderName, content.name) + sp.getString(R.string.notice_room_name_changed, senderName, content.name) } } private fun formatRoomTombstoneEvent(senderName: String?): CharSequence? { - return stringProvider.getString(R.string.notice_room_update, senderName) + return sp.getString(R.string.notice_room_update, senderName) } private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return if (content.topic.isNullOrEmpty()) { - stringProvider.getString(R.string.notice_room_topic_removed, senderName) + sp.getString(R.string.notice_room_topic_removed, senderName) } else { - stringProvider.getString(R.string.notice_room_topic_changed, senderName, content.topic) + sp.getString(R.string.notice_room_topic_changed, senderName, content.topic) } } @@ -100,12 +100,12 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null val formattedVisibility = when (historyVisibility) { - RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) - RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited) - RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) - RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) + RoomHistoryVisibility.SHARED -> sp.getString(R.string.notice_room_visibility_shared) + RoomHistoryVisibility.INVITED -> sp.getString(R.string.notice_room_visibility_invited) + RoomHistoryVisibility.JOINED -> sp.getString(R.string.notice_room_visibility_joined) + RoomHistoryVisibility.WORLD_READABLE -> sp.getString(R.string.notice_room_visibility_world_readable) } - return stringProvider.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) + return sp.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) } private fun formatCallEvent(event: Event, senderName: String?): CharSequence? { @@ -114,13 +114,13 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active val content = event.getClearContent().toModel() ?: return null val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO return if (isVideoCall) { - stringProvider.getString(R.string.notice_placed_video_call, senderName) + sp.getString(R.string.notice_placed_video_call, senderName) } else { - stringProvider.getString(R.string.notice_placed_voice_call, senderName) + sp.getString(R.string.notice_placed_voice_call, senderName) } } - EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, senderName) - EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, senderName) + EventType.CALL_ANSWER == event.type -> sp.getString(R.string.notice_answered_call, senderName) + EventType.CALL_HANGUP == event.type -> sp.getString(R.string.notice_ended_call, senderName) else -> null } } @@ -142,11 +142,11 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active if (eventContent?.displayName != prevEventContent?.displayName) { val displayNameText = when { prevEventContent?.displayName.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_display_name_set, event.senderId, eventContent?.displayName) + sp.getString(R.string.notice_display_name_set, event.senderId, eventContent?.displayName) eventContent?.displayName.isNullOrEmpty() -> - stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName) + sp.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName) else -> - stringProvider.getString(R.string.notice_display_name_changed_from, event.senderId, prevEventContent?.displayName, eventContent?.displayName) + sp.getString(R.string.notice_display_name_changed_from, event.senderId, prevEventContent?.displayName, eventContent?.displayName) } displayText.append(displayNameText) } @@ -154,15 +154,15 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active if (eventContent?.avatarUrl != prevEventContent?.avatarUrl) { val displayAvatarText = if (displayText.isNotEmpty()) { displayText.append(" ") - stringProvider.getString(R.string.notice_avatar_changed_too) + sp.getString(R.string.notice_avatar_changed_too) } else { - stringProvider.getString(R.string.notice_avatar_url_changed, senderName) + sp.getString(R.string.notice_avatar_url_changed, senderName) } displayText.append(displayAvatarText) } if (displayText.isEmpty()) { displayText.append( - stringProvider.getString(R.string.notice_member_no_changes, senderName) + sp.getString(R.string.notice_member_no_changes, senderName) ) } return displayText.toString() @@ -179,73 +179,63 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey val threePidDisplayName = eventContent.thirdPartyInvite?.displayName ?: "" eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, threePidDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, threePidDisplayName) + sp.getString(R.string.notice_room_third_party_registered_invite_with_reason, userWhoHasAccepted, threePidDisplayName, reason) + } ?: sp.getString(R.string.notice_room_third_party_registered_invite, userWhoHasAccepted, threePidDisplayName) } event.stateKey == selfUserId -> eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_invite_you_with_reason, senderDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) + sp.getString(R.string.notice_room_invite_you_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_invite_you, senderDisplayName) event.stateKey.isNullOrEmpty() -> eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_invite_no_invitee_with_reason, senderDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) + sp.getString(R.string.notice_room_invite_no_invitee_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) else -> eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) + sp.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) } } Membership.JOIN -> eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) + sp.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) } - ?: stringProvider.getString(R.string.notice_room_join, senderDisplayName) + ?: sp.getString(R.string.notice_room_join, senderDisplayName) Membership.LEAVE -> // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked if (event.senderId == event.stateKey) { if (prevEventContent?.membership == Membership.INVITE) { eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_reject_with_reason, senderDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_reject, senderDisplayName) + sp.getString(R.string.notice_room_reject_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_reject, senderDisplayName) } else { eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_leave, senderDisplayName) + sp.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason) + } ?: sp.getString(R.string.notice_room_leave, senderDisplayName) } } else if (prevEventContent?.membership == Membership.INVITE) { eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_withdraw_with_reason, senderDisplayName, targetDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) + sp.getString(R.string.notice_room_withdraw_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) } else if (prevEventContent?.membership == Membership.JOIN) { eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + sp.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) } else if (prevEventContent?.membership == Membership.BAN) { eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_unban_with_reason, senderDisplayName, targetDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) + sp.getString(R.string.notice_room_unban_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName) } else { null } Membership.BAN -> - eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, reason) - } - ?: stringProvider.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) + eventContent.safeReason?.let { + sp.getString(R.string.notice_room_ban_with_reason, senderDisplayName, targetDisplayName, it) + } ?: sp.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName) Membership.KNOCK -> eventContent.safeReason?.let { reason -> - stringProvider.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) - } ?: stringProvider.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) + sp.getString(R.string.notice_room_kick_with_reason, senderDisplayName, targetDisplayName, reason) + } ?: sp.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName) else -> null } } @@ -253,8 +243,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active private fun formatJoinRulesEvent(event: Event, senderName: String?): CharSequence? { val content = event.getClearContent().toModel() ?: return null return when (content.joinRules) { - RoomJoinRules.INVITE -> stringProvider.getString(R.string.room_join_rules_invite, senderName) - RoomJoinRules.PUBLIC -> stringProvider.getString(R.string.room_join_rules_public, senderName) + RoomJoinRules.INVITE -> sp.getString(R.string.room_join_rules_invite, senderName) + RoomJoinRules.PUBLIC -> sp.getString(R.string.room_join_rules_public, senderName) else -> null } } From 03d51281a2fc43ba7852ded0b5d732b2b5cfdaba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 19:18:35 +0100 Subject: [PATCH 23/51] Mistake --- .../home/room/detail/timeline/format/NoticeEventFormatter.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 8cdb93a99d..75100e6c03 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -192,15 +192,14 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active } ?: sp.getString(R.string.notice_room_invite_no_invitee, senderDisplayName) else -> eventContent.safeReason?.let { reason -> - sp.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName, reason) + sp.getString(R.string.notice_room_invite_with_reason, senderDisplayName, targetDisplayName, reason) } ?: sp.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName) } } Membership.JOIN -> eventContent.safeReason?.let { reason -> sp.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) - } - ?: sp.getString(R.string.notice_room_join, senderDisplayName) + } ?: sp.getString(R.string.notice_room_join, senderDisplayName) Membership.LEAVE -> // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked if (event.senderId == event.stateKey) { From 57354cbd69f77612ed1da48590647b1272aea0a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 20:11:19 +0100 Subject: [PATCH 24/51] Add reason to slash commands --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 4 +- .../java/im/vector/matrix/rx/RxSession.kt | 4 +- .../api/session/room/RoomDirectoryService.kt | 2 +- .../android/api/session/room/RoomService.kt | 2 + .../session/room/members/MembershipService.kt | 6 +- .../room/DefaultRoomDirectoryService.kt | 4 +- .../session/room/DefaultRoomService.kt | 4 +- .../android/internal/session/room/RoomAPI.kt | 4 +- .../membership/DefaultMembershipService.kt | 12 ++-- .../room/membership/joining/InviteBody.kt | 3 +- .../room/membership/joining/InviteTask.kt | 5 +- .../room/membership/joining/JoinRoomTask.kt | 3 +- .../room/membership/leaving/LeaveRoomTask.kt | 5 +- .../vector/riotx/features/command/Command.kt | 8 +-- .../riotx/features/command/CommandParser.kt | 72 +++++++++++++------ .../riotx/features/command/ParsedCommand.kt | 12 ++-- .../home/room/detail/RoomDetailViewModel.kt | 8 +-- .../home/room/list/RoomListViewModel.kt | 6 +- .../NotificationBroadcastReceiver.kt | 4 +- .../roomdirectory/RoomDirectoryViewModel.kt | 2 +- .../roompreview/RoomPreviewViewModel.kt | 2 +- 21 files changed, 105 insertions(+), 67 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index 6793d6249d..96f2de9cdc 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -57,8 +57,8 @@ class RxRoom(private val room: Room) { room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it) } - fun joinRoom(viaServers: List = emptyList()): Single = Single.create { - room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it) + fun joinRoom(reason: String?, viaServers: List = emptyList()): Single = Single.create { + room.join(reason, viaServers, MatrixCallbackSingle(it)).toSingle(it) } fun liveEventReadReceipts(eventId: String): Observable> { diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 1964d05a1b..1ef1c8cadb 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -76,8 +76,8 @@ class RxSession(private val session: Session) { session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it) } - fun joinRoom(roomId: String, viaServers: List = emptyList()): Single = Single.create { - session.joinRoom(roomId, viaServers, MatrixCallbackSingle(it)).toSingle(it) + fun joinRoom(roomId: String, reason: String?, viaServers: List = emptyList()): Single = Single.create { + session.joinRoom(roomId, reason, viaServers, MatrixCallbackSingle(it)).toSingle(it) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt index 930320d976..8d03c70efa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt @@ -35,7 +35,7 @@ interface RoomDirectoryService { /** * Join a room by id */ - fun joinRoom(roomId: String, callback: MatrixCallback): Cancelable + fun joinRoom(roomId: String, reason: String?, callback: MatrixCallback): Cancelable /** * Fetches the overall metadata about protocols supported by the homeserver. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 2dbb580a4f..71a9a8e4fa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -35,9 +35,11 @@ interface RoomService { /** * Join a room by id * @param roomId the roomId of the room to join + * @param reason optional reason for joining the room * @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room. */ fun joinRoom(roomId: String, + reason: String?, viaServers: List = emptyList(), callback: MatrixCallback): Cancelable diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index 8d60bee9da..0f487880b7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt @@ -52,16 +52,16 @@ interface MembershipService { /** * Invite a user in the room */ - fun invite(userId: String, callback: MatrixCallback): Cancelable + fun invite(userId: String, reason: String?, callback: MatrixCallback): Cancelable /** * Join the room, or accept an invitation. */ - fun join(viaServers: List = emptyList(), callback: MatrixCallback): Cancelable + fun join(reason: String?, viaServers: List = emptyList(), callback: MatrixCallback): Cancelable /** * Leave the room, or reject an invitation. */ - fun leave(callback: MatrixCallback): Cancelable + fun leave(reason: String?, callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt index 4251a66304..711e2bd97c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt @@ -44,9 +44,9 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu .executeBy(taskExecutor) } - override fun joinRoom(roomId: String, callback: MatrixCallback): Cancelable { + override fun joinRoom(roomId: String, reason: String?, callback: MatrixCallback): Cancelable { return joinRoomTask - .configureWith(JoinRoomTask.Params(roomId)) { + .configureWith(JoinRoomTask.Params(roomId, reason)) { this.callback = callback } .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index be2a588510..22caf76eaf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -96,9 +96,9 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona .executeBy(taskExecutor) } - override fun joinRoom(roomId: String, viaServers: List, callback: MatrixCallback): Cancelable { + override fun joinRoom(roomId: String, reason: String?, viaServers: List, callback: MatrixCallback): Cancelable { return joinRoomTask - .configureWith(JoinRoomTask.Params(roomId, viaServers)) { + .configureWith(JoinRoomTask.Params(roomId, reason, viaServers)) { this.callback = callback } .executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 797dbed31c..40164d1697 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -217,7 +217,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/join") fun join(@Path("roomId") roomId: String, @Query("server_name") viaServers: List, - @Body params: Map): Call + @Body params: Map): Call /** * Leave the given room. @@ -227,7 +227,7 @@ internal interface RoomAPI { */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") fun leave(@Path("roomId") roomId: String, - @Body params: Map): Call + @Body params: Map): Call /** * Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt index 3490fed30f..00c1c2c4ca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt @@ -83,8 +83,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr return result } - override fun invite(userId: String, callback: MatrixCallback): Cancelable { - val params = InviteTask.Params(roomId, userId) + override fun invite(userId: String, reason: String?, callback: MatrixCallback): Cancelable { + val params = InviteTask.Params(roomId, userId, reason) return inviteTask .configureWith(params) { this.callback = callback @@ -92,8 +92,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr .executeBy(taskExecutor) } - override fun join(viaServers: List, callback: MatrixCallback): Cancelable { - val params = JoinRoomTask.Params(roomId, viaServers) + override fun join(reason: String?, viaServers: List, callback: MatrixCallback): Cancelable { + val params = JoinRoomTask.Params(roomId, reason, viaServers) return joinTask .configureWith(params) { this.callback = callback @@ -101,8 +101,8 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr .executeBy(taskExecutor) } - override fun leave(callback: MatrixCallback): Cancelable { - val params = LeaveRoomTask.Params(roomId) + override fun leave(reason: String?, callback: MatrixCallback): Cancelable { + val params = LeaveRoomTask.Params(roomId, reason) return leaveRoomTask .configureWith(params) { this.callback = callback diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteBody.kt index 4529a17ab8..2d72197198 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteBody.kt @@ -21,5 +21,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class InviteBody( - @Json(name = "user_id") val userId: String + @Json(name = "user_id") val userId: String, + @Json(name = "reason") val reason: String? ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt index a41e8d3ca3..6bc453a0f3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/InviteTask.kt @@ -24,7 +24,8 @@ import javax.inject.Inject internal interface InviteTask : Task { data class Params( val roomId: String, - val userId: String + val userId: String, + val reason: String? ) } @@ -32,7 +33,7 @@ internal class DefaultInviteTask @Inject constructor(private val roomAPI: RoomAP override suspend fun execute(params: InviteTask.Params) { return executeRequest { - val body = InviteBody(params.userId) + val body = InviteBody(params.userId, params.reason) apiCall = roomAPI.invite(params.roomId, body) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt index 2555d80209..7304c09d57 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt @@ -32,6 +32,7 @@ import javax.inject.Inject internal interface JoinRoomTask : Task { data class Params( val roomId: String, + val reason: String?, val viaServers: List = emptyList() ) } @@ -43,7 +44,7 @@ internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: Room override suspend fun execute(params: JoinRoomTask.Params) { executeRequest { - apiCall = roomAPI.join(params.roomId, params.viaServers, HashMap()) + apiCall = roomAPI.join(params.roomId, params.viaServers, mapOf("reason" to params.reason)) } val roomId = params.roomId // Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt index be9a421e95..01198c47de 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -23,7 +23,8 @@ import javax.inject.Inject internal interface LeaveRoomTask : Task { data class Params( - val roomId: String + val roomId: String, + val reason: String? ) } @@ -31,7 +32,7 @@ internal class DefaultLeaveRoomTask @Inject constructor(private val roomAPI: Roo override suspend fun execute(params: LeaveRoomTask.Params) { return executeRequest { - apiCall = roomAPI.leave(params.roomId, HashMap()) + apiCall = roomAPI.leave(params.roomId, mapOf("reason" to params.reason)) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index 7d745b925b..9b3ecb3be5 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -27,12 +27,12 @@ import im.vector.riotx.R enum class Command(val command: String, val parameters: String, @StringRes val description: Int) { EMOTE("/me", "", R.string.command_description_emote), BAN_USER("/ban", " [reason]", R.string.command_description_ban_user), - UNBAN_USER("/unban", "", R.string.command_description_unban_user), + UNBAN_USER("/unban", " [reason]", R.string.command_description_unban_user), SET_USER_POWER_LEVEL("/op", " []", R.string.command_description_op_user), RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user), - INVITE("/invite", "", R.string.command_description_invite_user), - JOIN_ROOM("/join", "", R.string.command_description_join_room), - PART("/part", "", R.string.command_description_part_room), + INVITE("/invite", " [reason]", R.string.command_description_invite_user), + JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room), + PART("/part", " [reason]", R.string.command_description_part_room), TOPIC("/topic", "", R.string.command_description_topic), KICK_USER("/kick", " [reason]", R.string.command_description_kick_user), CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick), diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index bc451f8e84..bdcaa4d9b8 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -81,29 +81,52 @@ object CommandParser { ParsedCommand.SendEmote(message) } Command.JOIN_ROOM.command -> { - val roomAlias = textMessage.substring(Command.JOIN_ROOM.command.length).trim() + if (messageParts.size >= 2) { + val roomAlias = messageParts[1] - if (roomAlias.isNotEmpty()) { - ParsedCommand.JoinRoom(roomAlias) + if (roomAlias.isNotEmpty()) { + ParsedCommand.JoinRoom( + roomAlias, + textMessage.substring(Command.JOIN_ROOM.command.length + 1 + roomAlias.length) + .trim() + .takeIf { it.isNotBlank() } + ) + } else { + ParsedCommand.ErrorSyntax(Command.JOIN_ROOM) + } } else { ParsedCommand.ErrorSyntax(Command.JOIN_ROOM) } } Command.PART.command -> { - val roomAlias = textMessage.substring(Command.PART.command.length).trim() + if (messageParts.size >= 2) { + val roomAlias = messageParts[1] - if (roomAlias.isNotEmpty()) { - ParsedCommand.PartRoom(roomAlias) + if (roomAlias.isNotEmpty()) { + ParsedCommand.PartRoom( + roomAlias, + textMessage.substring(Command.PART.command.length + 1 + roomAlias.length) + .trim() + .takeIf { it.isNotBlank() } + ) + } else { + ParsedCommand.ErrorSyntax(Command.PART) + } } else { ParsedCommand.ErrorSyntax(Command.PART) } } Command.INVITE.command -> { - if (messageParts.size == 2) { + if (messageParts.size >= 2) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { - ParsedCommand.Invite(userId) + ParsedCommand.Invite( + userId, + textMessage.substring(Command.INVITE.command.length + 1 + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) } else { ParsedCommand.ErrorSyntax(Command.INVITE) } @@ -114,12 +137,14 @@ object CommandParser { Command.KICK_USER.command -> { if (messageParts.size >= 2) { val userId = messageParts[1] - if (MatrixPatterns.isUserId(userId)) { - val reason = textMessage.substring(Command.KICK_USER.command.length - + 1 - + userId.length).trim() - ParsedCommand.KickUser(userId, reason) + if (MatrixPatterns.isUserId(userId)) { + ParsedCommand.KickUser( + userId, + textMessage.substring(Command.KICK_USER.command.length + 1 + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) } else { ParsedCommand.ErrorSyntax(Command.KICK_USER) } @@ -130,12 +155,14 @@ object CommandParser { Command.BAN_USER.command -> { if (messageParts.size >= 2) { val userId = messageParts[1] - if (MatrixPatterns.isUserId(userId)) { - val reason = textMessage.substring(Command.BAN_USER.command.length - + 1 - + userId.length).trim() - ParsedCommand.BanUser(userId, reason) + if (MatrixPatterns.isUserId(userId)) { + ParsedCommand.BanUser( + userId, + textMessage.substring(Command.BAN_USER.command.length + 1 + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) } else { ParsedCommand.ErrorSyntax(Command.BAN_USER) } @@ -144,11 +171,16 @@ object CommandParser { } } Command.UNBAN_USER.command -> { - if (messageParts.size == 2) { + if (messageParts.size >= 2) { val userId = messageParts[1] if (MatrixPatterns.isUserId(userId)) { - ParsedCommand.UnbanUser(userId) + ParsedCommand.UnbanUser( + userId, + textMessage.substring(Command.UNBAN_USER.command.length + 1 + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) } else { ParsedCommand.ErrorSyntax(Command.UNBAN_USER) } diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt index 89438c8a9d..dd7c0c7e86 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt @@ -34,14 +34,14 @@ sealed class ParsedCommand { // Valid commands: class SendEmote(val message: CharSequence) : ParsedCommand() - class BanUser(val userId: String, val reason: String) : ParsedCommand() - class UnbanUser(val userId: String) : ParsedCommand() + class BanUser(val userId: String, val reason: String?) : ParsedCommand() + class UnbanUser(val userId: String, val reason: String?) : ParsedCommand() class SetUserPowerLevel(val userId: String, val powerLevel: Int) : ParsedCommand() - class Invite(val userId: String) : ParsedCommand() - class JoinRoom(val roomAlias: String) : ParsedCommand() - class PartRoom(val roomAlias: String) : ParsedCommand() + class Invite(val userId: String, val reason: String?) : ParsedCommand() + class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand() + class PartRoom(val roomAlias: String, val reason: String?) : ParsedCommand() class ChangeTopic(val topic: String) : ParsedCommand() - class KickUser(val userId: String, val reason: String) : ParsedCommand() + class KickUser(val userId: String, val reason: String?) : ParsedCommand() class ChangeDisplayName(val displayName: String) : ParsedCommand() class SetMarkdown(val enable: Boolean) : ParsedCommand() object ClearScalarToken : ParsedCommand() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index d0bff31f79..abdcc46f6a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -266,7 +266,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } session.rx() - .joinRoom(roomId, viaServer) + .joinRoom(roomId, null, viaServer) .map { roomId } .execute { copy(tombstoneEventHandling = it) @@ -487,7 +487,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) { _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) - room.invite(invite.userId, object : MatrixCallback { + room.invite(invite.userId, invite.reason, object : MatrixCallback { override fun onSuccess(data: Unit) { _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandResultOk) } @@ -553,11 +553,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleRejectInvite() { - room.leave(object : MatrixCallback {}) + room.leave(null, object : MatrixCallback {}) } private fun handleAcceptInvite() { - room.join(callback = object : MatrixCallback {}) + room.join(null, callback = object : MatrixCallback {}) } private fun handleEditAction(action: RoomDetailAction.EnterEditMode) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt index e5924d9f2a..8e9e76099d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt @@ -123,7 +123,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, ) } - session.getRoom(roomId)?.join(emptyList(), object : MatrixCallback { + session.getRoom(roomId)?.join(null, emptyList(), object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined @@ -158,7 +158,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, ) } - session.getRoom(roomId)?.leave(object : MatrixCallback { + session.getRoom(roomId)?.leave(null, object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data. // Instead, we wait for the room to be rejected @@ -197,7 +197,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) { - session.getRoom(action.roomId)?.leave(object : MatrixCallback { + session.getRoom(action.roomId)?.leave(null, object : MatrixCallback { override fun onFailure(failure: Throwable) { _viewEvents.post(RoomListViewEvents.Failure(failure)) } diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt index 63cd1c5ce6..447137d263 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt @@ -74,14 +74,14 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { private fun handleJoinRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> session.getRoom(roomId) - ?.join(emptyList(), object : MatrixCallback {}) + ?.join(null, emptyList(), object : MatrixCallback {}) } } private fun handleRejectRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> session.getRoom(roomId) - ?.leave(object : MatrixCallback {}) + ?.leave(null, object : MatrixCallback {}) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index 685e1aa282..3f8b6883ab 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -214,7 +214,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: ) } - session.joinRoom(action.roomId, emptyList(), object : MatrixCallback { + session.joinRoom(action.roomId, null, emptyList(), object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 9ffb64556f..42659b2b20 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -97,7 +97,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R ) } - session.joinRoom(state.roomId, emptyList(), object : MatrixCallback { + session.joinRoom(state.roomId, null, emptyList(), object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined From 411afb0bf3efe65ccf15bafbf1287b14a6c02279 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 20:12:55 +0100 Subject: [PATCH 25/51] Add shortcut for command length --- .../java/im/vector/riotx/features/command/Command.kt | 3 +++ .../vector/riotx/features/command/CommandParser.kt | 12 ++++++------ .../features/home/room/detail/RoomDetailFragment.kt | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index 9b3ecb3be5..8b72ffa4a6 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -39,4 +39,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d MARKDOWN("/markdown", "", R.string.command_description_markdown), CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), SPOILER("/spoiler", "", R.string.command_description_spoiler); + + val length + get() = command.length + 1 } diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index bdcaa4d9b8..359f2c1f13 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -87,7 +87,7 @@ object CommandParser { if (roomAlias.isNotEmpty()) { ParsedCommand.JoinRoom( roomAlias, - textMessage.substring(Command.JOIN_ROOM.command.length + 1 + roomAlias.length) + textMessage.substring(Command.JOIN_ROOM.length + roomAlias.length) .trim() .takeIf { it.isNotBlank() } ) @@ -105,7 +105,7 @@ object CommandParser { if (roomAlias.isNotEmpty()) { ParsedCommand.PartRoom( roomAlias, - textMessage.substring(Command.PART.command.length + 1 + roomAlias.length) + textMessage.substring(Command.PART.length + roomAlias.length) .trim() .takeIf { it.isNotBlank() } ) @@ -123,7 +123,7 @@ object CommandParser { if (MatrixPatterns.isUserId(userId)) { ParsedCommand.Invite( userId, - textMessage.substring(Command.INVITE.command.length + 1 + userId.length) + textMessage.substring(Command.INVITE.length + userId.length) .trim() .takeIf { it.isNotBlank() } ) @@ -141,7 +141,7 @@ object CommandParser { if (MatrixPatterns.isUserId(userId)) { ParsedCommand.KickUser( userId, - textMessage.substring(Command.KICK_USER.command.length + 1 + userId.length) + textMessage.substring(Command.KICK_USER.length + userId.length) .trim() .takeIf { it.isNotBlank() } ) @@ -159,7 +159,7 @@ object CommandParser { if (MatrixPatterns.isUserId(userId)) { ParsedCommand.BanUser( userId, - textMessage.substring(Command.BAN_USER.command.length + 1 + userId.length) + textMessage.substring(Command.BAN_USER.length + userId.length) .trim() .takeIf { it.isNotBlank() } ) @@ -177,7 +177,7 @@ object CommandParser { if (MatrixPatterns.isUserId(userId)) { ParsedCommand.UnbanUser( userId, - textMessage.substring(Command.UNBAN_USER.command.length + 1 + userId.length) + textMessage.substring(Command.UNBAN_USER.length + userId.length) .trim() .takeIf { it.isNotBlank() } ) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index d50b0c9f68..19ba1a85ce 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -1181,7 +1181,7 @@ class RoomDetailFragment @Inject constructor( && userId == session.myUserId) { // Empty composer, current user: start an emote composerLayout.composerEditText.setText(Command.EMOTE.command + " ") - composerLayout.composerEditText.setSelection(Command.EMOTE.command.length + 1) + composerLayout.composerEditText.setSelection(Command.EMOTE.length) } else { val roomMember = roomDetailViewModel.getMember(userId) // TODO move logic outside of fragment From 6d82ac7c5968ca608e6be35bb78f2f037c930346 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Dec 2019 20:22:04 +0100 Subject: [PATCH 26/51] Add default param values --- .../src/main/java/im/vector/matrix/rx/RxRoom.kt | 3 ++- .../src/main/java/im/vector/matrix/rx/RxSession.kt | 4 +++- .../android/api/session/room/RoomDirectoryService.kt | 8 ++++++-- .../matrix/android/api/session/room/RoomService.kt | 8 +++++--- .../api/session/room/members/MembershipService.kt | 11 ++++++++--- .../features/home/room/detail/RoomDetailViewModel.kt | 9 +++++---- .../features/home/room/list/RoomListViewModel.kt | 2 +- .../notifications/NotificationBroadcastReceiver.kt | 4 ++-- .../features/roomdirectory/RoomDirectoryViewModel.kt | 2 +- .../roomdirectory/roompreview/RoomPreviewViewModel.kt | 2 +- 10 files changed, 34 insertions(+), 19 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index 96f2de9cdc..bc0a866117 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -57,7 +57,8 @@ class RxRoom(private val room: Room) { room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it) } - fun joinRoom(reason: String?, viaServers: List = emptyList()): Single = Single.create { + fun joinRoom(reason: String? = null, + viaServers: List = emptyList()): Single = Single.create { room.join(reason, viaServers, MatrixCallbackSingle(it)).toSingle(it) } diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 1ef1c8cadb..5a42dbb804 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -76,7 +76,9 @@ class RxSession(private val session: Session) { session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it) } - fun joinRoom(roomId: String, reason: String?, viaServers: List = emptyList()): Single = Single.create { + fun joinRoom(roomId: String, + reason: String? = null, + viaServers: List = emptyList()): Single = Single.create { session.joinRoom(roomId, reason, viaServers, MatrixCallbackSingle(it)).toSingle(it) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt index 8d03c70efa..c0e413f83b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt @@ -30,12 +30,16 @@ interface RoomDirectoryService { /** * Get rooms from directory */ - fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, callback: MatrixCallback): Cancelable + fun getPublicRooms(server: String?, + publicRoomsParams: PublicRoomsParams, + callback: MatrixCallback): Cancelable /** * Join a room by id */ - fun joinRoom(roomId: String, reason: String?, callback: MatrixCallback): Cancelable + fun joinRoom(roomId: String, + reason: String? = null, + callback: MatrixCallback): Cancelable /** * Fetches the overall metadata about protocols supported by the homeserver. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 71a9a8e4fa..98abce5898 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -30,7 +30,8 @@ interface RoomService { /** * Create a room asynchronously */ - fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable + fun createRoom(createRoomParams: CreateRoomParams, + callback: MatrixCallback): Cancelable /** * Join a room by id @@ -39,7 +40,7 @@ interface RoomService { * @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room. */ fun joinRoom(roomId: String, - reason: String?, + reason: String? = null, viaServers: List = emptyList(), callback: MatrixCallback): Cancelable @@ -71,5 +72,6 @@ interface RoomService { /** * Mark all rooms as read */ - fun markAllAsRead(roomIds: List, callback: MatrixCallback): Cancelable + fun markAllAsRead(roomIds: List, + callback: MatrixCallback): Cancelable } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index 0f487880b7..b750c5347e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt @@ -52,16 +52,21 @@ interface MembershipService { /** * Invite a user in the room */ - fun invite(userId: String, reason: String?, callback: MatrixCallback): Cancelable + fun invite(userId: String, + reason: String? = null, + callback: MatrixCallback): Cancelable /** * Join the room, or accept an invitation. */ - fun join(reason: String?, viaServers: List = emptyList(), callback: MatrixCallback): Cancelable + fun join(reason: String? = null, + viaServers: List = emptyList(), + callback: MatrixCallback): Cancelable /** * Leave the room, or reject an invitation. */ - fun leave(reason: String?, callback: MatrixCallback): Cancelable + fun leave(reason: String? = null, + callback: MatrixCallback): Cancelable } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index abdcc46f6a..00a31db455 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -200,9 +200,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro invisibleEventsObservable.accept(action) } - fun getMember(userId: String) : RoomMember? { - return room.getRoomMember(userId) + fun getMember(userId: String): RoomMember? { + return room.getRoomMember(userId) } + /** * Convert a send mode to a draft and save the draft */ @@ -266,7 +267,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } session.rx() - .joinRoom(roomId, null, viaServer) + .joinRoom(roomId, viaServers = viaServer) .map { roomId } .execute { copy(tombstoneEventHandling = it) @@ -557,7 +558,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleAcceptInvite() { - room.join(null, callback = object : MatrixCallback {}) + room.join(callback = object : MatrixCallback {}) } private fun handleEditAction(action: RoomDetailAction.EnterEditMode) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt index 8e9e76099d..a9ea831723 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt @@ -123,7 +123,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, ) } - session.getRoom(roomId)?.join(null, emptyList(), object : MatrixCallback { + session.getRoom(roomId)?.join(callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt index 447137d263..c9dc131b42 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt @@ -74,14 +74,14 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { private fun handleJoinRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> session.getRoom(roomId) - ?.join(null, emptyList(), object : MatrixCallback {}) + ?.join(callback = object : MatrixCallback {}) } } private fun handleRejectRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> session.getRoom(roomId) - ?.leave(null, object : MatrixCallback {}) + ?.leave(callback = object : MatrixCallback {}) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index 3f8b6883ab..d89f0e2b99 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -214,7 +214,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: ) } - session.joinRoom(action.roomId, null, emptyList(), object : MatrixCallback { + session.joinRoom(action.roomId, callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 42659b2b20..54c86537d2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -97,7 +97,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R ) } - session.joinRoom(state.roomId, null, emptyList(), object : MatrixCallback { + session.joinRoom(state.roomId, callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined From 2f26f4b8bb46648bb86d58c45f6e2c652a0df611 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 09:26:14 +0100 Subject: [PATCH 27/51] Add default value (fix test compilation issue) --- .../vector/matrix/android/api/session/room/model/RoomMember.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt index e28ba90b52..6a4d8e3c94 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMember.kt @@ -26,7 +26,7 @@ import im.vector.matrix.android.api.session.events.model.UnsignedData @JsonClass(generateAdapter = true) data class RoomMember( @Json(name = "membership") val membership: Membership, - @Json(name = "reason") val reason: String?, + @Json(name = "reason") val reason: String? = null, @Json(name = "displayname") val displayName: String? = null, @Json(name = "avatar_url") val avatarUrl: String? = null, @Json(name = "is_direct") val isDirect: Boolean = false, From ac75fe12bfa0c770db13226672dd8f6177c3ba09 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Dec 2019 09:53:18 +0100 Subject: [PATCH 28/51] Will be merged for next release --- CHANGES.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0b8ecbba7a..60950d9d87 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Other changes: - - + - Add reason for all membership events (https://github.com/matrix-org/matrix-doc/pull/2367) Bugfix 🐛: - When automardown is ON, pills are sent as MD in body (#739) @@ -17,7 +17,7 @@ Translations 🗣: - Build 🧱: - - + - "ban" event are not rendered correctly (#716) Changes in RiotX 0.9.1 (2019-12-05) =================================================== @@ -39,14 +39,12 @@ Improvements 🙌: Other changes: - Fix a small grammatical error when an empty room list is shown. - - Add reason for all membership events (https://github.com/matrix-org/matrix-doc/pull/2367) Bugfix 🐛: - Do not show long click help if only invitation are displayed - Fix emoji filtering not working - Fix issue of closing Realm in another thread (#725) - Attempt to properly cancel the crypto module when user signs out (#724) - - "ban" event are not rendered correctly (#716) Changes in RiotX 0.8.0 (2019-11-19) =================================================== From c714266a8122b3f496ef21cffe947c21b5426dd2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Dec 2019 12:00:29 +0100 Subject: [PATCH 29/51] Fix crash reported by the PlayStore. NullPointerException: at im.vector.riotx.features.home.room.detail.RoomDetailFragment.updateJumpToReadMarkerViewVisibility (RoomDetailFragment.kt:524) Also properly cleanup model build listener --- .../home/room/detail/RoomDetailFragment.kt | 55 +++++++++++-------- .../home/room/list/RoomListFragment.kt | 11 ++-- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 19ba1a85ce..27edc6bc5d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -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 @@ -193,6 +194,8 @@ class RoomDetailFragment @Inject constructor( private lateinit var sharedActionViewModel: MessageSharedActionViewModel private lateinit var layoutManager: LinearLayoutManager + private lateinit var modelBuildListener: OnModelBuildFinishedListener + private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var keyboardStateUtils: KeyboardStateUtils @@ -286,8 +289,9 @@ class RoomDetailFragment @Inject constructor( } override fun onDestroyView() { - super.onDestroyView() + timelineEventController.removeModelBuildListener(modelBuildListener) recyclerView.adapter = null + super.onDestroyView() } override fun onDestroy() { @@ -470,13 +474,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,37 +526,41 @@ 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 } } private fun updateJumpToBottomViewVisibility() { debouncer.debounce("jump_to_bottom_visibility", 250, Runnable { - Timber.v("First visible: ${layoutManager.findFirstCompletelyVisibleItemPosition()}") - if (layoutManager.findFirstVisibleItemPosition() != 0) { - jumpToBottomView.show() - } else { - jumpToBottomView.hide() + if (isAdded) { + Timber.v("First visible: ${layoutManager.findFirstCompletelyVisibleItemPosition()}") + if (layoutManager.findFirstVisibleItemPosition() != 0) { + jumpToBottomView.show() + } else { + jumpToBottomView.hide() + } } }) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 04d1802264..0fab14867c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -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 @@ -38,10 +39,9 @@ import im.vector.riotx.core.error.ErrorFormatter 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 +65,7 @@ class RoomListFragment @Inject constructor( ) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener { + private lateinit var modelBuildListener: OnModelBuildFinishedListener private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel private val roomListParams: RoomListParams by args() private val roomListViewModel: RoomListViewModel by fragmentViewModel() @@ -118,8 +119,9 @@ class RoomListFragment @Inject constructor( } override fun onDestroyView() { - super.onDestroyView() + roomController.removeModelBuildListener(modelBuildListener) roomListView.adapter = null + super.onDestroyView() } private fun openSelectedRoom(event: RoomListViewEvents.SelectRoom) { @@ -198,7 +200,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 } From dbd4525404e67719449f93c90279430107cc76bf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Dec 2019 14:39:19 +0100 Subject: [PATCH 30/51] Make sure unhandled Rx error does not crash the app in production --- .../java/im/vector/riotx/VectorApplication.kt | 5 +-- .../main/java/im/vector/riotx/core/rx/Rx.kt | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/rx/Rx.kt diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt index 5ca888fc2e..875e6c417a 100644 --- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt @@ -42,6 +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.rx.setupRxPlugin import im.vector.riotx.core.utils.initKnownEmojiHashSet import im.vector.riotx.features.configuration.VectorConfiguration import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks @@ -55,8 +56,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 { @@ -87,6 +87,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. vectorComponent = DaggerVectorComponent.factory().create(this) vectorComponent.inject(this) vectorUncaughtExceptionHandler.activate(this) + setupRxPlugin() if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) diff --git a/vector/src/main/java/im/vector/riotx/core/rx/Rx.kt b/vector/src/main/java/im/vector/riotx/core/rx/Rx.kt new file mode 100644 index 0000000000..89de9030dc --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/rx/Rx.kt @@ -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 + } + } +} From 109c1fe482495f7b97bbfdaf474c91609321faf5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 6 Dec 2019 14:42:41 +0100 Subject: [PATCH 31/51] Cleanup --- vector/src/main/java/im/vector/riotx/VectorApplication.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt index 875e6c417a..f3043fbec8 100644 --- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt @@ -79,8 +79,6 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. lateinit var vectorComponent: VectorComponent private var fontThreadHandler: Handler? = null -// var slowMode = false - override fun onCreate() { super.onCreate() appContext = this From 9a01b4ace95af9e3376d9971f4baad19b4fffb9c Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2019 17:08:50 +0100 Subject: [PATCH 32/51] Make it through bunch of classes removing potential leaks --- .../content/ContentUploadStateTracker.kt | 2 ++ .../DefaultContentUploadStateTracker.kt | 4 ++++ .../session/room/timeline/DefaultTimeline.kt | 11 +++++---- .../debug/sas/DebugSasEmojiActivity.kt | 14 +++++++---- .../riotx/core/platform/VectorBaseFragment.kt | 1 + .../settings/KeysBackupSettingsFragment.kt | 12 +++++++--- .../CreateDirectRoomDirectoryUsersFragment.kt | 11 ++++++++- .../CreateDirectRoomKnownUsersFragment.kt | 10 +++++++- .../features/home/group/GroupListFragment.kt | 13 +++++++++-- .../home/room/detail/RoomDetailFragment.kt | 20 ++++++++-------- .../home/room/detail/RoomDetailViewModel.kt | 2 +- .../timeline/TimelineEventController.kt | 9 +++++++- .../helper/ContentUploadStateTrackerBinder.kt | 13 +++++++++-- .../helper/TimelineMediaSizeProvider.kt | 15 ++++++------ .../home/room/list/RoomListFragment.kt | 5 +++- .../login/terms/LoginTermsFragment.kt | 12 ++++++++-- .../reactions/EmojiSearchResultFragment.kt | 18 ++++++++++----- .../roomdirectory/PublicRoomsFragment.kt | 23 +++++++++---------- .../createroom/CreateRoomFragment.kt | 10 +++++--- .../picker/RoomDirectoryPickerFragment.kt | 10 +++++--- .../VectorSettingsIgnoredUsersFragment.kt | 14 ++++++++--- .../settings/push/PushGatewaysFragment.kt | 22 +++++++++++------- .../settings/push/PushRulesFragment.kt | 22 +++++++++++------- .../layout/fragment_create_direct_room.xml | 2 +- ...ent_create_direct_room_directory_users.xml | 2 +- .../main/res/layout/fragment_create_room.xml | 2 +- ...poxy.xml => fragment_generic_recycler.xml} | 4 ++-- .../main/res/layout/fragment_group_list.xml | 4 ++-- .../layout/fragment_keys_backup_settings.xml | 2 +- .../main/res/layout/fragment_login_terms.xml | 2 +- .../main/res/layout/fragment_public_rooms.xml | 2 +- .../layout/fragment_room_directory_picker.xml | 2 +- 32 files changed, 202 insertions(+), 93 deletions(-) rename vector/src/main/res/layout/{fragment_generic_recycler_epoxy.xml => fragment_generic_recycler.xml} (86%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUploadStateTracker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUploadStateTracker.kt index 540b164aa5..dedf9a83cf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUploadStateTracker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUploadStateTracker.kt @@ -22,6 +22,8 @@ interface ContentUploadStateTracker { fun untrack(key: String, updateListener: UpdateListener) + fun clear() + interface UpdateListener { fun onUpdate(state: State) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUploadStateTracker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUploadStateTracker.kt index 68f48d20db..66a8341801 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUploadStateTracker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUploadStateTracker.kt @@ -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) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index b83240a681..693855edbc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -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) } } diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt index ba1c47c08c..17a78f1c6a 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt @@ -18,18 +18,24 @@ package im.vector.riotx.features.debug.sas import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager import im.vector.matrix.android.api.crypto.getAllVerificationEmojis import im.vector.riotx.R -import kotlinx.android.synthetic.main.fragment_generic_recycler_epoxy.* +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.adapter = controller.adapter + recyclerView.layoutManager = LinearLayoutManager(this) controller.setData(SasState(getAllVerificationEmojis())) } + + override fun onDestroy() { + recyclerView.adapter = null + super.onDestroy() + } } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index 9f94c15edd..924cb6c7bc 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -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?) { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt index 9994ee5002..a00f71e5f8 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.crypto.keysbackup.settings import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog +import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R @@ -37,12 +38,17 @@ class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSetti override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - keysBackupSettingsRecyclerView.setController(keysBackupSettingsRecyclerViewController) - + keysBackupSettingsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) + keysBackupSettingsRecyclerView.adapter = keysBackupSettingsRecyclerViewController.adapter keysBackupSettingsRecyclerViewController.listener = this } + override fun onDestroyView() { + keysBackupSettingsRecyclerViewController.listener = null + keysBackupSettingsRecyclerView.adapter = null + super.onDestroyView() + } + override fun invalidate() = withState(viewModel) { state -> keysBackupSettingsRecyclerViewController.setData(state) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt index 59f31ec2ee..575940a376 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt @@ -18,6 +18,8 @@ package im.vector.riotx.features.home.createdirect import android.os.Bundle import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.jakewharton.rxbinding3.widget.textChanges @@ -48,10 +50,17 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor( setupCloseView() } + override fun onDestroyView() { + recyclerView.adapter = null + directRoomController.callback = null + super.onDestroyView() + } + private fun setupRecyclerView() { recyclerView.setHasFixedSize(true) directRoomController.callback = this - recyclerView.setController(directRoomController) + recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + recyclerView.adapter = directRoomController.adapter } private fun setupSearchByMatrixIdView() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt index 12019fa39e..bad322292b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt @@ -24,6 +24,7 @@ import android.view.MenuItem import android.view.View import android.widget.ScrollView import androidx.core.view.size +import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.chip.Chip @@ -67,6 +68,12 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( } } + override fun onDestroyView() { + knownUsersController.callback = null + recyclerView.adapter = null + super.onDestroyView() + } + override fun onPrepareOptionsMenu(menu: Menu) { withState(viewModel) { val createMenuItem = menu.findItem(R.id.action_create_direct_room) @@ -98,7 +105,8 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( // 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.layoutManager = LinearLayoutManager(requireContext()) + recyclerView.adapter = knownUsersController.adapter } private fun setupFilterView() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt index 39f8c17f05..5bc0c0f61d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt @@ -18,6 +18,8 @@ package im.vector.riotx.features.home.group import android.os.Bundle import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel @@ -45,14 +47,21 @@ 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.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) + groupListView.adapter = groupController.adapter viewModel.subscribe { renderState(it) } viewModel.openGroupLiveData.observeEvent(this) { sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) } } + override fun onDestroyView() { + groupController.callback = null + groupListView.adapter = null + super.onDestroyView() + } + private fun renderState(state: GroupListViewState) { when (state.asyncGroups) { is Incomplete -> stateView.state = StateView.State.Loading diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 27edc6bc5d..0d869048fb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -194,7 +194,7 @@ class RoomDetailFragment @Inject constructor( private lateinit var sharedActionViewModel: MessageSharedActionViewModel private lateinit var layoutManager: LinearLayoutManager - private lateinit var modelBuildListener: OnModelBuildFinishedListener + private var modelBuildListener: OnModelBuildFinishedListener? = null private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var keyboardStateUtils: KeyboardStateUtils @@ -289,14 +289,16 @@ class RoomDetailFragment @Inject constructor( } override fun onDestroyView() { + timelineEventController.callback = null timelineEventController.removeModelBuildListener(modelBuildListener) + modelBuildListener = null + debouncer.cancelAll() recyclerView.adapter = null super.onDestroyView() } override fun onDestroy() { roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState) - debouncer.cancelAll() super.onDestroy() } @@ -481,7 +483,7 @@ class RoomDetailFragment @Inject constructor( updateJumpToReadMarkerViewVisibility() updateJumpToBottomViewVisibility() } - timelineEventController.addModelBuildListener { modelBuildListener } + timelineEventController.addModelBuildListener(modelBuildListener) recyclerView.adapter = timelineEventController.adapter recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { @@ -554,13 +556,11 @@ class RoomDetailFragment @Inject constructor( private fun updateJumpToBottomViewVisibility() { debouncer.debounce("jump_to_bottom_visibility", 250, Runnable { - if (isAdded) { - Timber.v("First visible: ${layoutManager.findFirstCompletelyVisibleItemPosition()}") - if (layoutManager.findFirstVisibleItemPosition() != 0) { - jumpToBottomView.show() - } else { - jumpToBottomView.hide() - } + Timber.v("First visible: ${layoutManager.findFirstCompletelyVisibleItemPosition()}") + if (layoutManager.findFirstVisibleItemPosition() != 0) { + jumpToBottomView.show() + } else { + jumpToBottomView.hide() } }) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 00a31db455..e7a18753cd 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -863,7 +863,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro override fun onCleared() { timeline.dispose() - timeline.removeAllListeners() + timeline.removeListener(this) super.onCleared() } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index 326e19c431..83f9163496 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -44,7 +44,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 +209,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_() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt index 3fdce63668..bbc3474e1e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt @@ -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,20 @@ 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, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt index 0d11cf8a31..fc3ae98f5c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt @@ -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? = null fun getMaxSize(): Pair { @@ -31,17 +32,17 @@ class TimelineMediaSizeProvider @Inject constructor() { } private fun computeMaxSize(): Pair { - 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) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 0fab14867c..334f8b3417 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -65,7 +65,7 @@ class RoomListFragment @Inject constructor( ) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener { - private lateinit var modelBuildListener: OnModelBuildFinishedListener + private var modelBuildListener: OnModelBuildFinishedListener? = null private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel private val roomListParams: RoomListParams by args() private val roomListViewModel: RoomListViewModel by fragmentViewModel() @@ -120,7 +120,10 @@ class RoomListFragment @Inject constructor( override fun onDestroyView() { roomController.removeModelBuildListener(modelBuildListener) + modelBuildListener = null roomListView.adapter = null + roomController.listener = null + createChatFabMenu.listener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt index 08110f3b33..b50e7cfdcd 100755 --- a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.os.Parcelable import android.view.View import androidx.appcompat.app.AlertDialog +import androidx.recyclerview.widget.LinearLayoutManager import butterknife.OnClick import com.airbnb.mvrx.args import im.vector.riotx.R @@ -55,8 +56,9 @@ class LoginTermsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - loginTermsPolicyList.setController(policyController) + loginTermsPolicyList.setHasFixedSize(true) + loginTermsPolicyList.layoutManager = LinearLayoutManager(requireContext()) + loginTermsPolicyList.adapter = policyController.adapter policyController.listener = this val list = ArrayList() @@ -69,6 +71,12 @@ class LoginTermsFragment @Inject constructor( loginTermsViewState = LoginTermsViewState(list) } + override fun onDestroyView() { + loginTermsPolicyList.adapter = null + policyController.listener = null + super.onDestroyView() + } + private fun renderState() { policyController.setData(loginTermsViewState.localizedFlowDataLoginTermsChecked) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt index e5b46c2176..bef8a65542 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt @@ -25,14 +25,14 @@ import com.airbnb.mvrx.withState import im.vector.riotx.R 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() { - override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy + override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler val viewModel: EmojiSearchResultViewModel by activityViewModel() @@ -50,10 +50,16 @@ class EmojiSearchResultFragment @Inject constructor( } val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - epoxyRecyclerView.layoutManager = lmgr - val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, lmgr.orientation) - epoxyRecyclerView.addItemDecoration(dividerItemDecoration) - epoxyRecyclerView.setController(epoxyController) + recyclerView.layoutManager = lmgr + val dividerItemDecoration = DividerItemDecoration(recyclerView.context, lmgr.orientation) + recyclerView.addItemDecoration(dividerItemDecoration) + recyclerView.adapter = epoxyController.adapter + } + + override fun onDestroyView() { + epoxyController.listener = null + recyclerView.adapter = null + super.onDestroyView() } override fun invalidate() = withState(viewModel) { state -> diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index b41c563256..109364194d 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -62,6 +62,9 @@ class PublicRoomsFragment @Inject constructor( it.setDisplayHomeAsUpEnabled(true) } + sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) + setupRecyclerView() + publicRoomsFilter.queryTextChanges() .debounce(500, TimeUnit.MILLISECONDS) .subscribeBy { @@ -79,6 +82,12 @@ class PublicRoomsFragment @Inject constructor( } } + override fun onDestroyView() { + publicRoomsController.callback = null + publicRoomsList.adapter = null + super.onDestroyView() + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.menu_room_directory_change_protocol -> { @@ -90,22 +99,12 @@ 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.layoutManager = LinearLayoutManager(context) publicRoomsController.callback = this - - publicRoomsList.setController(publicRoomsController) + publicRoomsList.adapter = publicRoomsController.adapter } override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt index 0dec14f50e..95d9c198ee 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -50,6 +50,12 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C } } + override fun onDestroyView() { + createRoomForm.adapter = null + createRoomController.listener = null + super.onDestroyView() + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_create_room -> { @@ -63,11 +69,9 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context) - createRoomForm.layoutManager = layoutManager createRoomController.listener = this - - createRoomForm.setController(createRoomController) + createRoomForm.adapter = createRoomController.adapter } override fun onNameChange(newName: String) { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index ce7a57deba..cc56de7cd7 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -60,6 +60,12 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie setupRecyclerView() } + override fun onDestroyView() { + roomDirectoryPickerList.adapter = null + roomDirectoryPickerController.callback = null + super.onDestroyView() + } + override fun getMenuRes() = R.menu.menu_directory_server_picker override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -74,11 +80,9 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context) - roomDirectoryPickerList.layoutManager = layoutManager roomDirectoryPickerController.callback = this - - roomDirectoryPickerList.setController(roomDirectoryPickerController) + roomDirectoryPickerList.adapter = roomDirectoryPickerController.adapter } override fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData) { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 11e473ae24..de267047b9 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading import com.airbnb.mvrx.fragmentViewModel @@ -29,7 +30,7 @@ import im.vector.riotx.core.error.ErrorFormatter 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 +40,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 +50,19 @@ 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.layoutManager = LinearLayoutManager(requireContext()) + recyclerView.adapter = ignoredUsersController.adapter ignoredUsersViewModel.requestErrorLiveData.observeEvent(this) { displayErrorDialog(it) } } + override fun onDestroyView() { + ignoredUsersController.callback = null + recyclerView.adapter = null + super.onDestroyView() + } + override fun onResume() { super.onResume() diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt index ea23ba2583..e35ec6b5ff 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.settings.push import android.os.Bundle +import android.view.View import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -28,7 +29,7 @@ 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 @@ -36,7 +37,7 @@ class PushGatewaysFragment @Inject constructor( val pushGatewaysViewModelFactory: PushGatewaysViewModel.Factory ) : VectorBaseFragment() { - override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler_epoxy + override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler private val viewModel: PushGatewaysViewModel by fragmentViewModel(PushGatewaysViewModel::class) private val epoxyController by lazy { PushGateWayController(StringProvider(requireContext().resources)) } @@ -46,14 +47,19 @@ class PushGatewaysFragment @Inject constructor( (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_notifications_targets) } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - epoxyRecyclerView.layoutManager = lmgr - val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, + recyclerView.layoutManager = lmgr + val dividerItemDecoration = DividerItemDecoration(recyclerView.context, lmgr.orientation) - epoxyRecyclerView.addItemDecoration(dividerItemDecoration) - epoxyRecyclerView.setController(epoxyController) + recyclerView.addItemDecoration(dividerItemDecoration) + recyclerView.adapter = epoxyController.adapter + } + + override fun onDestroyView() { + recyclerView.adapter = null + super.onDestroyView() } override fun invalidate() = withState(viewModel) { state -> diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt index 5e14053d0b..52a5928e5d 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.settings.push import android.os.Bundle +import android.view.View import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -27,12 +28,12 @@ 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(): Int = R.layout.fragment_generic_recycler private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class) @@ -43,14 +44,19 @@ class PushRulesFragment : VectorBaseFragment() { (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_rules) } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - epoxyRecyclerView.layoutManager = lmgr - val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, + recyclerView.layoutManager = lmgr + val dividerItemDecoration = DividerItemDecoration(recyclerView.context, lmgr.orientation) - epoxyRecyclerView.addItemDecoration(dividerItemDecoration) - epoxyRecyclerView.setController(epoxyController) + recyclerView.addItemDecoration(dividerItemDecoration) + recyclerView.adapter = epoxyController.adapter + } + + override fun onDestroyView() { + recyclerView.adapter = null + super.onDestroyView() } override fun invalidate() = withState(viewModel) { state -> diff --git a/vector/src/main/res/layout/fragment_create_direct_room.xml b/vector/src/main/res/layout/fragment_create_direct_room.xml index f8450d1e6e..5ad2da1032 100644 --- a/vector/src/main/res/layout/fragment_create_direct_room.xml +++ b/vector/src/main/res/layout/fragment_create_direct_room.xml @@ -122,7 +122,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/createDirectRoomFilterDivider" /> - - - - - - - - - Date: Mon, 9 Dec 2019 17:40:52 +0100 Subject: [PATCH 33/51] Ensure we will not use EpoxyRecyclerView as a View anymore --- tools/check/forbidden_strings_in_resources.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/check/forbidden_strings_in_resources.txt b/tools/check/forbidden_strings_in_resources.txt index 435a81e3ba..17ae015265 100644 --- a/tools/check/forbidden_strings_in_resources.txt +++ b/tools/check/forbidden_strings_in_resources.txt @@ -81,4 +81,7 @@ layout_constraintLeft_ ### Will crash on API < 21. Use ?colorAccent instead \?android:colorAccent -\?android:attr/colorAccent \ No newline at end of file +\?android:attr/colorAccent + +### Use androidx.recyclerview.widget.RecyclerView because EpoxyRecyclerViews add behavior we do not want to + Date: Mon, 9 Dec 2019 17:43:14 +0100 Subject: [PATCH 34/51] Update CHANGES.md --- CHANGES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 677bc23965..21cf052d45 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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) =================================================== From 742136abe82ea7beb1ff6829633a6e494698aa1b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 18:01:58 +0100 Subject: [PATCH 35/51] Create RecyclerView extensions and cleanup all the recycler views --- .../debug/sas/DebugSasEmojiActivity.kt | 3 +- .../riotx/core/extensions/RecyclerView.kt | 32 +++++++++++++++++++ .../settings/KeysBackupSettingsFragment.kt | 3 +- .../CreateDirectRoomDirectoryUsersFragment.kt | 3 +- .../CreateDirectRoomKnownUsersFragment.kt | 3 +- .../features/home/group/GroupListFragment.kt | 3 +- .../room/breadcrumbs/BreadcrumbsFragment.kt | 3 +- .../home/room/detail/RoomDetailFragment.kt | 7 ++-- .../DisplayReadReceiptsBottomSheet.kt | 6 ++++ .../action/MessageActionsBottomSheet.kt | 6 ++++ .../edithistory/ViewEditHistoryBottomSheet.kt | 7 ++++ .../reactions/ViewReactionsBottomSheet.kt | 6 ++++ .../home/room/list/RoomListFragment.kt | 3 +- .../RoomListQuickActionsBottomSheet.kt | 6 ++++ .../login/terms/LoginTermsFragment.kt | 3 +- .../reactions/EmojiChooserFragment.kt | 6 ++++ .../reactions/EmojiChooserViewModel.kt | 1 + .../reactions/EmojiSearchResultFragment.kt | 3 +- .../roomdirectory/PublicRoomsFragment.kt | 3 +- .../createroom/CreateRoomFragment.kt | 3 +- .../picker/RoomDirectoryPickerFragment.kt | 3 +- ...ttingsNotificationsTroubleshootFragment.kt | 6 ++++ .../VectorSettingsIgnoredUsersFragment.kt | 3 +- .../settings/push/PushGatewaysFragment.kt | 3 +- .../settings/push/PushRulesFragment.kt | 3 +- 25 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt index 17a78f1c6a..89c31474b3 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt @@ -21,6 +21,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import im.vector.matrix.android.api.crypto.getAllVerificationEmojis import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup import kotlinx.android.synthetic.main.fragment_generic_recycler.* class DebugSasEmojiActivity : AppCompatActivity() { @@ -35,7 +36,7 @@ class DebugSasEmojiActivity : AppCompatActivity() { } override fun onDestroy() { - recyclerView.adapter = null + recyclerView.cleanup() super.onDestroy() } } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt new file mode 100644 index 0000000000..f844bf6c9a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt @@ -0,0 +1,32 @@ +/* + * 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 android.content.Context +import androidx.recyclerview.widget.RecyclerView +import com.airbnb.epoxy.EpoxyController + +fun RecyclerView.configureWith(context: Context, controller: EpoxyController) { + +} + +/** + * To call from Fragment.onDestroyView() + */ +fun RecyclerView.cleanup() { + adapter = null +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt index a00f71e5f8..7091bead4e 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt @@ -22,6 +22,7 @@ import androidx.recyclerview.widget.LinearLayoutManager 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.platform.VectorBaseFragment import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupActivity @@ -45,7 +46,7 @@ class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSetti override fun onDestroyView() { keysBackupSettingsRecyclerViewController.listener = null - keysBackupSettingsRecyclerView.adapter = null + keysBackupSettingsRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt index 575940a376..ac2230eae3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt @@ -25,6 +25,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.cleanup import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.setupAsSearch import im.vector.riotx.core.extensions.showKeyboard @@ -51,7 +52,7 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.adapter = null + recyclerView.cleanup() directRoomController.callback = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt index bad322292b..c333b6e09e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt @@ -32,6 +32,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.cleanup import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.setupAsSearch @@ -70,7 +71,7 @@ class CreateDirectRoomKnownUsersFragment @Inject constructor( override fun onDestroyView() { knownUsersController.callback = null - recyclerView.adapter = null + recyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt index 5bc0c0f61d..312a66d1db 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt @@ -25,6 +25,7 @@ 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.observeEvent import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment @@ -58,7 +59,7 @@ class GroupListFragment @Inject constructor( override fun onDestroyView() { groupController.callback = null - groupListView.adapter = null + groupListView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt index e57a2ddac4..713b69d454 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt @@ -21,6 +21,7 @@ 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.platform.VectorBaseFragment import im.vector.riotx.features.home.room.detail.RoomDetailSharedAction import im.vector.riotx.features.home.room.detail.RoomDetailSharedActionViewModel @@ -46,8 +47,8 @@ class BreadcrumbsFragment @Inject constructor( } override fun onDestroyView() { + breadcrumbsRecyclerView.cleanup() super.onDestroyView() - breadcrumbsRecyclerView.adapter = null } private fun setupRecyclerView() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 0d869048fb..e1bb24eb8f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -70,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 @@ -293,7 +290,7 @@ class RoomDetailFragment @Inject constructor( timelineEventController.removeModelBuildListener(modelBuildListener) modelBuildListener = null debouncer.cancelAll() - recyclerView.adapter = null + recyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt index f220570e69..b098ce2638 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt @@ -29,6 +29,7 @@ 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.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import kotlinx.android.parcel.Parcelize @@ -70,6 +71,11 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() { 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 { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index a5bf6f8558..8046b996fc 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -27,6 +27,7 @@ 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.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import javax.inject.Inject @@ -68,6 +69,11 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message messageActionsEpoxyController.listener = this } + override fun onDestroyView() { + recyclerView.cleanup() + super.onDestroyView() + } + override fun onUrlClicked(url: String): Boolean { sharedActionViewModel.post(EventSharedAction.OnUrlClicked(url)) // Always consume diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index 709bcb53c7..e28f42c078 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -30,6 +30,7 @@ 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.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData @@ -73,6 +74,12 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() { 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() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt index d5df8f7b40..d570a56242 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt @@ -29,6 +29,7 @@ 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.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData @@ -66,6 +67,11 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() { bottomSheetTitle.text = context?.getString(R.string.reactions) } + override fun onDestroyView() { + recyclerView.cleanup() + super.onDestroyView() + } + override fun invalidate() = withState(viewModel) { epoxyController.setData(it) super.invalidate() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 334f8b3417..00d964b28c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -36,6 +36,7 @@ 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 @@ -121,7 +122,7 @@ class RoomListFragment @Inject constructor( override fun onDestroyView() { roomController.removeModelBuildListener(modelBuildListener) modelBuildListener = null - roomListView.adapter = null + roomListView.cleanup() roomController.listener = null createChatFabMenu.listener = null super.onDestroyView() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt index 3a85cf26fa..8dab44b7a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt @@ -29,6 +29,7 @@ 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.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.navigation.Navigator import kotlinx.android.parcel.Parcelize @@ -76,6 +77,11 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R roomListActionsEpoxyController.listener = this } + override fun onDestroyView() { + recyclerView.cleanup() + super.onDestroyView() + } + override fun invalidate() = withState(viewModel) { roomListActionsEpoxyController.setData(it) super.invalidate() diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt index b50e7cfdcd..1e0d80de24 100755 --- a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt @@ -25,6 +25,7 @@ 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.utils.openUrlInExternalBrowser import im.vector.riotx.features.login.AbstractLoginFragment import im.vector.riotx.features.login.LoginAction @@ -72,7 +73,7 @@ class LoginTermsFragment @Inject constructor( } override fun onDestroyView() { - loginTermsPolicyList.adapter = null + loginTermsPolicyList.cleanup() policyController.listener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt index 5e705a70c2..c49bdcbf92 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt @@ -19,6 +19,7 @@ import android.os.Bundle import android.view.View import androidx.recyclerview.widget.RecyclerView import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.platform.VectorBaseFragment import javax.inject.Inject @@ -37,4 +38,9 @@ class EmojiChooserFragment @Inject constructor() : VectorBaseFragment() { it.adapter?.notifyDataSetChanged() } } + + override fun onDestroyView() { + (view as? RecyclerView)?.cleanup() + super.onDestroyView() + } } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt index bbde2ac54c..1979b02bfd 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt @@ -23,6 +23,7 @@ import javax.inject.Inject class EmojiChooserViewModel @Inject constructor() : ViewModel() { + // TODO Move the adapter out of the ViewModel var adapter: EmojiRecyclerAdapter? = null val emojiSourceLiveData: MutableLiveData = MutableLiveData() diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt index bef8a65542..cf5b6aa211 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt @@ -23,6 +23,7 @@ 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.platform.VectorBaseFragment import im.vector.riotx.core.utils.LiveEvent import kotlinx.android.synthetic.main.fragment_generic_recycler.* @@ -58,7 +59,7 @@ class EmojiSearchResultFragment @Inject constructor( override fun onDestroyView() { epoxyController.listener = null - recyclerView.adapter = null + recyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index 109364194d..f8d99d4b1c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -28,6 +28,7 @@ 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.observeEvent import im.vector.riotx.core.platform.VectorBaseFragment import io.reactivex.rxkotlin.subscribeBy @@ -84,7 +85,7 @@ class PublicRoomsFragment @Inject constructor( override fun onDestroyView() { publicRoomsController.callback = null - publicRoomsList.adapter = null + publicRoomsList.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt index 95d9c198ee..507dd7c71d 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -24,6 +24,7 @@ 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.platform.VectorBaseFragment import im.vector.riotx.features.roomdirectory.RoomDirectorySharedAction import im.vector.riotx.features.roomdirectory.RoomDirectorySharedActionViewModel @@ -51,7 +52,7 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C } override fun onDestroyView() { - createRoomForm.adapter = null + createRoomForm.cleanup() createRoomController.listener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index cc56de7cd7..f1ca8a25cd 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -25,6 +25,7 @@ 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.platform.VectorBaseFragment import im.vector.riotx.features.roomdirectory.RoomDirectoryAction import im.vector.riotx.features.roomdirectory.RoomDirectorySharedAction @@ -61,7 +62,7 @@ class RoomDirectoryPickerFragment @Inject constructor(val roomDirectoryPickerVie } override fun onDestroyView() { - roomDirectoryPickerList.adapter = null + roomDirectoryPickerList.cleanup() roomDirectoryPickerController.callback = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt index 6f43114eb4..3f69b5880e 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt @@ -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() diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index de267047b9..3c48336620 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -27,6 +27,7 @@ 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.observeEvent import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment @@ -59,7 +60,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( override fun onDestroyView() { ignoredUsersController.callback = null - recyclerView.adapter = null + recyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt index e35ec6b5ff..e76b7cbc85 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt @@ -25,6 +25,7 @@ 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.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.resources.StringProvider @@ -58,7 +59,7 @@ class PushGatewaysFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.adapter = null + recyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt index 52a5928e5d..7d5dd74a80 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt @@ -24,6 +24,7 @@ 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.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.resources.StringProvider @@ -55,7 +56,7 @@ class PushRulesFragment : VectorBaseFragment() { } override fun onDestroyView() { - recyclerView.adapter = null + recyclerView.cleanup() super.onDestroyView() } From f0aa34774e0b4fa35e0949472bfd97edd0926ab2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 21:31:04 +0100 Subject: [PATCH 36/51] Create RecyclerView extensions and cleanup all the recycler views --- .../features/debug/sas/DebugSasEmojiActivity.kt | 6 +++--- .../vector/riotx/core/extensions/RecyclerView.kt | 16 +++++++++++++--- .../settings/KeysBackupSettingsFragment.kt | 5 ++--- .../CreateDirectRoomDirectoryUsersFragment.kt | 11 ++--------- .../CreateDirectRoomKnownUsersFragment.kt | 10 ++-------- .../features/home/group/GroupListFragment.kt | 8 +++----- .../home/room/breadcrumbs/BreadcrumbsFragment.kt | 7 ++----- .../DisplayReadReceiptsBottomSheet.kt | 5 ++--- .../detail/timeline/TimelineEventController.kt | 1 - .../timeline/action/MessageActionsBottomSheet.kt | 8 ++++---- .../edithistory/ViewEditHistoryBottomSheet.kt | 12 +++++------- .../helper/ContentUploadStateTrackerBinder.kt | 1 - .../reactions/ViewReactionsBottomSheet.kt | 5 ++--- .../actions/RoomListQuickActionsBottomSheet.kt | 5 ++--- .../features/login/terms/LoginTermsFragment.kt | 6 ++---- .../reactions/EmojiSearchResultFragment.kt | 9 ++------- .../roomdirectory/PublicRoomsFragment.kt | 5 ++--- .../createroom/CreateRoomFragment.kt | 6 ++---- .../picker/RoomDirectoryPickerFragment.kt | 6 ++---- .../VectorSettingsIgnoredUsersFragment.kt | 5 ++--- .../settings/push/PushGatewaysFragment.kt | 11 +++-------- .../features/settings/push/PushRulesFragment.kt | 10 ++-------- .../src/main/res/layout/fragment_breadcrumbs.xml | 2 +- 23 files changed, 60 insertions(+), 100 deletions(-) diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt index 89c31474b3..acca04d09f 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt @@ -18,10 +18,10 @@ package im.vector.riotx.features.debug.sas import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.LinearLayoutManager import im.vector.matrix.android.api.crypto.getAllVerificationEmojis import im.vector.riotx.R 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() { @@ -29,9 +29,9 @@ class DebugSasEmojiActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.fragment_generic_recycler) + // TODO Inject val controller = SasEmojiController() - recyclerView.adapter = controller.adapter - recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.configureWith(controller) controller.setData(SasState(getAllVerificationEmojis())) } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt index f844bf6c9a..b34edc0e89 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt @@ -16,12 +16,22 @@ package im.vector.riotx.core.extensions -import android.content.Context +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController -fun RecyclerView.configureWith(context: Context, controller: 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, + itemDecoration: RecyclerView.ItemDecoration? = null, + hasFixedSize: Boolean = true) { + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + itemAnimator?.let { this.itemAnimator = it } + itemDecoration?.let { addItemDecoration(it) } + setHasFixedSize(hasFixedSize) + adapter = epoxyController.adapter } /** diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt index 7091bead4e..f0997de372 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsFragment.kt @@ -18,11 +18,11 @@ package im.vector.riotx.features.crypto.keysbackup.settings import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog -import androidx.recyclerview.widget.LinearLayoutManager 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 @@ -39,8 +39,7 @@ class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSetti override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - keysBackupSettingsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) - keysBackupSettingsRecyclerView.adapter = keysBackupSettingsRecyclerViewController.adapter + keysBackupSettingsRecyclerView.configureWith(keysBackupSettingsRecyclerViewController) keysBackupSettingsRecyclerViewController.listener = this } diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt index ac2230eae3..4230ea030d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomDirectoryUsersFragment.kt @@ -18,17 +18,12 @@ package im.vector.riotx.features.home.createdirect import android.os.Bundle import android.view.View -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.airbnb.mvrx.activityViewModel 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.cleanup -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 @@ -58,10 +53,8 @@ class CreateDirectRoomDirectoryUsersFragment @Inject constructor( } private fun setupRecyclerView() { - recyclerView.setHasFixedSize(true) directRoomController.callback = this - recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - recyclerView.adapter = directRoomController.adapter + recyclerView.configureWith(directRoomController) } private fun setupSearchByMatrixIdView() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt index c333b6e09e..8108e9705c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/createdirect/CreateDirectRoomKnownUsersFragment.kt @@ -24,7 +24,6 @@ import android.view.MenuItem import android.view.View import android.widget.ScrollView import androidx.core.view.size -import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.chip.Chip @@ -32,10 +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.cleanup -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.* @@ -102,12 +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.layoutManager = LinearLayoutManager(requireContext()) - recyclerView.adapter = knownUsersController.adapter + recyclerView.configureWith(knownUsersController) } private fun setupFilterView() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt index 312a66d1db..254571f8cf 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListFragment.kt @@ -18,19 +18,18 @@ package im.vector.riotx.features.home.group import android.os.Bundle import android.view.View -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.airbnb.mvrx.Incomplete 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 @@ -49,8 +48,7 @@ class GroupListFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) groupController.callback = this stateView.contentView = groupListView - groupListView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) - groupListView.adapter = groupController.adapter + groupListView.configureWith(groupController) viewModel.subscribe { renderState(it) } viewModel.openGroupLiveData.observeEvent(this) { sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt index 713b69d454..b8e2cf7987 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsFragment.kt @@ -18,10 +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 @@ -52,11 +52,8 @@ class BreadcrumbsFragment @Inject constructor( } 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) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt index b098ce2638..f943556bcd 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt @@ -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 @@ -30,6 +29,7 @@ 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 @@ -65,8 +65,7 @@ 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) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index 83f9163496..576b9fa0ba 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index 8046b996fc..23729734cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -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 @@ -28,8 +27,10 @@ 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 kotlinx.android.synthetic.main.activity_image_media_viewer.* import javax.inject.Inject /** @@ -62,8 +63,7 @@ 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 @@ -88,7 +88,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message override fun didSelectMenuAction(eventAction: EventSharedAction) { if (eventAction is EventSharedAction.ReportContent) { - // Toggle report menu + // Toggle report menu // TODO Reanable item animation? viewModel.handle(MessageActionsAction.ToggleReportMenu) } else { sharedActionViewModel.post(eventAction) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index e28f42c078..e8ee6ce10e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -19,9 +19,7 @@ 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 @@ -31,6 +29,7 @@ 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 @@ -67,10 +66,10 @@ 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, + itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL), + hasFixedSize = false) bottomSheetTitle.text = context?.getString(R.string.message_edits) } @@ -79,7 +78,6 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() { super.onDestroyView() } - override fun invalidate() = withState(viewModel) { epoxyController.setData(it) super.invalidate() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt index bbc3474e1e..d80c625e8f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt @@ -64,7 +64,6 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess it.contentUploadProgressTracker().clear() } } - } private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt index d570a56242..9ece3e2bbd 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt @@ -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 @@ -30,6 +29,7 @@ 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 @@ -62,8 +62,7 @@ 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) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt index 8dab44b7a8..28656e1d6e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt @@ -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 @@ -30,6 +29,7 @@ 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 @@ -70,8 +70,7 @@ 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 diff --git a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt index 1e0d80de24..83d68f5f31 100755 --- a/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/terms/LoginTermsFragment.kt @@ -20,12 +20,12 @@ import android.os.Bundle import android.os.Parcelable import android.view.View import androidx.appcompat.app.AlertDialog -import androidx.recyclerview.widget.LinearLayoutManager 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 @@ -57,9 +57,7 @@ class LoginTermsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - loginTermsPolicyList.setHasFixedSize(true) - loginTermsPolicyList.layoutManager = LinearLayoutManager(requireContext()) - loginTermsPolicyList.adapter = policyController.adapter + loginTermsPolicyList.configureWith(policyController) policyController.listener = this val list = ArrayList() diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt index cf5b6aa211..976d5e984e 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt @@ -18,12 +18,11 @@ 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.* @@ -50,11 +49,7 @@ class EmojiSearchResultFragment @Inject constructor( } } - val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - recyclerView.layoutManager = lmgr - val dividerItemDecoration = DividerItemDecoration(recyclerView.context, lmgr.orientation) - recyclerView.addItemDecoration(dividerItemDecoration) - recyclerView.adapter = epoxyController.adapter + recyclerView.configureWith(epoxyController, itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) } override fun onDestroyView() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index f8d99d4b1c..1d8ed48b08 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -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 @@ -29,6 +28,7 @@ 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 @@ -103,9 +103,8 @@ class PublicRoomsFragment @Inject constructor( private fun setupRecyclerView() { val epoxyVisibilityTracker = EpoxyVisibilityTracker() epoxyVisibilityTracker.attach(publicRoomsList) - publicRoomsList.layoutManager = LinearLayoutManager(context) + publicRoomsList.configureWith(publicRoomsController) publicRoomsController.callback = this - publicRoomsList.adapter = publicRoomsController.adapter } override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt index 507dd7c71d..aacc21916a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -19,12 +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 @@ -69,10 +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.adapter = createRoomController.adapter } override fun onNameChange(newName: String) { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index f1ca8a25cd..1299919d2b 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -19,13 +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 @@ -80,10 +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.adapter = roomDirectoryPickerController.adapter } override fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData) { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 3c48336620..a6b8a5414f 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -20,7 +20,6 @@ import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading import com.airbnb.mvrx.fragmentViewModel @@ -28,6 +27,7 @@ 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 @@ -51,8 +51,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true ignoredUsersController.callback = this - recyclerView.layoutManager = LinearLayoutManager(requireContext()) - recyclerView.adapter = ignoredUsersController.adapter + recyclerView.configureWith(ignoredUsersController) ignoredUsersViewModel.requestErrorLiveData.observeEvent(this) { displayErrorDialog(it) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt index e76b7cbc85..816e76c4c4 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt @@ -19,13 +19,12 @@ package im.vector.riotx.features.settings.push 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.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 @@ -50,12 +49,7 @@ class PushGatewaysFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - recyclerView.layoutManager = lmgr - val dividerItemDecoration = DividerItemDecoration(recyclerView.context, - lmgr.orientation) - recyclerView.addItemDecoration(dividerItemDecoration) - recyclerView.adapter = epoxyController.adapter + recyclerView.configureWith(epoxyController, itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) } override fun onDestroyView() { @@ -67,6 +61,7 @@ class PushGatewaysFragment @Inject constructor( epoxyController.setData(state) } + // TODO Move to a proper file class PushGateWayController(private val stringProvider: StringProvider) : TypedEpoxyController() { override fun buildModels(data: PushGatewayViewState?) { data?.pushGateways?.invoke()?.let { pushers -> diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt index 7d5dd74a80..62ac49e738 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt @@ -18,13 +18,12 @@ package im.vector.riotx.features.settings.push 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.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 @@ -47,12 +46,7 @@ class PushRulesFragment : VectorBaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val lmgr = LinearLayoutManager(context, RecyclerView.VERTICAL, false) - recyclerView.layoutManager = lmgr - val dividerItemDecoration = DividerItemDecoration(recyclerView.context, - lmgr.orientation) - recyclerView.addItemDecoration(dividerItemDecoration) - recyclerView.adapter = epoxyController.adapter + recyclerView.configureWith(epoxyController, itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) } override fun onDestroyView() { diff --git a/vector/src/main/res/layout/fragment_breadcrumbs.xml b/vector/src/main/res/layout/fragment_breadcrumbs.xml index 22cceadc03..5cdd2e964a 100644 --- a/vector/src/main/res/layout/fragment_breadcrumbs.xml +++ b/vector/src/main/res/layout/fragment_breadcrumbs.xml @@ -1,5 +1,5 @@ - Date: Mon, 9 Dec 2019 21:33:10 +0100 Subject: [PATCH 37/51] Cleanup --- vector/src/main/java/im/vector/riotx/core/platform/StateView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt b/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt index 247d6f54ee..f674478724 100755 --- a/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/StateView.kt @@ -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() } From a761a0dbd2edb94e4198a70ead84d8d5fa46b7de Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 21:56:03 +0100 Subject: [PATCH 38/51] Cleanup --- .../debug/sas/DebugSasEmojiActivity.kt | 1 - .../riotx/features/home/HomeDetailFragment.kt | 5 +- .../timeline/action/MessageActionsAnimator.kt | 34 +++++++++++++ .../action/MessageActionsBottomSheet.kt | 7 ++- .../room/filtered/FilteredRoomsActivity.kt | 4 +- .../reactions/EmojiReactionPickerActivity.kt | 6 +-- .../reactions/EmojiSearchResultFragment.kt | 2 +- .../settings/push/PushGateWayController.kt | 51 +++++++++++++++++++ .../settings/push/PushGatewaysFragment.kt | 35 ++----------- .../settings/push/PushRulesFragment.kt | 2 +- .../features/share/IncomingShareActivity.kt | 4 +- 11 files changed, 101 insertions(+), 50 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsAnimator.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/push/PushGateWayController.kt diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt index acca04d09f..170c156858 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/sas/DebugSasEmojiActivity.kt @@ -29,7 +29,6 @@ class DebugSasEmojiActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.fragment_generic_recycler) - // TODO Inject val controller = SasEmojiController() recyclerView.configureWith(controller) controller.setData(SasState(getAllVerificationEmojis())) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index fe98501e73..f60db12459 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -46,7 +46,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 +55,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) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsAnimator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsAnimator.kt new file mode 100644 index 0000000000..fbe32e0e2b --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsAnimator.kt @@ -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 + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index 23729734cb..63e3be908d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -30,7 +30,6 @@ 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 kotlinx.android.synthetic.main.activity_image_media_viewer.* import javax.inject.Inject /** @@ -88,7 +87,11 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message override fun didSelectMenuAction(eventAction: EventSharedAction) { if (eventAction is EventSharedAction.ReportContent) { - // Toggle report menu // TODO Reanable item animation? + // Toggle report menu + // Enable item animation + if (recyclerView.itemAnimator == null) { + recyclerView.itemAnimator = MessageActionsAnimator() + } viewModel.handle(MessageActionsAction.ToggleReportMenu) } else { sharedActionViewModel.post(eventAction) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt index c4bb0d9b15..04e738cec8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt @@ -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) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt index 85e4eecf21..02f564ba72 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt @@ -54,11 +54,11 @@ 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 emojiCompatFontProvider: EmojiCompatFontProvider diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt index 976d5e984e..d87b7d7946 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt @@ -32,7 +32,7 @@ class EmojiSearchResultFragment @Inject constructor( private val epoxyController: EmojiSearchResultController ) : VectorBaseFragment() { - override fun getLayoutResId(): Int = R.layout.fragment_generic_recycler + override fun getLayoutResId() = R.layout.fragment_generic_recycler val viewModel: EmojiSearchResultViewModel by activityViewModel() diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGateWayController.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGateWayController.kt new file mode 100644 index 0000000000..d8d23fbaf4 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGateWayController.kt @@ -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() { + + 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)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt index 816e76c4c4..c4d2118a77 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt @@ -19,7 +19,6 @@ package im.vector.riotx.features.settings.push import android.os.Bundle import android.view.View import androidx.recyclerview.widget.DividerItemDecoration -import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R @@ -27,20 +26,18 @@ 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.* 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 + 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() @@ -60,30 +57,4 @@ class PushGatewaysFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> epoxyController.setData(state) } - - // TODO Move to a proper file - class PushGateWayController(private val stringProvider: StringProvider) : TypedEpoxyController() { - 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)) - } - } - } - } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt index 62ac49e738..4deb410d03 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt @@ -33,7 +33,7 @@ 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 + override fun getLayoutResId() = R.layout.fragment_generic_recycler private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class) diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt index 33b0744d4f..e48c8246d2 100644 --- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt @@ -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) From edc6c3dd4fa42e1e78186af04faf7912637a1b05 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 22:00:41 +0100 Subject: [PATCH 39/51] Cleanup --- .../core/platform/VectorBaseBottomSheetDialogFragment.kt | 2 +- .../detail/readreceipts/DisplayReadReceiptsBottomSheet.kt | 4 ++-- .../room/detail/timeline/action/MessageActionsBottomSheet.kt | 4 ++-- .../detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt | 4 ++-- .../detail/timeline/reactions/ViewReactionsBottomSheet.kt | 4 ++-- .../home/room/list/actions/RoomListQuickActionsBottomSheet.kt | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt index 5727580653..70311e2f57 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -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) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt index f943556bcd..4827c825cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt @@ -53,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? { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index 63e3be908d..ba772344e0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -49,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? { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index e8ee6ce10e..5a5fc5de95 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -54,8 +54,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? { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt index 9ece3e2bbd..8fddc4c06a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt @@ -50,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? { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt index 28656e1d6e..60a26c8151 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt @@ -57,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? { From 99423bacb234706cc42bf6803857f19266a8fc45 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 22:09:17 +0100 Subject: [PATCH 40/51] Cleanup --- .../java/im/vector/riotx/core/extensions/RecyclerView.kt | 7 +++++-- .../timeline/edithistory/ViewEditHistoryBottomSheet.kt | 2 +- .../riotx/features/reactions/EmojiSearchResultFragment.kt | 3 +-- .../riotx/features/settings/push/PushGatewaysFragment.kt | 3 +-- .../riotx/features/settings/push/PushRulesFragment.kt | 3 +-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt index b34edc0e89..003045af51 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt @@ -16,6 +16,7 @@ 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 @@ -25,11 +26,13 @@ import com.airbnb.epoxy.EpoxyController */ fun RecyclerView.configureWith(epoxyController: EpoxyController, itemAnimator: RecyclerView.ItemAnimator? = null, - itemDecoration: RecyclerView.ItemDecoration? = null, + showDivider: Boolean = false, hasFixedSize: Boolean = true) { layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) itemAnimator?.let { this.itemAnimator = it } - itemDecoration?.let { addItemDecoration(it) } + if (showDivider) { + addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) + } setHasFixedSize(hasFixedSize) adapter = epoxyController.adapter } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index 5a5fc5de95..845d2a2b7a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -68,7 +68,7 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() { super.onActivityCreated(savedInstanceState) recyclerView.configureWith( epoxyController, - itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL), + showDivider = true, hasFixedSize = false) bottomSheetTitle.text = context?.getString(R.string.message_edits) } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt index d87b7d7946..b34a5f2357 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt @@ -17,7 +17,6 @@ package im.vector.riotx.features.reactions import android.os.Bundle import android.view.View -import androidx.recyclerview.widget.DividerItemDecoration import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R @@ -49,7 +48,7 @@ class EmojiSearchResultFragment @Inject constructor( } } - recyclerView.configureWith(epoxyController, itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) + recyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt index c4d2118a77..d5c19e8781 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysFragment.kt @@ -18,7 +18,6 @@ package im.vector.riotx.features.settings.push import android.os.Bundle import android.view.View -import androidx.recyclerview.widget.DividerItemDecoration import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R @@ -46,7 +45,7 @@ class PushGatewaysFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) + recyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt index 4deb410d03..bee9cf54d9 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesFragment.kt @@ -17,7 +17,6 @@ package im.vector.riotx.features.settings.push import android.os.Bundle import android.view.View -import androidx.recyclerview.widget.DividerItemDecoration import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -46,7 +45,7 @@ class PushRulesFragment : VectorBaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, itemDecoration = DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) + recyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { From 8527d3f162205b70f0b6d19f6cb283e8de9354c4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 22:30:29 +0100 Subject: [PATCH 41/51] Improve emoji picker search result --- .../reactions/EmojiSearchResultController.kt | 7 ++-- .../reactions/EmojiSearchResultFragment.kt | 20 +++++------ .../reactions/EmojiSearchResultItem.kt | 3 +- .../src/main/res/layout/item_emoji_result.xml | 33 ++++++++++--------- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultController.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultController.kt index 3e8f1c9769..208d9d7a56 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultController.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultController.kt @@ -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() { +class EmojiSearchResultController @Inject constructor( + private val stringProvider: StringProvider, + private val fontProvider: EmojiCompatFontProvider +) : TypedEpoxyController() { var emojiTypeface: Typeface? = fontProvider.typeface diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt index b34a5f2357..a62b2d39cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultFragment.kt @@ -29,25 +29,18 @@ import javax.inject.Inject class EmojiSearchResultFragment @Inject constructor( private val epoxyController: EmojiSearchResultController -) : VectorBaseFragment() { +) : VectorBaseFragment(), ReactionClickListener { 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 = object : ReactionClickListener { - override fun onReactionSelected(reaction: String) { - sharedViewModel?.selectedReaction = reaction - sharedViewModel?.navigateEvent?.value = LiveEvent(EmojiChooserViewModel.NAVIGATE_FINISH) - } - } - + epoxyController.listener = this recyclerView.configureWith(epoxyController, showDivider = true) } @@ -57,6 +50,11 @@ class EmojiSearchResultFragment @Inject constructor( super.onDestroyView() } + override fun onReactionSelected(reaction: String) { + sharedViewModel.selectedReaction = reaction + sharedViewModel.navigateEvent.value = LiveEvent(EmojiChooserViewModel.NAVIGATE_FINISH) + } + override fun invalidate() = withState(viewModel) { state -> epoxyController.setData(state) } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt index 1b117035d9..746fdddbf3 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt @@ -22,6 +22,7 @@ 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 @EpoxyModelClass(layout = R.layout.item_emoji_result) abstract class EmojiSearchResultItem : EpoxyModelWithHolder() { @@ -44,7 +45,7 @@ abstract class EmojiSearchResultItem : EpoxyModelWithHolder + android:paddingStart="@dimen/layout_horizontal_margin" + android:paddingEnd="@dimen/layout_horizontal_margin"> + android:layout_weight="1" + android:orientation="vertical"> - + android:textColor="?riotx_text_secondary" + android:textSize="14sp" + android:visibility="gone" + tools:text="Smile, foo, bar" + tools:visibility="visible" /> From 1ad8f47dc1bc5e5804271596bd545d65b5a344b0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 22:36:38 +0100 Subject: [PATCH 42/51] Split EmojiDataSource --- .../java/im/vector/riotx/core/utils/Emoji.kt | 4 +- .../reactions/EmojiChooserViewModel.kt | 1 + .../reactions/EmojiRecyclerAdapter.kt | 1 + .../reactions/EmojiSearchResultItem.kt | 3 +- .../reactions/EmojiSearchResultViewModel.kt | 4 +- .../features/reactions/data/EmojiCategory.kt | 26 +++++++++ .../features/reactions/data/EmojiData.kt | 26 +++++++++ .../reactions/{ => data}/EmojiDataSource.kt | 46 +--------------- .../features/reactions/data/EmojiItem.kt | 54 +++++++++++++++++++ 9 files changed, 116 insertions(+), 49 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiCategory.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiData.kt rename vector/src/main/java/im/vector/riotx/features/reactions/{ => data}/EmojiDataSource.kt (51%) create mode 100644 vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt diff --git a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt index a5babcc885..9b5552a73b 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt @@ -19,7 +19,7 @@ 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 im.vector.riotx.features.reactions.data.EmojiData import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber @@ -56,7 +56,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().also { diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt index 1979b02bfd..014250d860 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt @@ -19,6 +19,7 @@ import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import im.vector.riotx.core.utils.LiveEvent +import im.vector.riotx.features.reactions.data.EmojiDataSource import javax.inject.Inject class EmojiChooserViewModel @Inject constructor() : ViewModel() { diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt index b5270bad29..c7df7fbe7b 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt @@ -30,6 +30,7 @@ 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 diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt index 746fdddbf3..b3e70bcdf6 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt @@ -23,12 +23,13 @@ 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() { @EpoxyAttribute - lateinit var emojiItem: EmojiDataSource.EmojiItem + lateinit var emojiItem: EmojiItem @EpoxyAttribute var currentQuery: String? = null diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt index 8225fa7bd6..96746e583e 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt @@ -19,10 +19,12 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext 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 = emptyList() + val results: List = emptyList() ) : MvRxState class EmojiSearchResultViewModel(val dataSource: EmojiDataSource, initialState: EmojiSearchResultViewState) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiCategory.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiCategory.kt new file mode 100644 index 0000000000..9aa7428047 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiCategory.kt @@ -0,0 +1,26 @@ +/* + * 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.JsonClass + +@JsonClass(generateAdapter = true) +data class EmojiCategory( + val id: String, + val name: String, + val emojis: List +) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiData.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiData.kt new file mode 100644 index 0000000000..9db9a01ce8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiData.kt @@ -0,0 +1,26 @@ +/* + * 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.JsonClass + +@JsonClass(generateAdapter = true) +data class EmojiData( + val categories: List, + val emojis: Map, + val aliases: Map +) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiDataSource.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt similarity index 51% rename from vector/src/main/java/im/vector/riotx/features/reactions/EmojiDataSource.kt rename to vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt index 2853975938..794fdbfe12 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.reactions +package im.vector.riotx.features.reactions.data import android.content.Context -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass import com.squareup.moshi.Moshi import im.vector.riotx.R @@ -31,32 +29,6 @@ class EmojiDataSource(val context: Context) { 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, - val emojis: Map, - val aliases: Map) - - @JsonClass(generateAdapter = true) - data class EmojiCategory(val id: String, val name: String, val emojis: List) - - @JsonClass(generateAdapter = true) - data class EmojiItem( - @Json(name = "a") val name: String, - @Json(name = "b") val unicode: String, - @Json(name = "j") val keywords: List?, - val k: List?) { - - 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!! } } @@ -72,20 +44,4 @@ class EmojiDataSource(val context: Context) { 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', } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt new file mode 100644 index 0000000000..95deb32279 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt @@ -0,0 +1,54 @@ +/* + * 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 EmojiItem( + @Json(name = "a") val name: String, + @Json(name = "b") val unicode: String, + @Json(name = "j") val keywords: List?, + val k: List?) { + + var _emojiText: String? = null + + fun emojiString(): String { + if (_emojiText == null) { + val utf8Text = unicode.split("-").joinToString("") { "\\u$it" } // "\u0048\u0065\u006C\u006C\u006F World" + _emojiText = EmojiDataSource.fromUnicode(utf8Text) + } + return _emojiText!! + } +} + +// 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', From 29721775419ba914ed506c1462a4c7cfb0d4dabd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 22:46:39 +0100 Subject: [PATCH 43/51] Split EmojiDataSource - cleanup --- .../reactions/EmojiSearchResultViewModel.kt | 10 +++++++--- .../features/reactions/data/EmojiCategory.kt | 7 ++++--- .../features/reactions/data/EmojiData.kt | 7 ++++--- .../reactions/data/EmojiDataSource.kt | 13 ------------- .../features/reactions/data/EmojiItem.kt | 19 ++++++++++++++++--- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt index 96746e583e..a194e4ad4d 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt @@ -44,9 +44,13 @@ class EmojiSearchResultViewModel(val dataSource: EmojiDataSource, initialState: ?.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) - }) + || action.queryString + .split("\\s".toRegex()) + .fold(true, { prev, q -> + prev + && (it.keywords?.any { it.contains(q, true) } + ?: false) + }) } ?: emptyList() ) } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiCategory.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiCategory.kt index 9aa7428047..a6c238a4c4 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiCategory.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiCategory.kt @@ -16,11 +16,12 @@ package im.vector.riotx.features.reactions.data +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class EmojiCategory( - val id: String, - val name: String, - val emojis: List + @Json(name = "id") val id: String, + @Json(name = "name") val name: String, + @Json(name = "emojis") val emojis: List ) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiData.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiData.kt index 9db9a01ce8..f2094bfc7b 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiData.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiData.kt @@ -16,11 +16,12 @@ package im.vector.riotx.features.reactions.data +import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class EmojiData( - val categories: List, - val emojis: Map, - val aliases: Map + @Json(name = "categories") val categories: List, + @Json(name = "emojis") val emojis: Map, + @Json(name = "aliases") val aliases: Map ) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt index 794fdbfe12..5564182f4e 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt @@ -31,17 +31,4 @@ class EmojiDataSource(val context: Context) { this.rawData = jsonAdapter.fromJson(inputAsString) } } - - 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() - } - } } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt index 95deb32279..fc0959dfbb 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt @@ -23,18 +23,31 @@ import com.squareup.moshi.JsonClass data class EmojiItem( @Json(name = "a") val name: String, @Json(name = "b") val unicode: String, - @Json(name = "j") val keywords: List?, - val k: List?) { + @Json(name = "j") val keywords: List? +) { var _emojiText: String? = null fun emojiString(): String { if (_emojiText == null) { val utf8Text = unicode.split("-").joinToString("") { "\\u$it" } // "\u0048\u0065\u006C\u006C\u006F World" - _emojiText = EmojiDataSource.fromUnicode(utf8Text) + _emojiText = fromUnicode(utf8Text) } return _emojiText!! } + + companion object { + private fun fromUnicode(unicode: String): String { + val str = unicode.replace("\\", "") + val arr = str.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)) + } + } + } + } } // name: 'a', From 80306f20dfcd2d135d796751a8e6026aa31b0a43 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 22:57:23 +0100 Subject: [PATCH 44/51] Split EmojiDataSource - avoid !! --- .../features/reactions/data/EmojiItem.kt | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt index fc0959dfbb..9b4600b5c1 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt @@ -19,6 +19,23 @@ 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, @@ -26,14 +43,14 @@ data class EmojiItem( @Json(name = "j") val keywords: List? ) { - var _emojiText: String? = null + private 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!! + emojiText?.let { return it } + + val utf8Text = unicode.split("-").joinToString("") { "\\u$it" } // "\u0048\u0065\u006C\u006C\u006F World" + return fromUnicode(utf8Text) + .also { emojiText = it } } companion object { @@ -50,18 +67,3 @@ data class EmojiItem( } } -// 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', From 63e0b15f3d80262efd86ba6c7464db59112847ad Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 23:08:50 +0100 Subject: [PATCH 45/51] Split EmojiDataSource - cleanup --- .../features/reactions/data/EmojiItem.kt | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt index 9b4600b5c1..fdd9a80911 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt @@ -42,21 +42,27 @@ data class EmojiItem( @Json(name = "b") val unicode: String, @Json(name = "j") val keywords: List? ) { + // Cannot be private... + var cache: String? = null - private var emojiText: String? = null + val emoji: String + get() { + cache?.let { return it } - fun emojiString(): String { - emojiText?.let { return it } - - val utf8Text = unicode.split("-").joinToString("") { "\\u$it" } // "\u0048\u0065\u006C\u006C\u006F World" - return fromUnicode(utf8Text) - .also { emojiText = 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 str = unicode.replace("\\", "") - val arr = str.split("u".toRegex()).dropLastWhile { it.isEmpty() } + 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) From f00f34b244eed97debd1b8cf49c0a67c199562aa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Dec 2019 23:41:35 +0100 Subject: [PATCH 46/51] Improve EmojiChooserFragment: DI --- .../java/im/vector/riotx/VectorApplication.kt | 3 +- .../im/vector/riotx/core/di/FragmentModule.kt | 6 ++++ .../java/im/vector/riotx/core/utils/Emoji.kt | 2 ++ .../reactions/EmojiChooserFragment.kt | 28 ++++++++++++++-- .../reactions/EmojiChooserViewModel.kt | 33 ++++++------------- .../reactions/EmojiReactionPickerActivity.kt | 33 +++++++++---------- .../reactions/EmojiRecyclerAdapter.kt | 11 ++++--- .../reactions/EmojiSearchResultItem.kt | 4 +-- .../reactions/EmojiSearchResultViewModel.kt | 31 ++++++++++------- .../reactions/data/EmojiDataSource.kt | 23 +++++++++---- 10 files changed, 105 insertions(+), 69 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt index f3043fbec8..b6b0d16360 100644 --- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt @@ -43,7 +43,6 @@ 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.rx.setupRxPlugin -import im.vector.riotx.core.utils.initKnownEmojiHashSet import im.vector.riotx.features.configuration.VectorConfiguration import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.riotx.features.notifications.NotificationDrawerManager @@ -137,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) diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 1a7a07bee2..442c5f6f96 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -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 } diff --git a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt index 9b5552a73b..3b16c3ea22 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt @@ -49,6 +49,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? = null @@ -77,6 +78,7 @@ fun isSingleEmoji(string: String): Boolean { } return knownEmojiSet?.contains(string) ?: false } + */ /** * Test if a string contains emojis. diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt index c49bdcbf92..bbedeff552 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt @@ -17,13 +17,18 @@ package im.vector.riotx.features.reactions import android.os.Bundle import android.view.View +import androidx.lifecycle.observe import androidx.recyclerview.widget.RecyclerView import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.platform.VectorBaseFragment 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 @@ -32,15 +37,32 @@ 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!!) + + emojiRecyclerAdapter.reactionClickListener = this + emojiRecyclerAdapter.interactionListener = this + (view as? RecyclerView)?.let { - it.adapter = viewModel.adapter + it.adapter = emojiRecyclerAdapter it.adapter?.notifyDataSetChanged() } + + 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() { (view as? RecyclerView)?.cleanup() + emojiRecyclerAdapter.reactionClickListener = null + emojiRecyclerAdapter.interactionListener = null super.onDestroyView() } } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt index 014250d860..9a0317f454 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserViewModel.kt @@ -15,46 +15,33 @@ */ 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 -import im.vector.riotx.features.reactions.data.EmojiDataSource import javax.inject.Inject class EmojiChooserViewModel @Inject constructor() : ViewModel() { - // TODO Move the adapter out of the ViewModel - var adapter: EmojiRecyclerAdapter? = null - val emojiSourceLiveData: MutableLiveData = MutableLiveData() - val navigateEvent: MutableLiveData> = MutableLiveData() var selectedReaction: String? = null var eventId: String? = null val currentSection: MutableLiveData = MutableLiveData() + val moveToSection: MutableLiveData = 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 { diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt index 02f564ba72..7c2a9349e0 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt @@ -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(), @@ -60,7 +60,9 @@ class EmojiReactionPickerActivity : VectorBaseActivity(), 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,20 @@ 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?.let { categories -> + for (category in categories) { + 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 +136,6 @@ class EmojiReactionPickerActivity : VectorBaseActivity(), override fun compatibilityFontUpdate(typeface: Typeface?) { EmojiDrawView.configureTextPaint(this, typeface) - searchResultViewModel.dataSource } override fun onDestroy() { diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt index c7df7fbe7b..a955189b17 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt @@ -34,6 +34,7 @@ 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 /** @@ -43,10 +44,12 @@ import kotlin.math.abs * 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() { + var reactionClickListener: ReactionClickListener? = null var interactionListener: InteractionListener? = null private var mRecyclerView: RecyclerView? = null @@ -73,7 +76,7 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null, val sectionMojis = 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) } } @@ -197,7 +200,7 @@ class EmojiRecyclerAdapter(private val dataSource: EmojiDataSource? = null, val sectionMojis = categories[sectionNumber].emojis val sectionOffset = getSectionOffset(sectionNumber) val emoji = sectionMojis[position - sectionOffset] - val item = dataSource.rawData!!.emojis[emoji]!!.emojiString() + val item = dataSource.rawData!!.emojis[emoji]!!.emoji (holder as EmojiViewHolder).data = item if (scrollState != ScrollState.SETTLING || !isFastScroll) { // Log.i("PERF","Bind with draw at position:$position") diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt index b3e70bcdf6..fd0a6d70ff 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt @@ -43,12 +43,12 @@ abstract class EmojiSearchResultItem : EpoxyModelWithHolder = emptyList() ) : MvRxState -class EmojiSearchResultViewModel(val dataSource: EmojiDataSource, initialState: EmojiSearchResultViewState) +class EmojiSearchResultViewModel @AssistedInject constructor( + @Assisted initialState: EmojiSearchResultViewState, + private val dataSource: EmojiDataSource) : VectorViewModel(initialState) { + @AssistedInject.Factory + interface Factory { + fun create(initialState: EmojiSearchResultViewState): EmojiSearchResultViewModel + } + + companion object : MvRxViewModelFactory { + + 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) @@ -55,12 +70,4 @@ class EmojiSearchResultViewModel(val dataSource: EmojiDataSource, initialState: ) } } - - companion object : MvRxViewModelFactory { - - 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) - } - } } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt index 5564182f4e..c9d683a2b9 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt @@ -18,17 +18,28 @@ package im.vector.riotx.features.reactions.data import android.content.Context import com.squareup.moshi.Moshi import im.vector.riotx.R +import im.vector.riotx.core.di.ScreenScope +import timber.log.Timber +import javax.inject.Inject +import kotlin.system.measureTimeMillis -class EmojiDataSource(val context: Context) { +@ScreenScope +class EmojiDataSource @Inject constructor( + 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) + measureTimeMillis { + 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) + } + }.also { + Timber.e("Emoji: $it millis") } } } From 3c18fd5335d69154cb54e9cdb67b791093761b7c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2019 00:42:24 +0100 Subject: [PATCH 47/51] Improve EmojiChooserFragment: improve filtering result: sort --- .../reactions/EmojiReactionPickerActivity.kt | 24 +++-- .../reactions/EmojiRecyclerAdapter.kt | 87 ++++++++----------- .../reactions/EmojiSearchResultItem.kt | 2 +- .../reactions/EmojiSearchResultViewModel.kt | 34 +++++--- .../reactions/data/EmojiDataSource.kt | 27 ++---- .../features/reactions/data/EmojiItem.kt | 2 +- 6 files changed, 78 insertions(+), 98 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt index 7c2a9349e0..96536e1f16 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt @@ -95,20 +95,18 @@ class EmojiReactionPickerActivity : VectorBaseActivity(), viewModel.eventId = intent.getStringExtra(EXTRA_EVENT_ID) - emojiDataSource.rawData?.categories?.let { categories -> - for (category in categories) { - 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) + 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 { diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt index a955189b17..efccb9c917 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiRecyclerAdapter.kt @@ -40,12 +40,11 @@ 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 @Inject constructor( - private val dataSource: EmojiDataSource? + private val dataSource: EmojiDataSource ) : RecyclerView.Adapter() { @@ -70,13 +69,12 @@ class EmojiRecyclerAdapter @Inject constructor( 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).emoji + val item = dataSource.rawData.emojis.getValue(emoji).emoji reactionClickListener?.onReactionSelected(item) } } @@ -117,7 +115,7 @@ class EmojiRecyclerAdapter @Inject constructor( } fun scrollToSection(section: Int) { - if (section < 0 || section >= dataSource?.rawData?.categories?.size ?: 0) { + if (section < 0 || section >= dataSource.rawData.categories.size) { // ignore return } @@ -149,14 +147,12 @@ class EmojiRecyclerAdapter @Inject constructor( } 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 } @@ -165,13 +161,11 @@ class EmojiRecyclerAdapter @Inject constructor( 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 } @@ -180,36 +174,32 @@ class EmojiRecyclerAdapter @Inject constructor( // 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]!!.emoji - (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() @@ -230,15 +220,8 @@ class EmojiRecyclerAdapter @Inject constructor( 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?) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt index fd0a6d70ff..55bf29e25f 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultItem.kt @@ -46,7 +46,7 @@ abstract class EmojiSearchResultItem : EpoxyModelWithHolder - prev - && (it.keywords?.any { it.contains(q, true) } - ?: false) - }) - } ?: emptyList() + // 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) } + }) + } + .sortedBy { it.name } ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt index c9d683a2b9..a326828112 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt @@ -15,31 +15,22 @@ */ package im.vector.riotx.features.reactions.data -import android.content.Context +import android.content.res.Resources import com.squareup.moshi.Moshi import im.vector.riotx.R import im.vector.riotx.core.di.ScreenScope -import timber.log.Timber import javax.inject.Inject -import kotlin.system.measureTimeMillis @ScreenScope class EmojiDataSource @Inject constructor( - context: Context + resources: Resources ) { - - var rawData: EmojiData? = null - - init { - measureTimeMillis { - 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) + val rawData = resources.openRawResource(R.raw.emoji_picker_datasource) + .use { input -> + Moshi.Builder() + .build() + .adapter(EmojiData::class.java) + .fromJson(input.bufferedReader().use { it.readText() }) } - }.also { - Timber.e("Emoji: $it millis") - } - } + ?: EmojiData(emptyList(), emptyMap(), emptyMap()) } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt index fdd9a80911..caf6672964 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt @@ -40,7 +40,7 @@ import com.squareup.moshi.JsonClass data class EmojiItem( @Json(name = "a") val name: String, @Json(name = "b") val unicode: String, - @Json(name = "j") val keywords: List? + @Json(name = "j") val keywords: List = emptyList() ) { // Cannot be private... var cache: String? = null From 3ac53d20e9d7fe01a2698cd5092f155f60e410a6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2019 01:05:20 +0100 Subject: [PATCH 48/51] Bugfix: react several times with the same reaction was possible (was a TODO). --- .../room/model/relation/RelationService.kt | 1 + .../room/relation/DefaultRelationService.kt | 31 ++++++++++++++++--- .../home/room/detail/RoomDetailFragment.kt | 6 +--- .../reactions/EmojiReactionPickerActivity.kt | 10 ++++-- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt index 385699b4db..b3dd1c6f22 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt @@ -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) */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt index db3b6100a0..8731045e14 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index e1bb24eb8f..80f54a9c1f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -450,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)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt index 96536e1f16..562ad6f5b2 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiReactionPickerActivity.kt @@ -203,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? { + val eventId = data.getStringExtra(EXTRA_EVENT_ID) ?: return null + val reaction = data.getStringExtra(EXTRA_REACTION_RESULT) ?: return null + return eventId to reaction + } } } From a9e2c31c32fcd3c602a2709f5247131c29235946 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2019 01:08:05 +0100 Subject: [PATCH 49/51] Remove log for privacy --- .../timeline/edithistory/ViewEditHistoryEpoxyController.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt index 4661d8f8cd..1a5c6db270 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt @@ -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) { From 3a761be6b4c894650705ba52679694d7e495e5ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2019 01:27:12 +0100 Subject: [PATCH 50/51] Last cleanup --- vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt | 7 ------- .../im/vector/riotx/features/home/HomeDetailFragment.kt | 1 - .../timeline/edithistory/ViewEditHistoryBottomSheet.kt | 1 - .../riotx/features/reactions/EmojiChooserFragment.kt | 9 +++------ .../im/vector/riotx/features/reactions/data/EmojiItem.kt | 1 - vector/src/main/res/layout/emoji_chooser_fragment.xml | 6 ++---- 6 files changed, 5 insertions(+), 20 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt index 3b16c3ea22..f9e5654726 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt @@ -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.data.EmojiData -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]" + diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index f60db12459..ac8d429cb1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -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 diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index 845d2a2b7a..8aa7c8561c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -19,7 +19,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.RecyclerView import butterknife.BindView import butterknife.ButterKnife diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt index bbedeff552..1dc9f34924 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiChooserFragment.kt @@ -18,10 +18,10 @@ package im.vector.riotx.features.reactions import android.os.Bundle import android.view.View import androidx.lifecycle.observe -import androidx.recyclerview.widget.RecyclerView 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( @@ -41,10 +41,7 @@ class EmojiChooserFragment @Inject constructor( emojiRecyclerAdapter.reactionClickListener = this emojiRecyclerAdapter.interactionListener = this - (view as? RecyclerView)?.let { - it.adapter = emojiRecyclerAdapter - it.adapter?.notifyDataSetChanged() - } + emojiRecyclerView.adapter = emojiRecyclerAdapter viewModel.moveToSection.observe(viewLifecycleOwner) { section -> emojiRecyclerAdapter.scrollToSection(section) @@ -60,7 +57,7 @@ class EmojiChooserFragment @Inject constructor( } override fun onDestroyView() { - (view as? RecyclerView)?.cleanup() + emojiRecyclerView.cleanup() emojiRecyclerAdapter.reactionClickListener = null emojiRecyclerAdapter.interactionListener = null super.onDestroyView() diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt index caf6672964..57083e8467 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiItem.kt @@ -72,4 +72,3 @@ data class EmojiItem( } } } - diff --git a/vector/src/main/res/layout/emoji_chooser_fragment.xml b/vector/src/main/res/layout/emoji_chooser_fragment.xml index 197e7ce6ef..586411f3d5 100644 --- a/vector/src/main/res/layout/emoji_chooser_fragment.xml +++ b/vector/src/main/res/layout/emoji_chooser_fragment.xml @@ -1,7 +1,7 @@ - - \ No newline at end of file + tools:spanCount="10" /> From d91ff87fb9420667c1e19045754c76942f6160d2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Dec 2019 15:47:26 +0100 Subject: [PATCH 51/51] Prepare release 0.10.0 --- CHANGES.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 21cf052d45..7ea78c2417 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes in RiotX 0.10.0 (2019-XX-XX) +Changes in RiotX 0.10.0 (2019-12-10) =================================================== Features ✨: @@ -15,12 +15,6 @@ Bugfix 🐛: - "ban" event are not rendered correctly (#716) - Fix crash when rotating screen in Room timeline -Translations 🗣: - - - -Build 🧱: - - - Changes in RiotX 0.9.1 (2019-12-05) ===================================================