From 3f8ddbe8803909c8aaf887e94400e2e12b4148c4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Oct 2019 16:23:35 +0200 Subject: [PATCH 01/30] Opening links from RiotX reuses browser tab (#599) --- CHANGES.md | 1 + .../riotx/core/utils/ExternalApplicationsUtil.kt | 1 + .../features/home/room/detail/RoomDetailFragment.kt | 10 +++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 42fb2cc291..d474e0bf69 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ Bugfix: - after login, the icon in the top left is a green 'A' for (all communities) rather than my avatar (#267) - Picture uploads are unreliable, pictures are shown in wrong aspect ratio on desktop client (#517) - Invitation notifications are not dismissed automatically if room is joined from another client (#347) + - Opening links from RiotX reuses browser tab (#599) Translations: - diff --git a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt index a8ed3c30f2..c51cfe39ae 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt @@ -50,6 +50,7 @@ fun openUrlInExternalBrowser(context: Context, uri: Uri?) { uri?.let { val browserIntent = Intent(Intent.ACTION_VIEW, it).apply { putExtra(Browser.EXTRA_APPLICATION_ID, context.packageName) + putExtra(Browser.EXTRA_CREATE_NEW_TAB, true) } try { 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 8dc9d3948c..c7b1966870 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 @@ -785,7 +785,7 @@ class RoomDetailFragment : // TimelineEventController.Callback ************************************************************ override fun onUrlClicked(url: String): Boolean { - return permalinkHandler.launch(requireActivity(), url, object : NavigateToRoomInterceptor { + val managed = permalinkHandler.launch(requireActivity(), url, object : NavigateToRoomInterceptor { override fun navToRoom(roomId: String, eventId: String?): Boolean { // Same room? if (roomId == roomDetailArgs.roomId) { @@ -803,6 +803,14 @@ class RoomDetailFragment : return false } }) + + if (!managed) { + // Open in external browser, in a new Tab + openUrlInExternalBrowser(requireContext(), url) + } + + // In fact it is always managed + return true } override fun onUrlLongClicked(url: String): Boolean { From 0a0c344bfb326c0657046a0a50bdf36cd1c99b31 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Oct 2019 10:10:16 +0200 Subject: [PATCH 02/30] Upgrade RecyclerView version to fix issues with a11y. Also minor upgrade of some other libs --- matrix-sdk-android/build.gradle | 6 +++--- vector/build.gradle | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 328cfbee20..3e6d3ea88b 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -102,7 +102,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "androidx.appcompat:appcompat:1.1.0" - implementation "androidx.recyclerview:recyclerview:1.1.0-beta04" + implementation "androidx.recyclerview:recyclerview:1.1.0-beta05" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" @@ -110,8 +110,8 @@ dependencies { // Network implementation 'com.squareup.retrofit2:retrofit:2.6.2' implementation 'com.squareup.retrofit2:converter-moshi:2.6.2' - implementation 'com.squareup.okhttp3:okhttp:4.2.0' - implementation 'com.squareup.okhttp3:logging-interceptor:4.2.0' + implementation 'com.squareup.okhttp3:okhttp:4.2.2' + implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' implementation 'com.novoda:merlin:1.2.0' implementation "com.squareup.moshi:moshi-adapters:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" diff --git a/vector/build.gradle b/vector/build.gradle index 3992bba8a1..78018a6107 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -281,7 +281,7 @@ dependencies { // UI implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - implementation 'com.google.android.material:material:1.1.0-alpha10' + implementation 'com.google.android.material:material:1.1.0-beta01' implementation 'me.gujun.android:span:1.7' implementation "ru.noties.markwon:core:$markwon_version" implementation "ru.noties.markwon:html:$markwon_version" From 116d569fa8436bd820d6923e2e89dff0a5e55b8e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Oct 2019 14:35:37 +0200 Subject: [PATCH 03/30] Fix regression after merge conflict: big font for messages with only big emoji --- .../timeline/factory/MessageItemFactory.kt | 17 +++++++++++++---- .../helper/MessageItemAttributesFactory.kt | 3 +++ .../room/detail/timeline/item/AbsMessageItem.kt | 2 ++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 58bda1eaf5..4619eb4c8e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -19,9 +19,9 @@ package im.vector.riotx.features.home.room.detail.timeline.factory import android.text.SpannableStringBuilder import android.text.Spanned import android.text.TextPaint +import android.text.style.AbsoluteSizeSpan import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan -import android.text.style.RelativeSizeSpan import android.view.View import dagger.Lazy import im.vector.matrix.android.api.permalinks.MatrixLinkify @@ -39,6 +39,8 @@ import im.vector.riotx.core.linkify.VectorLinkify import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.utils.DebouncedClickListener +import im.vector.riotx.core.utils.DimensionConverter +import im.vector.riotx.core.utils.containsOnlyEmojis import im.vector.riotx.core.utils.isLocalFile import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.helper.* @@ -241,12 +243,13 @@ class MessageItemFactory @Inject constructor( return MessageTextItem_() .apply { if (informationData.hasBeenEdited) { - val spannable = annotateWithEdited(linkifiedBody, callback, informationData) + val spannable = annotateWithEdited(linkifiedBody, callback, attributes.dimensionConverter, informationData) message(spannable) } else { message(linkifiedBody) } } + .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString())) .searchForPills(isFormatted) .leftGuideline(avatarSizeProvider.leftGuideline) .attributes(attributes) @@ -257,6 +260,7 @@ class MessageItemFactory @Inject constructor( private fun annotateWithEdited(linkifiedBody: CharSequence, callback: TimelineEventController.Callback?, + dimensionConverter: DimensionConverter, informationData: MessageInformationData): SpannableStringBuilder { val spannable = SpannableStringBuilder() spannable.append(linkifiedBody) @@ -271,7 +275,8 @@ class MessageItemFactory @Inject constructor( editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) - spannable.setSpan(RelativeSizeSpan(.9f), editStart, editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + // Note: text size is set to 14sp + spannable.setSpan(AbsoluteSizeSpan(dimensionConverter.spToPx(13)), editStart, editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) spannable.setSpan(object : ClickableSpan() { override fun onClick(widget: View?) { callback?.onEditedDecorationClicked(informationData) @@ -321,7 +326,7 @@ class MessageItemFactory @Inject constructor( return MessageTextItem_() .apply { if (informationData.hasBeenEdited) { - val spannable = annotateWithEdited(message, callback, informationData) + val spannable = annotateWithEdited(message, callback, attributes.dimensionConverter, informationData) message(spannable) } else { message(message) @@ -351,4 +356,8 @@ class MessageItemFactory @Inject constructor( VectorLinkify.addLinks(spannable, true) return spannable } + + companion object { + private const val MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT = 5 + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 0e1229eeca..dddf507453 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.utils.DebouncedClickListener +import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem @@ -32,6 +33,7 @@ class MessageItemAttributesFactory @Inject constructor( private val avatarRenderer: AvatarRenderer, private val colorProvider: ColorProvider, private val avatarSizeProvider: AvatarSizeProvider, + private val dimensionConverter: DimensionConverter, private val emojiCompatFontProvider: EmojiCompatFontProvider) { fun create(messageContent: MessageContent?, @@ -42,6 +44,7 @@ class MessageItemAttributesFactory @Inject constructor( informationData = informationData, avatarRenderer = avatarRenderer, colorProvider = colorProvider, + dimensionConverter = dimensionConverter, itemLongClickListener = View.OnLongClickListener { view -> callback?.onEventLongClicked(informationData, messageContent, view) ?: false }, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index bddee50861..461028c3d0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -29,6 +29,7 @@ import im.vector.riotx.R import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.ui.views.ReadMarkerView import im.vector.riotx.core.utils.DebouncedClickListener +import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.reactions.widget.ReactionButton @@ -172,6 +173,7 @@ abstract class AbsMessageItem : BaseEventItem() { val informationData: MessageInformationData, val avatarRenderer: AvatarRenderer, val colorProvider: ColorProvider, + val dimensionConverter: DimensionConverter, val itemLongClickListener: View.OnLongClickListener? = null, val itemClickListener: View.OnClickListener? = null, val memberClickListener: View.OnClickListener? = null, From 3622c0ecb428711edc3bd82c3640bcbd9cf5f326 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Oct 2019 15:42:22 +0200 Subject: [PATCH 04/30] Mark all as read --- CHANGES.md | 1 + .../android/api/session/room/RoomService.kt | 5 +++ .../session/room/DefaultRoomService.kt | 10 ++++++ .../internal/session/room/RoomModule.kt | 17 ++++----- .../session/room/read/MarkAllRoomsReadTask.kt | 35 +++++++++++++++++++ .../session/sync/ReadReceiptHandler.kt | 2 +- .../api/pushrules/PushrulesConditionTest.kt | 20 ++++++++--- .../debug/DebugMaterialThemeActivity.kt | 2 +- .../riotx/features/home/HomeActivity.kt | 2 +- .../home/room/list/RoomListActions.kt | 6 +--- .../home/room/list/RoomListFragment.kt | 14 ++++++++ .../home/room/list/RoomListViewModel.kt | 11 ++++++ vector/src/main/res/menu/room_list.xml | 9 +++++ vector/src/main/res/menu/vector_home.xml | 24 ------------- 14 files changed, 111 insertions(+), 47 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/MarkAllRoomsReadTask.kt create mode 100644 vector/src/main/res/menu/room_list.xml delete mode 100755 vector/src/main/res/menu/vector_home.xml diff --git a/CHANGES.md b/CHANGES.md index 42fb2cc291..dd7f47de03 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Improvements: - Persist active tab between sessions (#503) - Do not upload file too big for the homeserver (#587) - Handle read markers (#84) + - Mark all messages as read (#396) Other changes: - Accessibility improvements to read receipts in the room timeline and reactions emoji chooser 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 175d393c86..c7fedb2627 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 @@ -53,4 +53,9 @@ interface RoomService { * @return the [LiveData] of [RoomSummary] */ fun liveRoomSummaries(): LiveData> + + /** + * Mark all rooms as read + */ + fun markAllAsRead(roomIds: List, callback: MatrixCallback): Cancelable } 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 c64676c8c2..962b7b54d6 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 @@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields 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.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import io.realm.Realm @@ -41,6 +42,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona private val roomSummaryMapper: RoomSummaryMapper, private val createRoomTask: CreateRoomTask, private val joinRoomTask: JoinRoomTask, + private val markAllRoomsReadTask: MarkAllRoomsReadTask, private val roomFactory: RoomFactory, private val taskExecutor: TaskExecutor) : RoomService { @@ -80,4 +82,12 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona } .executeBy(taskExecutor) } + + override fun markAllAsRead(roomIds: List, callback: MatrixCallback): Cancelable { + return markAllRoomsReadTask + .configureWith(MarkAllRoomsReadTask.Params(roomIds)) { + this.callback = callback + } + .executeBy(taskExecutor) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index b3db84c9c6..5755d6b46e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -40,22 +40,14 @@ import im.vector.matrix.android.internal.session.room.membership.leaving.Default import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask import im.vector.matrix.android.internal.session.room.prune.DefaultPruneEventTask import im.vector.matrix.android.internal.session.room.prune.PruneEventTask +import im.vector.matrix.android.internal.session.room.read.DefaultMarkAllRoomsReadTask import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask +import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask -import im.vector.matrix.android.internal.session.room.relation.DefaultFetchEditHistoryTask -import im.vector.matrix.android.internal.session.room.relation.DefaultFindReactionEventForUndoTask -import im.vector.matrix.android.internal.session.room.relation.DefaultUpdateQuickReactionTask -import im.vector.matrix.android.internal.session.room.relation.FetchEditHistoryTask -import im.vector.matrix.android.internal.session.room.relation.FindReactionEventForUndoTask -import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask +import im.vector.matrix.android.internal.session.room.relation.* import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.timeline.* -import im.vector.matrix.android.internal.session.room.timeline.ClearUnlinkedEventsTask -import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask -import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask -import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask -import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import retrofit2.Retrofit @Module @@ -110,6 +102,9 @@ internal abstract class RoomModule { @Binds abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask + @Binds + abstract fun bindMarkAllRoomsReadTask(markAllRoomsReadTask: DefaultMarkAllRoomsReadTask): MarkAllRoomsReadTask + @Binds abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/MarkAllRoomsReadTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/MarkAllRoomsReadTask.kt new file mode 100644 index 0000000000..99376a981a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/read/MarkAllRoomsReadTask.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.matrix.android.internal.session.room.read + +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface MarkAllRoomsReadTask : Task { + data class Params( + val roomIds: List + ) +} + +internal class DefaultMarkAllRoomsReadTask @Inject constructor(private val readMarkersTask: SetReadMarkersTask) : MarkAllRoomsReadTask { + + override suspend fun execute(params: MarkAllRoomsReadTask.Params) { + params.roomIds.forEach { roomId -> + readMarkersTask.execute(SetReadMarkersTask.Params(roomId, markAllAsRead = true)) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt index 4dbcc7168f..62fbd42ed5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/ReadReceiptHandler.kt @@ -25,7 +25,7 @@ import io.realm.Realm import timber.log.Timber import javax.inject.Inject -// the receipts dictionnaries +// the receipts dictionaries // key : $EventId // value : dict key $UserId // value dict key ts diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index 36aded79ad..882e171e4a 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.api.util.Optional import org.junit.Assert import org.junit.Test @@ -172,7 +173,6 @@ class PushrulesConditionTest { } class MockRoomService() : RoomService { - override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } @@ -192,9 +192,21 @@ class PushrulesConditionTest { override fun liveRoomSummaries(): LiveData> { return MutableLiveData() } + + override fun markAllAsRead(roomIds: List, callback: MatrixCallback): Cancelable { + TODO("not implemented") // To change body of created functions use File | Settings | File Templates. + } } class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room { + override fun getReadMarkerLive(): LiveData> { + TODO("not implemented") // To change body of created functions use File | Settings | File Templates. + } + + override fun getMyReadReceiptLive(): LiveData> { + TODO("not implemented") // To change body of created functions use File | Settings | File Templates. + } + override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } @@ -242,7 +254,7 @@ class PushrulesConditionTest { override fun fetchEditHistory(eventId: String, callback: MatrixCallback>) { } - override fun liveTimeLineEvent(eventId: String): LiveData { + override fun getTimeLineEventLive(eventId: String): LiveData> { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } @@ -250,7 +262,7 @@ class PushrulesConditionTest { return _numberOfJoinedMembers } - override fun liveRoomSummary(): LiveData { + override fun getRoomSummaryLive(): LiveData> { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } @@ -347,7 +359,7 @@ class PushrulesConditionTest { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } - override fun getEventSummaryLive(eventId: String): LiveData { + override fun getEventSummaryLive(eventId: String): LiveData> { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMaterialThemeActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMaterialThemeActivity.kt index ab6b86801a..542b0a1cbb 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMaterialThemeActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMaterialThemeActivity.kt @@ -59,7 +59,7 @@ abstract class DebugMaterialThemeActivity : AppCompatActivity() { } override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.vector_home, menu) + menuInflater.inflate(R.menu.home, menu) return true } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 9071b51acf..1e0121a500 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -182,7 +182,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { } } - return true + return super.onOptionsItemSelected(item) } override fun onBackPressed() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListActions.kt index 7302d9d2b8..8271086421 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListActions.kt @@ -19,14 +19,10 @@ package im.vector.riotx.features.home.room.list import im.vector.matrix.android.api.session.room.model.RoomSummary sealed class RoomListActions { - data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions() - data class ToggleCategory(val category: RoomCategory) : RoomListActions() - data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListActions() - data class RejectInvitation(val roomSummary: RoomSummary) : RoomListActions() - data class FilterWith(val filter: String) : RoomListActions() + object MarkAllRoomsRead : RoomListActions() } 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 d0957f752b..75bf15efa7 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 @@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.list import android.os.Bundle import android.os.Parcelable +import android.view.MenuItem import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -78,6 +79,19 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O injector.inject(this) } + override fun getMenuRes() = R.menu.room_list + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_home_mark_all_as_read -> { + roomListViewModel.accept(RoomListActions.MarkAllRoomsRead) + return true + } + } + + return super.onOptionsItemSelected(item) + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setupCreateRoomButton() 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 0edf3b72e0..292e5405c4 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 @@ -78,6 +78,7 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room is RoomListActions.AcceptInvitation -> handleAcceptInvitation(action) is RoomListActions.RejectInvitation -> handleRejectInvitation(action) is RoomListActions.FilterWith -> handleFilter(action) + is RoomListActions.MarkAllRoomsRead -> handleMarkAllRoomsRead() } } @@ -193,6 +194,16 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room }) } + private fun handleMarkAllRoomsRead() = withState { state -> + state.asyncFilteredRooms.invoke() + ?.flatMap { it.value } + ?.filter { it.membership == Membership.JOIN } + ?.map { it.roomId } + ?.toList() + ?.let { session.markAllAsRead(it, object : MatrixCallback {}) } + } + + private fun buildRoomSummaries(rooms: List): RoomSummaries { val invites = ArrayList() val favourites = ArrayList() diff --git a/vector/src/main/res/menu/room_list.xml b/vector/src/main/res/menu/room_list.xml new file mode 100644 index 0000000000..60ffdcd87b --- /dev/null +++ b/vector/src/main/res/menu/room_list.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/vector_home.xml b/vector/src/main/res/menu/vector_home.xml deleted file mode 100755 index fe24e6fdf5..0000000000 --- a/vector/src/main/res/menu/vector_home.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - From f7f97e2098577590430745f849499d6752a5b037 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 12 Oct 2019 16:04:47 +0100 Subject: [PATCH 05/30] Typos Signed-off-by: Dominic Fischer --- .../internal/crypto/DefaultCryptoService.kt | 1 - .../algorithms/megolm/MXMegolmEncryption.kt | 2 - .../features/settings/VectorPreferences.kt | 45 ------------------- 3 files changed, 48 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 853360d12f..599e5fef81 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -785,7 +785,6 @@ internal class DefaultCryptoService @Inject constructor( * * @param password the password * @param anIterationCount the encryption iteration count (0 means no encryption) - * @param callback the exported keys */ private suspend fun exportRoomKeys(password: String, anIterationCount: Int): ByteArray { return withContext(coroutineDispatchers.crypto) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 2f5e657376..77a8f2ec1c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -164,7 +164,6 @@ internal class MXMegolmEncryption( * * @param session the session info * @param devicesByUser the devices map - * @param callback the asynchronous callback */ private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo, devicesByUser: Map>) { @@ -272,7 +271,6 @@ internal class MXMegolmEncryption( * This method must be called in getDecryptingThreadHandler() thread. * * @param userIds the user ids whose devices must be checked. - * @param callback the asynchronous callback */ private suspend fun getDevicesInRoom(userIds: List): MXUsersDevicesMap { // We are happy to use a cached version here: we assume that if we already diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index 5d08d7626d..4ad3f54e30 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -215,8 +215,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Clear the preferences. - * - * @param context the context */ fun clearPreferences() { val keysToKeep = HashSet(mKeysToKeepAfterLogout) @@ -266,7 +264,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if we have already asked the user to disable battery optimisations on android >= M devices. * - * @param context the context * @return true if it was already requested */ fun didAskUserToIgnoreBatteryOptimizations(): Boolean { @@ -275,8 +272,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Mark as requested the question to disable battery optimisations. - * - * @param context the context */ fun setDidAskUserToIgnoreBatteryOptimizations() { defaultPrefs.edit { @@ -297,7 +292,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the timestamp must be displayed in 12h format * - * @param context the context * @return true if the time must be displayed in 12h format */ fun displayTimeIn12hFormat(): Boolean { @@ -307,7 +301,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the join and leave membership events should be shown in the messages list. * - * @param context the context * @return true if the join and leave membership events should be shown in the messages list */ fun showJoinLeaveMessages(): Boolean { @@ -317,7 +310,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the avatar and display name events should be shown in the messages list. * - * @param context the context * @return true true if the avatar and display name events should be shown in the messages list. */ fun showAvatarDisplayNameChangeMessages(): Boolean { @@ -327,7 +319,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells the native camera to take a photo or record a video. * - * @param context the context * @return true to use the native camera app to record video or take photo. */ fun useNativeCamera(): Boolean { @@ -337,7 +328,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the send voice feature is enabled. * - * @param context the context * @return true if the send voice feature is enabled. */ fun isSendVoiceFeatureEnabled(): Boolean { @@ -347,7 +337,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells which compression level to use by default * - * @param context the context * @return the selected compression level */ fun getSelectedDefaultMediaCompressionLevel(): Int { @@ -357,7 +346,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells which media source to use by default * - * @param context the context * @return the selected media source */ fun getSelectedDefaultMediaSource(): Int { @@ -367,7 +355,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells whether to use shutter sound. * - * @param context the context * @return true if shutter sound should play */ fun useShutterSound(): Boolean { @@ -377,7 +364,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Update the notification ringtone * - * @param context the context * @param uri the new notification ringtone, or null for no RingTone */ fun setNotificationRingTone(uri: Uri?) { @@ -402,7 +388,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Provides the selected notification ring tone * - * @param context the context * @return the selected ring tone or null for no RingTone */ fun getNotificationRingTone(): Uri? { @@ -435,7 +420,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Provide the notification ringtone filename * - * @param context the context * @return the filename or null if "None" is selected */ fun getNotificationRingToneName(): String? { @@ -469,7 +453,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Enable or disable the lazy loading * - * @param context the context * @param newValue true to enable lazy loading, false to disable it */ fun setUseLazyLoading(newValue: Boolean) { @@ -481,7 +464,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the lazy loading is enabled * - * @param context the context * @return true if the lazy loading of room members is enabled */ fun useLazyLoading(): Boolean { @@ -491,7 +473,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * User explicitly refuses the lazy loading. * - * @param context the context */ fun setUserRefuseLazyLoading() { defaultPrefs.edit { @@ -502,7 +483,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the user has explicitly refused the lazy loading * - * @param context the context * @return true if the user has explicitly refuse the lazy loading of room members */ fun hasUserRefusedLazyLoading(): Boolean { @@ -512,7 +492,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the data save mode is enabled * - * @param context the context * @return true if the data save mode is enabled */ fun useDataSaveMode(): Boolean { @@ -522,7 +501,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the conf calls must be done with Jitsi. * - * @param context the context * @return true if the conference call must be done with jitsi. */ fun useJitsiConfCall(): Boolean { @@ -532,7 +510,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the application is started on boot * - * @param context the context * @return true if the application must be started on boot */ fun autoStartOnBoot(): Boolean { @@ -542,7 +519,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the application is started on boot * - * @param context the context * @param value true to start the application on boot */ fun setAutoStartOnBoot(value: Boolean) { @@ -554,7 +530,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Provides the selected saving period. * - * @param context the context * @return the selected period */ fun getSelectedMediasSavingPeriod(): Int { @@ -564,7 +539,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Updates the selected saving period. * - * @param context the context * @param index the selected period index */ fun setSelectedMediasSavingPeriod(index: Int) { @@ -576,7 +550,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Provides the minimum last access time to keep a media file. * - * @param context the context * @return the min last access time (in seconds) */ fun getMinMediasLastAccessTime(): Long { @@ -595,7 +568,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Provides the selected saving period. * - * @param context the context * @return the selected period */ fun getSelectedMediasSavingPeriodString(): String { @@ -620,7 +592,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the markdown is enabled * - * @param context the context * @return true if the markdown is enabled */ fun isMarkdownEnabled(): Boolean { @@ -630,7 +601,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Update the markdown enable status. * - * @param context the context * @param isEnabled true to enable the markdown */ fun setMarkdownEnabled(isEnabled: Boolean) { @@ -642,7 +612,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the read receipts should be shown * - * @param context the context * @return true if the read receipts should be shown */ fun showReadReceipts(): Boolean { @@ -652,7 +621,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the message timestamps must be always shown * - * @param context the context * @return true if the message timestamps must be always shown */ fun alwaysShowTimeStamps(): Boolean { @@ -662,7 +630,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the typing notifications should be sent * - * @param context the context * @return true to send the typing notifs */ fun sendTypingNotifs(): Boolean { @@ -672,7 +639,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells of the missing notifications rooms must be displayed at left (home screen) * - * @param context the context * @return true to move the missed notifications to the left side */ fun pinMissedNotifications(): Boolean { @@ -682,7 +648,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells of the unread rooms must be displayed at left (home screen) * - * @param context the context * @return true to move the unread room to the left side */ fun pinUnreadMessages(): Boolean { @@ -692,7 +657,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the phone must vibrate when mentioning * - * @param context the context * @return true */ fun vibrateWhenMentioning(): Boolean { @@ -702,7 +666,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if a dialog has been displayed to ask to use the analytics tracking (piwik, matomo, etc.). * - * @param context the context * @return true if a dialog has been displayed to ask to use the analytics tracking */ fun didAskToUseAnalytics(): Boolean { @@ -712,7 +675,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * To call if the user has been asked for analytics tracking. * - * @param context the context */ fun setDidAskToUseAnalytics() { defaultPrefs.edit { @@ -723,7 +685,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the analytics tracking is authorized (piwik, matomo, etc.). * - * @param context the context * @return true if the analytics tracking is authorized */ fun useAnalytics(): Boolean { @@ -733,7 +694,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Enable or disable the analytics tracking. * - * @param context the context * @param useAnalytics true to enable the analytics tracking */ fun setUseAnalytics(useAnalytics: Boolean) { @@ -745,7 +705,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if media should be previewed before sending * - * @param context the context * @return true to preview media */ fun previewMediaWhenSending(): Boolean { @@ -755,7 +714,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if message should be send by pressing enter on the soft keyboard * - * @param context the context * @return true to send message with enter */ fun sendMessageWithEnter(): Boolean { @@ -765,7 +723,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if the rage shake is used. * - * @param context the context * @return true if the rage shake is used */ fun useRageshake(): Boolean { @@ -775,7 +732,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Update the rage shake status. * - * @param context the context * @param isEnabled true to enable the rage shake */ fun setUseRageshake(isEnabled: Boolean) { @@ -787,7 +743,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { /** * Tells if all the events must be displayed ie even the redacted events. * - * @param context the context * @return true to display all the events even the redacted ones. */ fun displayAllEvents(): Boolean { From e4d0e0b0bf73e5085fc971d6f788d14592a65326 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Oct 2019 11:02:16 +0200 Subject: [PATCH 06/30] Update after Ganfra's review --- .../detail/timeline/factory/MessageItemFactory.kt | 13 +++++++++---- .../timeline/helper/MessageItemAttributesFactory.kt | 3 --- .../room/detail/timeline/item/AbsMessageItem.kt | 2 -- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 4619eb4c8e..0bb5c3a1d8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -53,6 +53,7 @@ import javax.inject.Inject class MessageItemFactory @Inject constructor( private val colorProvider: ColorProvider, + private val dimensionConverter: DimensionConverter, private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val htmlRenderer: Lazy, private val stringProvider: StringProvider, @@ -243,7 +244,7 @@ class MessageItemFactory @Inject constructor( return MessageTextItem_() .apply { if (informationData.hasBeenEdited) { - val spannable = annotateWithEdited(linkifiedBody, callback, attributes.dimensionConverter, informationData) + val spannable = annotateWithEdited(linkifiedBody, callback, informationData) message(spannable) } else { message(linkifiedBody) @@ -260,7 +261,6 @@ class MessageItemFactory @Inject constructor( private fun annotateWithEdited(linkifiedBody: CharSequence, callback: TimelineEventController.Callback?, - dimensionConverter: DimensionConverter, informationData: MessageInformationData): SpannableStringBuilder { val spannable = SpannableStringBuilder() spannable.append(linkifiedBody) @@ -276,7 +276,12 @@ class MessageItemFactory @Inject constructor( Spanned.SPAN_INCLUSIVE_EXCLUSIVE) // Note: text size is set to 14sp - spannable.setSpan(AbsoluteSizeSpan(dimensionConverter.spToPx(13)), editStart, editEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + spannable.setSpan( + AbsoluteSizeSpan(dimensionConverter.spToPx(13)), + editStart, + editEnd, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + spannable.setSpan(object : ClickableSpan() { override fun onClick(widget: View?) { callback?.onEditedDecorationClicked(informationData) @@ -326,7 +331,7 @@ class MessageItemFactory @Inject constructor( return MessageTextItem_() .apply { if (informationData.hasBeenEdited) { - val spannable = annotateWithEdited(message, callback, attributes.dimensionConverter, informationData) + val spannable = annotateWithEdited(message, callback, informationData) message(spannable) } else { message(message) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index dddf507453..0e1229eeca 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.utils.DebouncedClickListener -import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem @@ -33,7 +32,6 @@ class MessageItemAttributesFactory @Inject constructor( private val avatarRenderer: AvatarRenderer, private val colorProvider: ColorProvider, private val avatarSizeProvider: AvatarSizeProvider, - private val dimensionConverter: DimensionConverter, private val emojiCompatFontProvider: EmojiCompatFontProvider) { fun create(messageContent: MessageContent?, @@ -44,7 +42,6 @@ class MessageItemAttributesFactory @Inject constructor( informationData = informationData, avatarRenderer = avatarRenderer, colorProvider = colorProvider, - dimensionConverter = dimensionConverter, itemLongClickListener = View.OnLongClickListener { view -> callback?.onEventLongClicked(informationData, messageContent, view) ?: false }, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index 461028c3d0..bddee50861 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -29,7 +29,6 @@ import im.vector.riotx.R import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.ui.views.ReadMarkerView import im.vector.riotx.core.utils.DebouncedClickListener -import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.reactions.widget.ReactionButton @@ -173,7 +172,6 @@ abstract class AbsMessageItem : BaseEventItem() { val informationData: MessageInformationData, val avatarRenderer: AvatarRenderer, val colorProvider: ColorProvider, - val dimensionConverter: DimensionConverter, val itemLongClickListener: View.OnLongClickListener? = null, val itemClickListener: View.OnClickListener? = null, val memberClickListener: View.OnClickListener? = null, From 203da0f37ebbeb6e576b1a8a2ea11d8bf37c0639 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Oct 2019 16:48:31 +0200 Subject: [PATCH 07/30] Mark all as read: not for all Room list and look if there is unread rooms --- .../home/room/list/RoomListFragment.kt | 22 +++++++++++++++++++ .../home/room/list/RoomListViewModel.kt | 1 - .../home/room/list/RoomListViewState.kt | 8 +++++++ 3 files changed, 30 insertions(+), 1 deletion(-) 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 75bf15efa7..6665500676 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 @@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.list import android.os.Bundle import android.os.Parcelable +import android.view.Menu import android.view.MenuItem import androidx.annotation.StringRes import androidx.core.content.ContextCompat @@ -79,6 +80,8 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O injector.inject(this) } + private var hasUnreadRooms = false + override fun getMenuRes() = R.menu.room_list override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -92,6 +95,11 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O return super.onOptionsItemSelected(item) } + override fun onPrepareOptionsMenu(menu: Menu) { + menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms + super.onPrepareOptionsMenu(menu) + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setupCreateRoomButton() @@ -194,6 +202,20 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O is Fail -> renderFailure(state.asyncFilteredRooms.error) } roomController.update(state) + + // Mark all as read menu + when (roomListParams.displayMode) { + DisplayMode.HOME, + DisplayMode.PEOPLE, + DisplayMode.ROOMS -> { + val newValue = state.hasUnread + if (hasUnreadRooms != newValue) { + hasUnreadRooms = newValue + requireActivity().invalidateOptionsMenu() + } + } + else -> Unit + } } private fun renderSuccess(state: RoomListViewState) { 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 292e5405c4..695ab53812 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 @@ -203,7 +203,6 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room ?.let { session.markAllAsRead(it, object : MatrixCallback {}) } } - private fun buildRoomSummaries(rooms: List): RoomSummaries { val invites = ArrayList() val favourites = ArrayList() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt index 2f388b60d8..505554a8fb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt @@ -20,6 +20,7 @@ import androidx.annotation.StringRes import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotx.R @@ -67,6 +68,13 @@ data class RoomListViewState( RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded) } } + + val hasUnread: Boolean + get() = asyncFilteredRooms.invoke() + ?.flatMap { it.value } + ?.filter { it.membership == Membership.JOIN } + ?.any { it.hasUnreadMessages } + ?: false } typealias RoomSummaries = LinkedHashMap> From 3986839801283abd9278360404e7739c8d6989ee Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Oct 2019 14:25:19 +0200 Subject: [PATCH 08/30] Inject userId --- .../room/model/relation/RelationService.kt | 6 ++---- .../internal/crypto/keysbackup/KeysBackup.kt | 18 ++++++++---------- .../room/relation/DefaultRelationService.kt | 9 ++++----- .../relation/FindReactionEventForUndoTask.kt | 11 ++++++----- .../room/relation/UpdateQuickReactionTask.kt | 15 ++++++++------- .../home/room/detail/RoomDetailViewModel.kt | 4 ++-- 6 files changed, 30 insertions(+), 33 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 c6a58eeec1..14f730c2ba 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 @@ -60,11 +60,9 @@ interface RelationService { * Undo a reaction (emoji) to the targetedEvent. * @param reaction the reaction (preferably emoji) * @param targetEventId the id of the event being reacted - * @param myUserId used to know if a reaction event was made by the user */ fun undoReaction(reaction: String, - targetEventId: String, - myUserId: String) // : Cancelable + targetEventId: String) : Cancelable /** * Edit a text message body. Limited to "m.text" contentType @@ -92,7 +90,7 @@ interface RelationService { compatibilityBodyText: String = "* $newBodyText"): Cancelable /** - * Get's the edit history of the given event + * Get the edit history of the given event */ fun fetchEditHistory(eventId: String, callback: MatrixCallback>) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt index a66d50dfad..51c3527042 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt @@ -50,6 +50,7 @@ import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrap import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.Task @@ -77,6 +78,7 @@ import kotlin.random.Random @SessionScope internal class KeysBackup @Inject constructor( + @UserId private val userId: String, private val credentials: Credentials, private val cryptoStore: IMXCryptoStore, private val olmDevice: MXOlmDevice, @@ -375,8 +377,6 @@ internal class KeysBackup @Inject constructor( */ @WorkerThread private fun getKeysBackupTrustBg(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust { - val myUserId = credentials.userId - val keysBackupVersionTrust = KeysBackupVersionTrust() val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() @@ -388,7 +388,7 @@ internal class KeysBackup @Inject constructor( return keysBackupVersionTrust } - val mySigs = authData.signatures?.get(myUserId) + val mySigs = authData.signatures?.get(userId) if (mySigs.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Ignoring key backup because it lacks any signatures from this user") return keysBackupVersionTrust @@ -403,7 +403,7 @@ internal class KeysBackup @Inject constructor( } if (deviceId != null) { - val device = cryptoStore.getUserDevice(deviceId, myUserId) + val device = cryptoStore.getUserDevice(deviceId, userId) var isSignatureValid = false if (device == null) { @@ -450,10 +450,8 @@ internal class KeysBackup @Inject constructor( } else { GlobalScope.launch(coroutineDispatchers.main) { val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { - val myUserId = credentials.userId - // Get current signatures, or create an empty set - val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap() + val myUserSignatures = authData.signatures?.get(userId)?.toMutableMap() ?: HashMap() if (trust) { @@ -462,7 +460,7 @@ internal class KeysBackup @Inject constructor( val deviceSignatures = objectSigner.signObject(canonicalJson) - deviceSignatures[myUserId]?.forEach { entry -> + deviceSignatures[userId]?.forEach { entry -> myUserSignatures[entry.key] = entry.value } } else { @@ -478,7 +476,7 @@ internal class KeysBackup @Inject constructor( val newMegolmBackupAuthData = authData.copy() val newSignatures = newMegolmBackupAuthData.signatures!!.toMutableMap() - newSignatures[myUserId] = myUserSignatures + newSignatures[userId] = myUserSignatures newMegolmBackupAuthData.signatures = newSignatures @@ -1411,5 +1409,5 @@ internal class KeysBackup @Inject constructor( * DEBUG INFO * ========================================================================================== */ - override fun toString() = "KeysBackup for ${credentials.userId}" + override fun toString() = "KeysBackup for $userId" } 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 6abc7ed60e..9f6500ce51 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 @@ -75,13 +75,13 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv return CancelableWork(context, sendRelationWork.id) } - override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ { + override fun undoReaction(reaction: String, targetEventId: String): Cancelable { val params = FindReactionEventForUndoTask.Params( roomId, targetEventId, - reaction, - myUserId + reaction ) + // TODO We should avoid using MatrixCallback internally val callback = object : MatrixCallback { override fun onSuccess(data: FindReactionEventForUndoTask.Result) { if (data.redactEventId == null) { @@ -89,7 +89,6 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv // TODO? } data.redactEventId?.let { toRedact -> - val redactEvent = eventFactory.createRedactEvent(roomId, toRedact, null).also { saveLocalEcho(it) } @@ -99,7 +98,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv } } } - findReactionEventForUndoTask + return findReactionEventForUndoTask .configureWith(params) { this.retryCount = Int.MAX_VALUE this.callback = callback diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt index 997ed18492..d1abf93c21 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FindReactionEventForUndoTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryE import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.task.Task import io.realm.Realm import javax.inject.Inject @@ -29,8 +30,7 @@ internal interface FindReactionEventForUndoTask : Task - getReactionToRedact(realm, params.reaction, params.eventId, params.myUserId)?.eventId + getReactionToRedact(realm, params.reaction, params.eventId)?.eventId } return FindReactionEventForUndoTask.Result(eventId) } - private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String, userId: String): EventEntity? { + private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String): EventEntity? { val summary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() if (summary != null) { summary.reactionsSummary.where() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/UpdateQuickReactionTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/UpdateQuickReactionTask.kt index e4b71c0d42..6ec316e9a4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/UpdateQuickReactionTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/UpdateQuickReactionTask.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryE import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.task.Task import io.realm.Realm import javax.inject.Inject @@ -30,8 +31,7 @@ internal interface UpdateQuickReactionTask : Task?>? = null monarchy.doWithRealm { realm -> - res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId, params.myUserId) + res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId) } return UpdateQuickReactionTask.Result(res?.first, res?.second ?: emptyList()) } - private fun updateQuickReaction(realm: Realm, reaction: String, oppositeReaction: String, eventId: String, myUserId: String): Pair?> { + private fun updateQuickReaction(realm: Realm, reaction: String, oppositeReaction: String, eventId: String): Pair?> { // the emoji reaction has been selected, we need to check if we have reacted it or not val existingSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() ?: return Pair(reaction, null) @@ -68,7 +69,7 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(private val mo val toRedact = aggregationForOppositeReaction?.sourceEvents?.mapNotNull { // find source event val entity = EventEntity.where(realm, it).findFirst() - if (entity?.sender == myUserId) entity.eventId else null + if (entity?.sender == userId) entity.eventId else null } return Pair(reaction, toRedact) } else { @@ -77,7 +78,7 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(private val mo val toRedact = aggregationForReaction.sourceEvents.mapNotNull { // find source event val entity = EventEntity.where(realm, it).findFirst() - if (entity?.sender == myUserId) entity.eventId else null + if (entity?.sender == userId) entity.eventId else null } return Pair(null, toRedact) } 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 7db9fc41d5..9eb11d2019 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 @@ -454,14 +454,14 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleUndoReact(action: RoomDetailActions.UndoReaction) { - room.undoReaction(action.key, action.targetEventId, session.myUserId) + room.undoReaction(action.key, action.targetEventId) } private fun handleUpdateQuickReaction(action: RoomDetailActions.UpdateQuickReactAction) { if (action.add) { room.sendReaction(action.selectedReaction, action.targetEventId) } else { - room.undoReaction(action.selectedReaction, action.targetEventId, session.myUserId) + room.undoReaction(action.selectedReaction, action.targetEventId) } } From eff04be2473f1daa20efc64bab93a17cf8547275 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Oct 2019 14:26:24 +0200 Subject: [PATCH 09/30] Change order of class (no effect) --- .../vector/riotx/features/home/room/detail/RoomDetailActions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt index d42af3e50a..ac1e1df51a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt @@ -31,8 +31,8 @@ sealed class RoomDetailActions { data class TimelineEventTurnsInvisible(val event: TimelineEvent) : RoomDetailActions() data class LoadMoreTimelineEvents(val direction: Timeline.Direction) : RoomDetailActions() data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions() - data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions() data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions() + data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions() data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions() data class NavigateToEvent(val eventId: String, val highlight: Boolean) : RoomDetailActions() data class SetReadMarkerAction(val eventId: String) : RoomDetailActions() From be94b2f90ae66b2e2e377aa203dc7721e40530fb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Oct 2019 14:28:12 +0200 Subject: [PATCH 10/30] Change order of parameters (no effect) --- .../riotx/features/home/room/detail/RoomDetailActions.kt | 2 +- .../riotx/features/home/room/detail/RoomDetailFragment.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt index ac1e1df51a..91614d5d74 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt @@ -30,7 +30,7 @@ sealed class RoomDetailActions { data class TimelineEventTurnsVisible(val event: TimelineEvent) : RoomDetailActions() data class TimelineEventTurnsInvisible(val event: TimelineEvent) : RoomDetailActions() data class LoadMoreTimelineEvents(val direction: Timeline.Direction) : RoomDetailActions() - data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions() + data class SendReaction(val targetEventId: String, val reaction: String) : RoomDetailActions() data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions() data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions() data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions() 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 6907af9ff6..f09fbff09f 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 @@ -420,7 +420,7 @@ class RoomDetailFragment : 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? - roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId)) + roomDetailViewModel.process(RoomDetailActions.SendReaction(eventId, reaction)) } } } @@ -909,7 +909,7 @@ class RoomDetailFragment : override fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean) { if (on) { // we should test the current real state of reaction on this event - roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, informationData.eventId)) + roomDetailViewModel.process(RoomDetailActions.SendReaction(informationData.eventId, reaction)) } else { // I need to redact a reaction roomDetailViewModel.process(RoomDetailActions.UndoReaction(informationData.eventId, reaction)) From 8078c39d6ed53d30b11e3de2c3588efc8dab65ab Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Oct 2019 14:29:32 +0200 Subject: [PATCH 11/30] Rename parameter --- .../vector/riotx/features/home/room/detail/RoomDetailActions.kt | 2 +- .../riotx/features/home/room/detail/RoomDetailViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt index 91614d5d74..d032182994 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt @@ -31,7 +31,7 @@ sealed class RoomDetailActions { data class TimelineEventTurnsInvisible(val event: TimelineEvent) : RoomDetailActions() data class LoadMoreTimelineEvents(val direction: Timeline.Direction) : RoomDetailActions() data class SendReaction(val targetEventId: String, val reaction: String) : RoomDetailActions() - data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions() + data class UndoReaction(val targetEventId: String, val reaction: String, val reason: String? = "") : RoomDetailActions() data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions() data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions() data class NavigateToEvent(val eventId: String, val highlight: Boolean) : RoomDetailActions() 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 9eb11d2019..58cfb97b87 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 @@ -454,7 +454,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleUndoReact(action: RoomDetailActions.UndoReaction) { - room.undoReaction(action.key, action.targetEventId) + room.undoReaction(action.reaction, action.targetEventId) } private fun handleUpdateQuickReaction(action: RoomDetailActions.UpdateQuickReactAction) { From 17636019e045cdc820d24a977e02b219df72b32d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Oct 2019 14:32:34 +0200 Subject: [PATCH 12/30] Change order of parameters --- .../session/room/model/relation/RelationService.kt | 12 ++++++------ .../session/room/relation/DefaultRelationService.kt | 4 ++-- .../android/api/pushrules/PushrulesConditionTest.kt | 2 +- .../features/home/room/detail/RoomDetailViewModel.kt | 8 ++++---- 4 files changed, 13 insertions(+), 13 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 14f730c2ba..5af5183dfa 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,19 +50,19 @@ interface RelationService { /** * Sends a reaction (emoji) to the targetedEvent. - * @param reaction the reaction (preferably emoji) * @param targetEventId the id of the event being reacted + * @param reaction the reaction (preferably emoji) */ - fun sendReaction(reaction: String, - targetEventId: String): Cancelable + fun sendReaction(targetEventId: String, + reaction: String): Cancelable /** * Undo a reaction (emoji) to the targetedEvent. - * @param reaction the reaction (preferably emoji) * @param targetEventId the id of the event being reacted + * @param reaction the reaction (preferably emoji) */ - fun undoReaction(reaction: String, - targetEventId: String) : Cancelable + fun undoReaction(targetEventId: String, + reaction: String): Cancelable /** * Edit a text message body. Limited to "m.text" contentType 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 9f6500ce51..68669171c7 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 @@ -65,7 +65,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv fun create(roomId: String): RelationService } - override fun sendReaction(reaction: String, targetEventId: String): Cancelable { + override fun sendReaction(targetEventId: String, reaction: String): Cancelable { val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction) .also { saveLocalEcho(it) @@ -75,7 +75,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv return CancelableWork(context, sendRelationWork.id) } - override fun undoReaction(reaction: String, targetEventId: String): Cancelable { + override fun undoReaction(targetEventId: String, reaction: String): Cancelable { val params = FindReactionEventForUndoTask.Params( roomId, targetEventId, diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index 36aded79ad..c7543c3a50 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -330,7 +330,7 @@ class PushrulesConditionTest { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } - override fun sendReaction(reaction: String, targetEventId: String): Cancelable { + override fun sendReaction(targetEventId: String, reaction: String): Cancelable { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } 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 58cfb97b87..57a2c76f61 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 @@ -445,7 +445,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleSendReaction(action: RoomDetailActions.SendReaction) { - room.sendReaction(action.reaction, action.targetEventId) + room.sendReaction(action.targetEventId, action.reaction) } private fun handleRedactEvent(action: RoomDetailActions.RedactAction) { @@ -454,14 +454,14 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleUndoReact(action: RoomDetailActions.UndoReaction) { - room.undoReaction(action.reaction, action.targetEventId) + room.undoReaction(action.targetEventId, action.reaction) } private fun handleUpdateQuickReaction(action: RoomDetailActions.UpdateQuickReactAction) { if (action.add) { - room.sendReaction(action.selectedReaction, action.targetEventId) + room.sendReaction(action.targetEventId, action.selectedReaction) } else { - room.undoReaction(action.selectedReaction, action.targetEventId) + room.undoReaction(action.targetEventId, action.selectedReaction) } } From cd0a40c18dbb69d20978ce59db9f0634e67935b7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 18 Oct 2019 14:34:44 +0200 Subject: [PATCH 13/30] Fix compil test issue --- .../matrix/android/api/pushrules/PushrulesConditionTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt index c7543c3a50..b2e2bfbf5f 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/api/pushrules/PushrulesConditionTest.kt @@ -334,7 +334,7 @@ class PushrulesConditionTest { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } - override fun undoReaction(reaction: String, targetEventId: String, myUserId: String) { + override fun undoReaction(targetEventId: String, reaction: String): Cancelable { TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } From 1dacfa6744142e90a4df1eae73e0fd52a54ed782 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Oct 2019 14:36:10 +0200 Subject: [PATCH 14/30] Rework message menu bottom sheet: remove sub Fragment and use Epoxy - Also move some class to some dedicated package --- .../vector/riotx/core/di/ScreenComponent.kt | 8 +- .../VectorBaseBottomSheetDialogFragment.kt | 3 +- .../home/room/detail/RoomDetailFragment.kt | 6 +- .../DisplayReadReceiptsBottomSheet.kt | 2 +- .../timeline/action/BottomSheetItemAction.kt | 55 ++++ .../action/BottomSheetItemMessagePreview.kt | 59 ++++ .../action/BottomSheetItemQuickReactions.kt | 80 +++++ .../action/BottomSheetItemSendState.kt | 52 +++ .../action/BottomSheetItemSeparator.kt | 27 ++ .../action/MessageActionsBottomSheet.kt | 98 +----- .../action/MessageActionsEpoxyController.kt | 104 ++++++ .../action/MessageActionsViewModel.kt | 297 +++++++++++++++--- .../timeline/action/MessageMenuFragment.kt | 104 ------ .../timeline/action/MessageMenuViewModel.kt | 279 ---------------- .../timeline/action/QuickReactionFragment.kt | 89 ------ .../timeline/action/QuickReactionViewModel.kt | 96 ------ .../detail/timeline/action/SimpleAction.kt | 43 +++ .../ViewEditHistoryBottomSheet.kt | 4 +- .../ViewEditHistoryEpoxyController.kt | 4 +- .../ViewEditHistoryViewModel.kt | 3 +- .../ReactionInfoSimpleItem.kt | 2 +- .../ViewReactionsBottomSheet.kt} | 14 +- .../ViewReactionsEpoxyController.kt | 2 +- .../ViewReactionsViewModel.kt} | 19 +- .../bottom_sheet_epoxylist_with_title.xml | 3 +- .../bottom_sheet_generic_recycler_epoxy.xml | 22 ++ .../layout/bottom_sheet_message_actions.xml | 149 --------- .../main/res/layout/fragment_message_menu.xml | 5 - ...ction.xml => item_bottom_sheet_action.xml} | 2 +- .../res/layout/item_bottom_sheet_divider.xml | 6 + .../item_bottom_sheet_message_preview.xml | 78 +++++ .../item_bottom_sheet_message_status.xml | 33 ++ ...l => item_bottom_sheet_quick_reaction.xml} | 0 33 files changed, 872 insertions(+), 876 deletions(-) rename vector/src/main/java/im/vector/riotx/{features/home/room/detail/timeline/action => core/platform}/VectorBaseBottomSheetDialogFragment.kt (95%) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemAction.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemMessagePreview.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemQuickReactions.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemSendState.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemSeparator.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuFragment.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/QuickReactionFragment.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/QuickReactionViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt rename vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/{action => edithistory}/ViewEditHistoryBottomSheet.kt (93%) rename vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/{action => edithistory}/ViewEditHistoryEpoxyController.kt (98%) rename vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/{action => edithistory}/ViewEditHistoryViewModel.kt (96%) rename vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/{action => reactions}/ReactionInfoSimpleItem.kt (96%) rename vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/{action/ViewReactionBottomSheet.kt => reactions/ViewReactionsBottomSheet.kt} (82%) rename vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/{action => reactions}/ViewReactionsEpoxyController.kt (96%) rename vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/{action/ViewReactionViewModel.kt => reactions/ViewReactionsViewModel.kt} (83%) create mode 100644 vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml delete mode 100644 vector/src/main/res/layout/bottom_sheet_message_actions.xml delete mode 100644 vector/src/main/res/layout/fragment_message_menu.xml rename vector/src/main/res/layout/{adapter_item_action.xml => item_bottom_sheet_action.xml} (97%) create mode 100644 vector/src/main/res/layout/item_bottom_sheet_divider.xml create mode 100644 vector/src/main/res/layout/item_bottom_sheet_message_preview.xml create mode 100644 vector/src/main/res/layout/item_bottom_sheet_message_status.xml rename vector/src/main/res/layout/{adapter_item_action_quick_reaction.xml => item_bottom_sheet_quick_reaction.xml} (100%) diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index 3b18d3042e..2cbc3d2a8b 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -42,6 +42,8 @@ import im.vector.riotx.features.home.group.GroupListFragment import im.vector.riotx.features.home.room.detail.RoomDetailFragment import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.riotx.features.home.room.detail.timeline.action.* +import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet +import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.invite.VectorInviteView @@ -103,12 +105,10 @@ interface ScreenComponent { fun inject(messageActionsBottomSheet: MessageActionsBottomSheet) - fun inject(viewReactionBottomSheet: ViewReactionBottomSheet) + fun inject(viewReactionsBottomSheet: ViewReactionsBottomSheet) fun inject(viewEditHistoryBottomSheet: ViewEditHistoryBottomSheet) - fun inject(messageMenuFragment: MessageMenuFragment) - fun inject(vectorSettingsActivity: VectorSettingsActivity) fun inject(createRoomFragment: CreateRoomFragment) @@ -135,8 +135,6 @@ interface ScreenComponent { fun inject(sasVerificationIncomingFragment: SASVerificationIncomingFragment) - fun inject(quickReactionFragment: QuickReactionFragment) - fun inject(emojiReactionPickerActivity: EmojiReactionPickerActivity) fun inject(loginActivity: LoginActivity) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt similarity index 95% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/VectorBaseBottomSheetDialogFragment.kt rename to vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt index 026cb3ba1c..892f7b0daa 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.timeline.action +package im.vector.riotx.core.platform import android.content.Context import android.os.Bundle @@ -24,7 +24,6 @@ import com.airbnb.mvrx.MvRxViewModelStore import com.google.android.material.bottomsheet.BottomSheetDialogFragment import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.ScreenComponent -import im.vector.riotx.core.platform.VectorBaseActivity import java.util.* /** 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 f09fbff09f..7447bf92e6 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 @@ -94,7 +94,9 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.action.* +import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet import im.vector.riotx.features.home.room.detail.timeline.item.* +import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.riotx.features.html.EventHtmlRenderer import im.vector.riotx.features.html.PillImageSpan import im.vector.riotx.features.invite.VectorInviteView @@ -917,7 +919,7 @@ class RoomDetailFragment : } override fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String) { - ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, informationData) + ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, informationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } @@ -970,7 +972,7 @@ class RoomDetailFragment : startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE) } is SimpleAction.ViewReactions -> { - ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) + ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } is SimpleAction.Copy -> { 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 510fc1e26f..80539d73e4 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 @@ -30,7 +30,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.features.home.room.detail.timeline.action.VectorBaseBottomSheetDialogFragment +import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.* diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemAction.kt new file mode 100644 index 0000000000..8ee7460d53 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemAction.kt @@ -0,0 +1,55 @@ +/* + * 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 android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +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 + +/** + * A action for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_bottom_sheet_action) +abstract class BottomSheetItemAction : VectorEpoxyModel() { + + @EpoxyAttribute + @DrawableRes + var iconRes: Int = 0 + @EpoxyAttribute + var textRes: Int = 0 + @EpoxyAttribute + lateinit var listener: View.OnClickListener + + override fun bind(holder: Holder) { + holder.view.setOnClickListener { + listener.onClick(it) + } + + holder.icon.setImageResource(iconRes) + holder.text.setText(textRes) + } + + class Holder : VectorEpoxyHolder() { + val icon by bind(R.id.action_icon) + val text by bind(R.id.action_title) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemMessagePreview.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemMessagePreview.kt new file mode 100644 index 0000000000..d37aa43770 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemMessagePreview.kt @@ -0,0 +1,59 @@ +/* + * 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 android.widget.ImageView +import android.widget.TextView +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.core.extensions.setTextOrHide +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData + +/** + * A message preview for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_bottom_sheet_message_preview) +abstract class BottomSheetItemMessagePreview : VectorEpoxyModel() { + + @EpoxyAttribute + lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute + lateinit var informationData: MessageInformationData + @EpoxyAttribute + var senderName: String? = null + @EpoxyAttribute + lateinit var body: CharSequence + @EpoxyAttribute + var time: CharSequence? = null + + override fun bind(holder: Holder) { + avatarRenderer.render(informationData.avatarUrl, informationData.senderId, senderName, holder.avatar) + holder.sender.setTextOrHide(senderName) + holder.body.text = body + holder.timestamp.setTextOrHide(time) + } + + class Holder : VectorEpoxyHolder() { + val avatar by bind(R.id.bottom_sheet_message_preview_avatar) + val sender by bind(R.id.bottom_sheet_message_preview_sender) + val body by bind(R.id.bottom_sheet_message_preview_body) + val timestamp by bind(R.id.bottom_sheet_message_preview_timestamp) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemQuickReactions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemQuickReactions.kt new file mode 100644 index 0000000000..3aafa7c974 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemQuickReactions.kt @@ -0,0 +1,80 @@ +/* + * 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 android.graphics.Typeface +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.EmojiCompatFontProvider +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +/** + * A quick reaction list for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_bottom_sheet_quick_reaction) +abstract class BottomSheetItemQuickReactions : VectorEpoxyModel() { + + @EpoxyAttribute + lateinit var fontProvider: EmojiCompatFontProvider + @EpoxyAttribute + lateinit var texts: List + @EpoxyAttribute + lateinit var selecteds: List + @EpoxyAttribute + var listener: Listener? = null + + override fun bind(holder: Holder) { + holder.textViews.forEachIndexed { index, textView -> + textView.typeface = fontProvider.typeface ?: Typeface.DEFAULT + textView.text = texts[index] + textView.alpha = if (selecteds[index]) 0.2f else 1f + + textView.setOnClickListener { + listener?.didSelect(texts[index], !selecteds[index]) + } + } + } + + class Holder : VectorEpoxyHolder() { + private val quickReaction0 by bind(R.id.quickReaction0) + private val quickReaction1 by bind(R.id.quickReaction1) + private val quickReaction2 by bind(R.id.quickReaction2) + private val quickReaction3 by bind(R.id.quickReaction3) + private val quickReaction4 by bind(R.id.quickReaction4) + private val quickReaction5 by bind(R.id.quickReaction5) + private val quickReaction6 by bind(R.id.quickReaction6) + private val quickReaction7 by bind(R.id.quickReaction7) + + val textViews + get() = listOf( + quickReaction0, + quickReaction1, + quickReaction2, + quickReaction3, + quickReaction4, + quickReaction5, + quickReaction6, + quickReaction7 + ) + } + + interface Listener { + fun didSelect(emoji: String, selected: Boolean) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemSendState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemSendState.kt new file mode 100644 index 0000000000..86a5512349 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemSendState.kt @@ -0,0 +1,52 @@ +/* + * 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 android.view.View +import android.widget.TextView +import androidx.annotation.DrawableRes +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 + +/** + * A send state for bottom sheet. + */ +@EpoxyModelClass(layout = R.layout.item_bottom_sheet_message_status) +abstract class BottomSheetItemSendState : VectorEpoxyModel() { + + @EpoxyAttribute + var showProgress: Boolean = false + @EpoxyAttribute + lateinit var text: CharSequence + @EpoxyAttribute + @DrawableRes + var drawableStart: Int = 0 + + override fun bind(holder: Holder) { + holder.progress.isVisible = showProgress + holder.text.setCompoundDrawablesWithIntrinsicBounds(drawableStart, 0, 0, 0) + holder.text.text = text + } + + class Holder : VectorEpoxyHolder() { + val progress by bind(R.id.messageStatusProgress) + val text by bind(R.id.messageStatusText) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemSeparator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemSeparator.kt new file mode 100644 index 0000000000..f09f68b714 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemSeparator.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.riotx.features.home.room.detail.timeline.action + +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +@EpoxyModelClass(layout = R.layout.item_bottom_sheet_divider) +abstract class BottomSheetItemSeparator : VectorEpoxyModel() { + + class Holder : VectorEpoxyHolder() +} 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 11f3207e32..39116b59b5 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 @@ -21,92 +21,53 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.TextView -import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProviders -import butterknife.BindView -import butterknife.ButterKnife -import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog +import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData -import kotlinx.android.synthetic.main.bottom_sheet_message_actions.* +import kotlinx.android.synthetic.main.bottom_sheet_generic_recycler_epoxy.* import javax.inject.Inject /** * Bottom sheet fragment that shows a message preview with list of contextual actions - * (Includes fragments for quick reactions and list of actions) */ -class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() { +class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), MessageActionsEpoxyController.MessageActionsEpoxyControllerListener { + @Inject lateinit var messageActionViewModelFactory: MessageActionsViewModel.Factory + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var fontProvider: EmojiCompatFontProvider - @Inject - lateinit var messageActionViewModelFactory: MessageActionsViewModel.Factory - @Inject - lateinit var avatarRenderer: AvatarRenderer private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class) + private lateinit var messageActionsEpoxyController: MessageActionsEpoxyController private lateinit var actionHandlerModel: ActionsHandler - @BindView(R.id.bottom_sheet_message_preview_avatar) - lateinit var senderAvatarImageView: ImageView - - @BindView(R.id.bottom_sheet_message_preview_sender) - lateinit var senderNameTextView: TextView - - @BindView(R.id.bottom_sheet_message_preview_timestamp) - lateinit var messageTimestampText: TextView - - @BindView(R.id.bottom_sheet_message_preview_body) - lateinit var messageBodyTextView: TextView - override fun injectWith(screenComponent: ScreenComponent) { screenComponent.inject(this) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_message_actions, container, false) - ButterKnife.bind(this, view) - return view + return inflater.inflate(R.layout.bottom_sheet_generic_recycler_epoxy, container, false) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) actionHandlerModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) - val cfm = childFragmentManager - var menuActionFragment = cfm.findFragmentByTag("MenuActionFragment") as? MessageMenuFragment - if (menuActionFragment == null) { - menuActionFragment = MessageMenuFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as TimelineEventFragmentArgs) - cfm.beginTransaction() - .replace(R.id.bottom_sheet_menu_container, menuActionFragment, "MenuActionFragment") - .commit() - } - menuActionFragment.interactionListener = object : MessageMenuFragment.InteractionListener { - override fun didSelectMenuAction(simpleAction: SimpleAction) { - actionHandlerModel.fireAction(simpleAction) - dismiss() - } - } + messageActionsEpoxyController = MessageActionsEpoxyController(requireContext(), avatarRenderer, fontProvider) + bottomSheetEpoxyRecyclerView.setController(messageActionsEpoxyController) + messageActionsEpoxyController.listener = this + } - var quickReactionFragment = cfm.findFragmentByTag("QuickReaction") as? QuickReactionFragment - if (quickReactionFragment == null) { - quickReactionFragment = QuickReactionFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as TimelineEventFragmentArgs) - cfm.beginTransaction() - .replace(R.id.bottom_sheet_quick_reaction_container, quickReactionFragment, "QuickReaction") - .commit() - } - quickReactionFragment.interactionListener = object : QuickReactionFragment.InteractionListener { - override fun didQuickReactWith(clickedOn: String, add: Boolean, eventId: String) { - actionHandlerModel.fireAction(SimpleAction.QuickReact(eventId, clickedOn, add)) - dismiss() - } - } + override fun didSelectMenuAction(simpleAction: SimpleAction) { + actionHandlerModel.fireAction(simpleAction) + dismiss() } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { @@ -124,32 +85,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() { } override fun invalidate() = withState(viewModel) { - val body = viewModel.resolveBody(it) - if (body != null) { - bottom_sheet_message_preview.isVisible = true - senderNameTextView.text = it.senderName() - messageBodyTextView.text = body - messageTimestampText.text = it.time() - avatarRenderer.render(it.informationData.avatarUrl, it.informationData.senderId, it.senderName(), senderAvatarImageView) - } else { - bottom_sheet_message_preview.isVisible = false - } - quickReactBottomDivider.isVisible = it.canReact() - bottom_sheet_quick_reaction_container.isVisible = it.canReact() - if (it.informationData.sendState.isSending()) { - messageStatusInfo.isVisible = true - messageStatusProgress.isVisible = true - messageStatusText.text = getString(R.string.event_status_sending_message) - messageStatusText.setCompoundDrawables(null, null, null, null) - } else if (it.informationData.sendState.hasFailed()) { - messageStatusInfo.isVisible = true - messageStatusProgress.isVisible = false - messageStatusText.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_warning_small, 0, 0, 0) - messageStatusText.text = getString(R.string.unable_to_send_message) - } else { - messageStatusInfo.isVisible = false - } - return@withState + messageActionsEpoxyController.setData(it) } companion object { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt new file mode 100644 index 0000000000..1de7302820 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -0,0 +1,104 @@ +/* + * 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 android.content.Context +import android.view.View +import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Success +import im.vector.riotx.EmojiCompatFontProvider +import im.vector.riotx.R +import im.vector.riotx.features.home.AvatarRenderer + +/** + * Epoxy controller for message action list + */ +class MessageActionsEpoxyController(private val context: Context, + private val avatarRenderer: AvatarRenderer, + private val fontProvider: EmojiCompatFontProvider) : TypedEpoxyController() { + + var listener: MessageActionsEpoxyControllerListener? = null + + override fun buildModels(state: MessageActionState) { + // Message preview + val body = state.messageBody + if (body != null) { + bottomSheetItemMessagePreview { + id("preview") + avatarRenderer(avatarRenderer) + informationData(state.informationData) + senderName(state.senderName()) + body(body) + time(state.time()) + } + } + + // Send state + if (state.informationData.sendState.isSending()) { + bottomSheetItemSendState { + id("send_state") + showProgress(true) + text(context.getString(R.string.event_status_sending_message)) + } + } else if (state.informationData.sendState.hasFailed()) { + bottomSheetItemSendState { + id("send_state") + showProgress(false) + text(context.getString(R.string.unable_to_send_message)) + drawableStart(R.drawable.ic_warning_small) + } + } + + // Quick reactions + if (state.canReact() && state.quickStates is Success) { + // Separator + bottomSheetItemSeparator { + id("reaction_separator") + } + + bottomSheetItemQuickReactions { + id("quick_reaction") + fontProvider(fontProvider) + texts(state.quickStates()?.map { it.reaction }.orEmpty()) + selecteds(state.quickStates()?.map { it.isSelected }.orEmpty()) + listener(object : BottomSheetItemQuickReactions.Listener { + override fun didSelect(emoji: String, selected: Boolean) { + listener?.didSelectMenuAction(SimpleAction.QuickReact(state.eventId, emoji, selected)) + } + }) + } + } + + // Separator + bottomSheetItemSeparator { + id("actions_separator") + } + + // Action + state.actions()?.forEachIndexed { index, action -> + bottomSheetItemAction { + id("action_$index") + iconRes(action.iconResId) + textRes(action.titleRes) + listener(View.OnClickListener { listener?.didSelectMenuAction(action) }) + } + } + } + + interface MessageActionsEpoxyControllerListener { + fun didSelectMenuAction(simpleAction: SimpleAction) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index c80c7c5f15..cc1237a555 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -21,26 +21,47 @@ import com.squareup.inject.assisted.AssistedInject import dagger.Lazy import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.isTextMessage +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent +import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited +import im.vector.matrix.android.api.util.Optional import im.vector.matrix.rx.RxRoom import im.vector.matrix.rx.unwrap +import im.vector.riotx.R import im.vector.riotx.core.extensions.canReact import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.html.EventHtmlRenderer import java.text.SimpleDateFormat import java.util.* +/** + * Quick reactions state + */ +data class ToggleState( + val reaction: String, + val isSelected: Boolean +) + data class MessageActionState( val roomId: String, val eventId: String, val informationData: MessageInformationData, - val timelineEvent: Async = Uninitialized + val timelineEvent: Async = Uninitialized, + val messageBody: CharSequence? = null, + // For quick reactions + val quickStates: Async> = Uninitialized, + // For actions + val actions: Async> = Uninitialized ) : MvRxState { constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData) @@ -49,18 +70,93 @@ data class MessageActionState( fun senderName(): String = informationData.memberName?.toString() ?: "" - fun time(): String? = timelineEvent()?.root?.originServerTs?.let { dateFormat.format(Date(it)) } - ?: "" + fun time(): String? = timelineEvent()?.root?.originServerTs?.let { dateFormat.format(Date(it)) } ?: "" fun canReact() = timelineEvent()?.canReact() == true +} - fun messageBody(eventHtmlRenderer: EventHtmlRenderer?, noticeEventFormatter: NoticeEventFormatter?): CharSequence? { +/** + * Information related to an event and used to display preview in contextual bottomsheet. + */ +class MessageActionsViewModel @AssistedInject constructor(@Assisted + initialState: MessageActionState, + private val eventHtmlRenderer: Lazy, + private val session: Session, + private val noticeEventFormatter: NoticeEventFormatter, + private val stringProvider: StringProvider +) : VectorViewModel(initialState) { + + private val eventId = initialState.eventId + private val informationData = initialState.informationData + private val room = session.getRoom(initialState.roomId) + + @AssistedInject.Factory + interface Factory { + fun create(initialState: MessageActionState): MessageActionsViewModel + } + + companion object : MvRxViewModelFactory { + + val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀") + + override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? { + val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.messageActionViewModelFactory.create(state) + } + } + + init { + observeEvent() + observeReactions() + observeEventAction() + } + + private fun observeEvent() { + if (room == null) return + RxRoom(room) + .liveTimelineEvent(eventId) + .unwrap() + .execute { + copy( + timelineEvent = it, + messageBody = computeMessageBody(it) + ) + } + } + + private fun observeEventAction() { + if (room == null) return + RxRoom(room) + .liveTimelineEvent(eventId) + .map { + actionsForEvent(it) + } + .execute { + copy(actions = it) + } + } + + private fun observeReactions() { + if (room == null) return + RxRoom(room) + .liveAnnotationSummary(eventId) + .map { annotations -> + quickEmojis.map { emoji -> + ToggleState(emoji, annotations.getOrNull()?.reactionsSummary?.firstOrNull { it.key == emoji }?.addedByMe ?: false) + } + } + .execute { + copy(quickStates = it) + } + } + + private fun computeMessageBody(timelineEvent: Async): CharSequence? { return when (timelineEvent()?.root?.getClearType()) { EventType.MESSAGE -> { val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent() if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { - eventHtmlRenderer?.render(messageContent.formattedBody - ?: messageContent.body) + eventHtmlRenderer.get().render(messageContent.formattedBody + ?: messageContent.body) } else { messageContent?.body } @@ -72,54 +168,177 @@ data class MessageActionState( EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER -> { - timelineEvent()?.let { noticeEventFormatter?.format(it) } + timelineEvent()?.let { noticeEventFormatter.format(it) } } else -> null } } -} -/** - * Information related to an event and used to display preview in contextual bottomsheet. - */ -class MessageActionsViewModel @AssistedInject constructor(@Assisted - initialState: MessageActionState, - private val eventHtmlRenderer: Lazy, - session: Session, - private val noticeEventFormatter: NoticeEventFormatter -) : VectorViewModel(initialState) { + private fun actionsForEvent(optionalEvent: Optional): List { + val event = optionalEvent.getOrNull() ?: return emptyList() - private val eventId = initialState.eventId - private val room = session.getRoom(initialState.roomId) + val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel() + ?: event.root.getClearContent().toModel() + val type = messageContent?.type - @AssistedInject.Factory - interface Factory { - fun create(initialState: MessageActionState): MessageActionsViewModel - } + return arrayListOf().apply { + if (event.root.sendState.hasFailed()) { + if (canRetry(event)) { + add(SimpleAction.Resend(eventId)) + } + add(SimpleAction.Remove(eventId)) + } else if (event.root.sendState.isSending()) { + // TODO is uploading attachment? + if (canCancel(event)) { + add(SimpleAction.Cancel(eventId)) + } + } else { + if (!event.root.isRedacted()) { + if (canReply(event, messageContent)) { + add(SimpleAction.Reply(eventId)) + } - companion object : MvRxViewModelFactory { + if (canEdit(event, session.myUserId)) { + add(SimpleAction.Edit(eventId)) + } - override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? { - val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.messageActionViewModelFactory.create(state) + if (canRedact(event, session.myUserId)) { + add(SimpleAction.Delete(eventId)) + } + + if (canCopy(type)) { + // TODO copy images? html? see ClipBoard + add(SimpleAction.Copy(messageContent!!.body)) + } + + if (event.canReact()) { + add(SimpleAction.AddReaction(eventId)) + } + + if (canQuote(event, messageContent)) { + add(SimpleAction.Quote(eventId)) + } + + if (canViewReactions(event)) { + add(SimpleAction.ViewReactions(informationData)) + } + + if (event.hasBeenEdited()) { + add(SimpleAction.ViewEditHistory(informationData)) + } + + if (canShare(type)) { + if (messageContent is MessageImageContent) { + session.contentUrlResolver().resolveFullSize(messageContent.url)?.let { url -> + add(SimpleAction.Share(url)) + } + } + // TODO + } + + if (event.root.sendState == SendState.SENT) { + // TODO Can be redacted + + // TODO sent by me or sufficient power level + } + } + + add(SimpleAction.ViewSource(event.root.toContentStringWithIndent())) + if (event.isEncrypted()) { + val decryptedContent = event.root.toClearContentStringWithIndent() + ?: stringProvider.getString(R.string.encryption_information_decryption_error) + add(SimpleAction.ViewDecryptedSource(decryptedContent)) + } + add(SimpleAction.CopyPermalink(eventId)) + + if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) { + // not sent by me + add(SimpleAction.Flag(eventId)) + } + } } } - init { - observeEvent() + private fun canCancel(@Suppress("UNUSED_PARAMETER") event: TimelineEvent): Boolean { + return false } - private fun observeEvent() { - if (room == null) return - RxRoom(room) - .liveTimelineEvent(eventId) - .unwrap() - .execute { - copy(timelineEvent = it) - } + private fun canReply(event: TimelineEvent, messageContent: MessageContent?): Boolean { + // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment + if (event.root.getClearType() != EventType.MESSAGE) return false + return when (messageContent?.type) { + MessageType.MSGTYPE_TEXT, + MessageType.MSGTYPE_NOTICE, + MessageType.MSGTYPE_EMOTE, + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_VIDEO, + MessageType.MSGTYPE_AUDIO, + MessageType.MSGTYPE_FILE -> true + else -> false + } } - fun resolveBody(state: MessageActionState): CharSequence? { - return state.messageBody(eventHtmlRenderer.get(), noticeEventFormatter) + private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean { + // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment + if (event.root.getClearType() != EventType.MESSAGE) return false + return when (messageContent?.type) { + MessageType.MSGTYPE_TEXT, + MessageType.MSGTYPE_NOTICE, + MessageType.MSGTYPE_EMOTE, + MessageType.FORMAT_MATRIX_HTML, + MessageType.MSGTYPE_LOCATION -> { + true + } + else -> false + } + } + + private fun canRedact(event: TimelineEvent, myUserId: String): Boolean { + // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment + if (event.root.getClearType() != EventType.MESSAGE) return false + // TODO if user is admin or moderator + return event.root.senderId == myUserId + } + + private fun canRetry(event: TimelineEvent): Boolean { + return event.root.sendState.hasFailed() && event.root.isTextMessage() + } + + private fun canViewReactions(event: TimelineEvent): Boolean { + // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment + if (event.root.getClearType() != EventType.MESSAGE) return false + // TODO if user is admin or moderator + return event.annotations?.reactionsSummary?.isNotEmpty() ?: false + } + + private fun canEdit(event: TimelineEvent, myUserId: String): Boolean { + // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment + if (event.root.getClearType() != EventType.MESSAGE) return false + // TODO if user is admin or moderator + val messageContent = event.root.getClearContent().toModel() + return event.root.senderId == myUserId && ( + messageContent?.type == MessageType.MSGTYPE_TEXT + || messageContent?.type == MessageType.MSGTYPE_EMOTE + ) + } + + private fun canCopy(type: String?): Boolean { + return when (type) { + MessageType.MSGTYPE_TEXT, + MessageType.MSGTYPE_NOTICE, + MessageType.MSGTYPE_EMOTE, + MessageType.FORMAT_MATRIX_HTML, + MessageType.MSGTYPE_LOCATION -> true + else -> false + } + } + + private fun canShare(type: String?): Boolean { + return when (type) { + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_AUDIO, + MessageType.MSGTYPE_VIDEO -> true + else -> false + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuFragment.kt deleted file mode 100644 index 2eec705eea..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuFragment.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package im.vector.riotx.features.home.room.detail.timeline.action - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import com.airbnb.mvrx.MvRx -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.platform.VectorBaseFragment -import im.vector.riotx.features.themes.ThemeUtils -import javax.inject.Inject - -/** - * Fragment showing the list of available contextual action for a given message. - */ -class MessageMenuFragment : VectorBaseFragment() { - - @Inject lateinit var messageMenuViewModelFactory: MessageMenuViewModel.Factory - private val viewModel: MessageMenuViewModel by fragmentViewModel(MessageMenuViewModel::class) - private var addSeparators = false - var interactionListener: InteractionListener? = null - - override fun injectWith(injector: ScreenComponent) { - injector.inject(this) - } - - override fun getLayoutResId() = R.layout.fragment_message_menu - - override fun invalidate() = withState(viewModel) { state -> - - val linearLayout = view as? LinearLayout - if (linearLayout != null) { - val inflater = LayoutInflater.from(linearLayout.context) - linearLayout.removeAllViews() - var insertIndex = 0 - val actions = state.actions() - actions?.forEachIndexed { index, action -> - inflateActionView(action, inflater, linearLayout)?.let { - it.setOnClickListener { - interactionListener?.didSelectMenuAction(action) - } - linearLayout.addView(it, insertIndex) - insertIndex++ - if (addSeparators) { - if (index < actions.size - 1) { - linearLayout.addView(inflateSeparatorView(), insertIndex) - insertIndex++ - } - } - } - } - } - } - - private fun inflateActionView(action: SimpleAction, inflater: LayoutInflater, container: ViewGroup?): View? { - return inflater.inflate(R.layout.adapter_item_action, container, false)?.apply { - findViewById(R.id.action_icon)?.setImageResource(action.iconResId) - findViewById(R.id.action_title)?.setText(action.titleRes) - } - } - - private fun inflateSeparatorView(): View { - val frame = FrameLayout(requireContext()) - frame.setBackgroundColor(ThemeUtils.getColor(requireContext(), R.attr.vctr_list_divider_color)) - frame.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, requireContext().resources.displayMetrics.density.toInt()) - return frame - } - - interface InteractionListener { - fun didSelectMenuAction(simpleAction: SimpleAction) - } - - companion object { - fun newInstance(pa: TimelineEventFragmentArgs): MessageMenuFragment { - val args = Bundle() - args.putParcelable(MvRx.KEY_ARG, pa) - val fragment = MessageMenuFragment() - fragment.arguments = args - return fragment - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt deleted file mode 100644 index 14d730044a..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package im.vector.riotx.features.home.room.detail.timeline.action - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import com.airbnb.mvrx.* -import com.squareup.inject.assisted.Assisted -import com.squareup.inject.assisted.AssistedInject -import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.isTextMessage -import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.message.MessageContent -import im.vector.matrix.android.api.session.room.model.message.MessageImageContent -import im.vector.matrix.android.api.session.room.model.message.MessageType -import im.vector.matrix.android.api.session.room.send.SendState -import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited -import im.vector.matrix.android.api.util.Optional -import im.vector.matrix.rx.RxRoom -import im.vector.riotx.R -import im.vector.riotx.core.extensions.canReact -import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData - -sealed class SimpleAction(@StringRes val titleRes: Int, @DrawableRes val iconResId: Int) { - data class AddReaction(val eventId: String) : SimpleAction(R.string.message_add_reaction, R.drawable.ic_add_reaction) - data class Copy(val content: String) : SimpleAction(R.string.copy, R.drawable.ic_copy) - data class Edit(val eventId: String) : SimpleAction(R.string.edit, R.drawable.ic_edit) - data class Quote(val eventId: String) : SimpleAction(R.string.quote, R.drawable.ic_quote) - data class Reply(val eventId: String) : SimpleAction(R.string.reply, R.drawable.ic_reply) - data class Share(val imageUrl: String) : SimpleAction(R.string.share, R.drawable.ic_share) - data class Resend(val eventId: String) : SimpleAction(R.string.global_retry, R.drawable.ic_refresh_cw) - data class Remove(val eventId: String) : SimpleAction(R.string.remove, R.drawable.ic_trash) - data class Delete(val eventId: String) : SimpleAction(R.string.delete, R.drawable.ic_delete) - data class Cancel(val eventId: String) : SimpleAction(R.string.cancel, R.drawable.ic_close_round) - data class ViewSource(val content: String) : SimpleAction(R.string.view_source, R.drawable.ic_view_source) - data class ViewDecryptedSource(val content: String) : SimpleAction(R.string.view_decrypted_source, R.drawable.ic_view_source) - data class CopyPermalink(val eventId: String) : SimpleAction(R.string.permalink, R.drawable.ic_permalink) - data class Flag(val eventId: String) : SimpleAction(R.string.report_content, R.drawable.ic_flag) - data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) : SimpleAction(0, 0) - data class ViewReactions(val messageInformationData: MessageInformationData) : SimpleAction(R.string.message_view_reaction, R.drawable.ic_view_reactions) - data class ViewEditHistory(val messageInformationData: MessageInformationData) : - SimpleAction(R.string.message_view_edit_history, R.drawable.ic_view_edit_history) -} - -data class MessageMenuState( - val roomId: String, - val eventId: String, - val informationData: MessageInformationData, - val actions: Async> = Uninitialized -) : MvRxState { - - constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData) -} - -/** - * Manages list actions for a given message (copy / paste / forward...) - */ -class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: MessageMenuState, - private val session: Session, - private val stringProvider: StringProvider) : VectorViewModel(initialState) { - - @AssistedInject.Factory - interface Factory { - fun create(initialState: MessageMenuState): MessageMenuViewModel - } - - private val room = session.getRoom(initialState.roomId) - ?: throw IllegalStateException("Shouldn't use this ViewModel without a room") - - private val eventId = initialState.eventId - private val informationData: MessageInformationData = initialState.informationData - - companion object : MvRxViewModelFactory { - override fun create(viewModelContext: ViewModelContext, state: MessageMenuState): MessageMenuViewModel? { - val fragment: MessageMenuFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.messageMenuViewModelFactory.create(state) - } - } - - init { - observeEvent() - } - - private fun observeEvent() { - RxRoom(room) - .liveTimelineEvent(eventId) - .map { - actionsForEvent(it) - } - .execute { - copy(actions = it) - } - } - - private fun actionsForEvent(optionalEvent: Optional): List { - val event = optionalEvent.getOrNull() ?: return emptyList() - - val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel() - ?: event.root.getClearContent().toModel() - val type = messageContent?.type - - return arrayListOf().apply { - if (event.root.sendState.hasFailed()) { - if (canRetry(event)) { - add(SimpleAction.Resend(eventId)) - } - add(SimpleAction.Remove(eventId)) - } else if (event.root.sendState.isSending()) { - // TODO is uploading attachment? - if (canCancel(event)) { - add(SimpleAction.Cancel(eventId)) - } - } else { - if (!event.root.isRedacted()) { - if (canReply(event, messageContent)) { - add(SimpleAction.Reply(eventId)) - } - - if (canEdit(event, session.myUserId)) { - add(SimpleAction.Edit(eventId)) - } - - if (canRedact(event, session.myUserId)) { - add(SimpleAction.Delete(eventId)) - } - - if (canCopy(type)) { - // TODO copy images? html? see ClipBoard - add(SimpleAction.Copy(messageContent!!.body)) - } - - if (event.canReact()) { - add(SimpleAction.AddReaction(eventId)) - } - - if (canQuote(event, messageContent)) { - add(SimpleAction.Quote(eventId)) - } - - if (canViewReactions(event)) { - add(SimpleAction.ViewReactions(informationData)) - } - - if (event.hasBeenEdited()) { - add(SimpleAction.ViewEditHistory(informationData)) - } - - if (canShare(type)) { - if (messageContent is MessageImageContent) { - session.contentUrlResolver().resolveFullSize(messageContent.url)?.let { url -> - add(SimpleAction.Share(url)) - } - } - // TODO - } - - if (event.root.sendState == SendState.SENT) { - // TODO Can be redacted - - // TODO sent by me or sufficient power level - } - } - - add(SimpleAction.ViewSource(event.root.toContentStringWithIndent())) - if (event.isEncrypted()) { - val decryptedContent = event.root.toClearContentStringWithIndent() - ?: stringProvider.getString(R.string.encryption_information_decryption_error) - add(SimpleAction.ViewDecryptedSource(decryptedContent)) - } - add(SimpleAction.CopyPermalink(eventId)) - - if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) { - // not sent by me - add(SimpleAction.Flag(eventId)) - } - } - } - } - - private fun canCancel(@Suppress("UNUSED_PARAMETER") event: TimelineEvent): Boolean { - return false - } - - private fun canReply(event: TimelineEvent, messageContent: MessageContent?): Boolean { - // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment - if (event.root.getClearType() != EventType.MESSAGE) return false - return when (messageContent?.type) { - MessageType.MSGTYPE_TEXT, - MessageType.MSGTYPE_NOTICE, - MessageType.MSGTYPE_EMOTE, - MessageType.MSGTYPE_IMAGE, - MessageType.MSGTYPE_VIDEO, - MessageType.MSGTYPE_AUDIO, - MessageType.MSGTYPE_FILE -> true - else -> false - } - } - - private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean { - // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment - if (event.root.getClearType() != EventType.MESSAGE) return false - return when (messageContent?.type) { - MessageType.MSGTYPE_TEXT, - MessageType.MSGTYPE_NOTICE, - MessageType.MSGTYPE_EMOTE, - MessageType.FORMAT_MATRIX_HTML, - MessageType.MSGTYPE_LOCATION -> { - true - } - else -> false - } - } - - private fun canRedact(event: TimelineEvent, myUserId: String): Boolean { - // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment - if (event.root.getClearType() != EventType.MESSAGE) return false - // TODO if user is admin or moderator - return event.root.senderId == myUserId - } - - private fun canRetry(event: TimelineEvent): Boolean { - return event.root.sendState.hasFailed() && event.root.isTextMessage() - } - - private fun canViewReactions(event: TimelineEvent): Boolean { - // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment - if (event.root.getClearType() != EventType.MESSAGE) return false - // TODO if user is admin or moderator - return event.annotations?.reactionsSummary?.isNotEmpty() ?: false - } - - private fun canEdit(event: TimelineEvent, myUserId: String): Boolean { - // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment - if (event.root.getClearType() != EventType.MESSAGE) return false - // TODO if user is admin or moderator - val messageContent = event.root.getClearContent().toModel() - return event.root.senderId == myUserId && ( - messageContent?.type == MessageType.MSGTYPE_TEXT - || messageContent?.type == MessageType.MSGTYPE_EMOTE - ) - } - - private fun canCopy(type: String?): Boolean { - return when (type) { - MessageType.MSGTYPE_TEXT, - MessageType.MSGTYPE_NOTICE, - MessageType.MSGTYPE_EMOTE, - MessageType.FORMAT_MATRIX_HTML, - MessageType.MSGTYPE_LOCATION -> true - else -> false - } - } - - private fun canShare(type: String?): Boolean { - return when (type) { - MessageType.MSGTYPE_IMAGE, - MessageType.MSGTYPE_AUDIO, - MessageType.MSGTYPE_VIDEO -> true - else -> false - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/QuickReactionFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/QuickReactionFragment.kt deleted file mode 100644 index cabb4c113f..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/QuickReactionFragment.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package im.vector.riotx.features.home.room.detail.timeline.action - -import android.graphics.Typeface -import android.os.Bundle -import android.view.View -import android.widget.TextView -import com.airbnb.mvrx.MvRx -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState -import im.vector.riotx.EmojiCompatFontProvider -import im.vector.riotx.R -import im.vector.riotx.core.di.ScreenComponent -import im.vector.riotx.core.platform.VectorBaseFragment -import kotlinx.android.synthetic.main.adapter_item_action_quick_reaction.* -import javax.inject.Inject - -/** - * Quick Reaction Fragment (agree / like reactions) - */ -class QuickReactionFragment : VectorBaseFragment() { - - private val viewModel: QuickReactionViewModel by fragmentViewModel(QuickReactionViewModel::class) - - var interactionListener: InteractionListener? = null - - @Inject lateinit var fontProvider: EmojiCompatFontProvider - @Inject lateinit var quickReactionViewModelFactory: QuickReactionViewModel.Factory - - override fun getLayoutResId() = R.layout.adapter_item_action_quick_reaction - - override fun injectWith(injector: ScreenComponent) { - injector.inject(this) - } - - private lateinit var textViews: List - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - textViews = listOf(quickReaction0, quickReaction1, quickReaction2, quickReaction3, - quickReaction4, quickReaction5, quickReaction6, quickReaction7) - textViews.forEachIndexed { index, textView -> - textView.typeface = fontProvider.typeface ?: Typeface.DEFAULT - textView.setOnClickListener { - viewModel.didSelect(index) - } - } - } - - override fun invalidate() = withState(viewModel) { - val quickReactionsStates = it.quickStates() ?: return@withState - quickReactionsStates.forEachIndexed { index, qs -> - textViews[index].text = qs.reaction - textViews[index].alpha = if (qs.isSelected) 0.2f else 1f - } - - if (it.result != null) { - interactionListener?.didQuickReactWith(it.result.reaction, it.result.isSelected, it.eventId) - } - } - - interface InteractionListener { - fun didQuickReactWith(clickedOn: String, add: Boolean, eventId: String) - } - - companion object { - fun newInstance(pa: TimelineEventFragmentArgs): QuickReactionFragment { - val args = Bundle() - args.putParcelable(MvRx.KEY_ARG, pa) - val fragment = QuickReactionFragment() - fragment.arguments = args - return fragment - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/QuickReactionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/QuickReactionViewModel.kt deleted file mode 100644 index edcfd8e28c..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/QuickReactionViewModel.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package im.vector.riotx.features.home.room.detail.timeline.action - -import com.airbnb.mvrx.* -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.RxRoom -import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData - -/** - * Quick reactions state, it's a toggle with 3rd state - */ -data class ToggleState( - val reaction: String, - val isSelected: Boolean -) - -data class QuickReactionState( - val roomId: String, - val eventId: String, - val informationData: MessageInformationData, - val quickStates: Async> = Uninitialized, - val result: ToggleState? = null - /** Pair of 'clickedOn' and current toggles state*/ -) : MvRxState { - - constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData) -} - -/** - * Quick reaction view model - */ -class QuickReactionViewModel @AssistedInject constructor(@Assisted initialState: QuickReactionState, - private val session: Session) : VectorViewModel(initialState) { - - @AssistedInject.Factory - interface Factory { - fun create(initialState: QuickReactionState): QuickReactionViewModel - } - - private val room = session.getRoom(initialState.roomId) - private val eventId = initialState.eventId - - companion object : MvRxViewModelFactory { - - val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀") - - override fun create(viewModelContext: ViewModelContext, state: QuickReactionState): QuickReactionViewModel? { - val fragment: QuickReactionFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.quickReactionViewModelFactory.create(state) - } - } - - init { - observeReactions() - } - - private fun observeReactions() { - if (room == null) return - RxRoom(room) - .liveAnnotationSummary(eventId) - .map { annotations -> - quickEmojis.map { emoji -> - ToggleState(emoji, annotations.getOrNull()?.reactionsSummary?.firstOrNull { it.key == emoji }?.addedByMe - ?: false) - } - } - .execute { - copy(quickStates = it) - } - } - - fun didSelect(index: Int) = withState { - val selectedReaction = it.quickStates()?.get(index) ?: return@withState - val isSelected = selectedReaction.isSelected - setState { - copy(result = ToggleState(selectedReaction.reaction, !isSelected)) - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt new file mode 100644 index 0000000000..9ba1bbb212 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt @@ -0,0 +1,43 @@ +/* + * 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.annotation.DrawableRes +import androidx.annotation.StringRes +import im.vector.riotx.R +import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData + +sealed class SimpleAction(@StringRes val titleRes: Int, @DrawableRes val iconResId: Int) { + data class AddReaction(val eventId: String) : SimpleAction(R.string.message_add_reaction, R.drawable.ic_add_reaction) + data class Copy(val content: String) : SimpleAction(R.string.copy, R.drawable.ic_copy) + data class Edit(val eventId: String) : SimpleAction(R.string.edit, R.drawable.ic_edit) + data class Quote(val eventId: String) : SimpleAction(R.string.quote, R.drawable.ic_quote) + data class Reply(val eventId: String) : SimpleAction(R.string.reply, R.drawable.ic_reply) + data class Share(val imageUrl: String) : SimpleAction(R.string.share, R.drawable.ic_share) + data class Resend(val eventId: String) : SimpleAction(R.string.global_retry, R.drawable.ic_refresh_cw) + data class Remove(val eventId: String) : SimpleAction(R.string.remove, R.drawable.ic_trash) + data class Delete(val eventId: String) : SimpleAction(R.string.delete, R.drawable.ic_delete) + data class Cancel(val eventId: String) : SimpleAction(R.string.cancel, R.drawable.ic_close_round) + data class ViewSource(val content: String) : SimpleAction(R.string.view_source, R.drawable.ic_view_source) + data class ViewDecryptedSource(val content: String) : SimpleAction(R.string.view_decrypted_source, R.drawable.ic_view_source) + data class CopyPermalink(val eventId: String) : SimpleAction(R.string.permalink, R.drawable.ic_permalink) + data class Flag(val eventId: String) : SimpleAction(R.string.report_content, R.drawable.ic_flag) + data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) : SimpleAction(0, 0) + data class ViewReactions(val messageInformationData: MessageInformationData) : SimpleAction(R.string.message_view_reaction, R.drawable.ic_view_reactions) + data class ViewEditHistory(val messageInformationData: MessageInformationData) : + SimpleAction(R.string.message_view_edit_history, R.drawable.ic_view_edit_history) +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt similarity index 93% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryBottomSheet.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index 5fefb36e29..acdef2058d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.timeline.action +package im.vector.riotx.features.home.room.detail.timeline.edithistory import android.os.Bundle import android.view.LayoutInflater @@ -29,6 +29,8 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.html.EventHtmlRenderer import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.* diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt similarity index 98% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt index 288f001651..d36e98f67c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.timeline.action +package im.vector.riotx.features.home.room.detail.timeline.edithistory import android.content.Context import android.text.Spannable @@ -41,7 +41,7 @@ import timber.log.Timber import java.util.* /** - * Epoxy controller for reaction event list + * Epoxy controller for edit history list */ class ViewEditHistoryEpoxyController(private val context: Context, val dateFormatter: VectorDateFormatter, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt similarity index 96% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt index 890fbe60e5..e2b976b273 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.timeline.action +package im.vector.riotx.features.home.room.detail.timeline.edithistory import com.airbnb.mvrx.* import com.squareup.inject.assisted.Assisted @@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.date.VectorDateFormatter +import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import timber.log.Timber import java.util.* diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt similarity index 96% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt index e1d03d93fc..39392324aa 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ReactionInfoSimpleItem.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.timeline.action +package im.vector.riotx.features.home.room.detail.timeline.reactions import android.widget.TextView import androidx.core.view.isVisible diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt similarity index 82% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt index b4eba4bbec..deb2a84818 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.timeline.action +package im.vector.riotx.features.home.room.detail.timeline.reactions import android.os.Bundle import android.view.LayoutInflater @@ -30,6 +30,8 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.* import javax.inject.Inject @@ -37,11 +39,11 @@ import javax.inject.Inject /** * Bottom sheet displaying list of reactions for a given event ordered by timestamp */ -class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() { +class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() { - private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class) + private val viewModel: ViewReactionsViewModel by fragmentViewModel(ViewReactionsViewModel::class) - @Inject lateinit var viewReactionViewModelFactory: ViewReactionViewModel.Factory + @Inject lateinit var viewReactionsViewModelFactory: ViewReactionsViewModel.Factory @BindView(R.id.bottom_sheet_display_reactions_list) lateinit var epoxyRecyclerView: EpoxyRecyclerView @@ -72,7 +74,7 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() { } companion object { - fun newInstance(roomId: String, informationData: MessageInformationData): ViewReactionBottomSheet { + fun newInstance(roomId: String, informationData: MessageInformationData): ViewReactionsBottomSheet { val args = Bundle() val parcelableArgs = TimelineEventFragmentArgs( informationData.eventId, @@ -80,7 +82,7 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() { informationData ) args.putParcelable(MvRx.KEY_ARG, parcelableArgs) - return ViewReactionBottomSheet().apply { arguments = args } + return ViewReactionsBottomSheet().apply { arguments = args } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt similarity index 96% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt index 6b1c099261..7fd2edcbfe 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsEpoxyController.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.timeline.action +package im.vector.riotx.features.home.room.detail.timeline.reactions import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt similarity index 83% rename from vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index a3611edd87..208e126022 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.home.room.detail.timeline.action +package im.vector.riotx.features.home.room.detail.timeline.reactions import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -30,6 +30,7 @@ import im.vector.matrix.rx.RxRoom import im.vector.matrix.rx.unwrap import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.date.VectorDateFormatter +import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import io.reactivex.Observable import io.reactivex.Single @@ -53,10 +54,10 @@ data class ReactionInfo( /** * Used to display the list of members that reacted to a given event */ -class ViewReactionViewModel @AssistedInject constructor(@Assisted +class ViewReactionsViewModel @AssistedInject constructor(@Assisted initialState: DisplayReactionsViewState, - private val session: Session, - private val dateFormatter: VectorDateFormatter + private val session: Session, + private val dateFormatter: VectorDateFormatter ) : VectorViewModel(initialState) { private val roomId = initialState.roomId @@ -66,14 +67,14 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted @AssistedInject.Factory interface Factory { - fun create(initialState: DisplayReactionsViewState): ViewReactionViewModel + fun create(initialState: DisplayReactionsViewState): ViewReactionsViewModel } - companion object : MvRxViewModelFactory { + companion object : MvRxViewModelFactory { - override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionViewModel? { - val fragment: ViewReactionBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.viewReactionViewModelFactory.create(state) + override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionsViewModel? { + val fragment: ViewReactionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.viewReactionsViewModelFactory.create(state) } } diff --git a/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml b/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml index 9b3ffb26a3..0cc2e16ed9 100644 --- a/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml +++ b/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml @@ -25,7 +25,6 @@ android:orientation="vertical" android:scrollbars="vertical" tools:itemCount="15" - tools:listitem="@layout/item_simple_reaction_info"> + tools:listitem="@layout/item_simple_reaction_info" /> - \ No newline at end of file diff --git a/vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml b/vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml new file mode 100644 index 0000000000..5ca952272f --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/bottom_sheet_message_actions.xml b/vector/src/main/res/layout/bottom_sheet_message_actions.xml deleted file mode 100644 index c7d4f5ac8e..0000000000 --- a/vector/src/main/res/layout/bottom_sheet_message_actions.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vector/src/main/res/layout/fragment_message_menu.xml b/vector/src/main/res/layout/fragment_message_menu.xml deleted file mode 100644 index 4538ac935c..0000000000 --- a/vector/src/main/res/layout/fragment_message_menu.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/adapter_item_action.xml b/vector/src/main/res/layout/item_bottom_sheet_action.xml similarity index 97% rename from vector/src/main/res/layout/adapter_item_action.xml rename to vector/src/main/res/layout/item_bottom_sheet_action.xml index 03d2f81115..44145e7bf5 100644 --- a/vector/src/main/res/layout/adapter_item_action.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_action.xml @@ -2,7 +2,7 @@ + diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml new file mode 100644 index 0000000000..a688f38d0f --- /dev/null +++ b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_status.xml b/vector/src/main/res/layout/item_bottom_sheet_message_status.xml new file mode 100644 index 0000000000..10c129cf58 --- /dev/null +++ b/vector/src/main/res/layout/item_bottom_sheet_message_status.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml b/vector/src/main/res/layout/item_bottom_sheet_quick_reaction.xml similarity index 100% rename from vector/src/main/res/layout/adapter_item_action_quick_reaction.xml rename to vector/src/main/res/layout/item_bottom_sheet_quick_reaction.xml From 0a79b8b315cb202d3090b885d8f107d372768aa8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Oct 2019 15:29:31 +0200 Subject: [PATCH 15/30] Cleanup --- .../action/MessageActionsBottomSheet.kt | 25 +------------------ .../action/MessageActionsEpoxyController.kt | 15 +++++------ .../bottom_sheet_epoxylist_with_title.xml | 1 - .../bottom_sheet_generic_recycler_epoxy.xml | 1 - .../res/layout/item_bottom_sheet_action.xml | 3 +-- 5 files changed, 10 insertions(+), 35 deletions(-) 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 39116b59b5..53ac1bd430 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 @@ -15,22 +15,16 @@ */ package im.vector.riotx.features.home.room.detail.timeline.action -import android.app.Dialog import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout import androidx.lifecycle.ViewModelProviders import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment -import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import kotlinx.android.synthetic.main.bottom_sheet_generic_recycler_epoxy.* import javax.inject.Inject @@ -40,12 +34,10 @@ import javax.inject.Inject */ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), MessageActionsEpoxyController.MessageActionsEpoxyControllerListener { @Inject lateinit var messageActionViewModelFactory: MessageActionsViewModel.Factory - @Inject lateinit var avatarRenderer: AvatarRenderer - @Inject lateinit var fontProvider: EmojiCompatFontProvider + @Inject lateinit var messageActionsEpoxyController: MessageActionsEpoxyController private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class) - private lateinit var messageActionsEpoxyController: MessageActionsEpoxyController private lateinit var actionHandlerModel: ActionsHandler override fun injectWith(screenComponent: ScreenComponent) { @@ -60,7 +52,6 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message super.onActivityCreated(savedInstanceState) actionHandlerModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) - messageActionsEpoxyController = MessageActionsEpoxyController(requireContext(), avatarRenderer, fontProvider) bottomSheetEpoxyRecyclerView.setController(messageActionsEpoxyController) messageActionsEpoxyController.listener = this } @@ -70,20 +61,6 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message dismiss() } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val dialog = super.onCreateDialog(savedInstanceState) - // We want to force the bottom sheet initial state to expanded - (dialog as? BottomSheetDialog)?.let { bottomSheetDialog -> - bottomSheetDialog.setOnShowListener { dialog -> - val d = dialog as BottomSheetDialog - (d.findViewById(com.google.android.material.R.id.design_bottom_sheet) as? FrameLayout)?.let { - BottomSheetBehavior.from(it).state = BottomSheetBehavior.STATE_COLLAPSED - } - } - } - return dialog - } - override fun invalidate() = withState(viewModel) { messageActionsEpoxyController.setData(it) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 1de7302820..66c1949631 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -15,20 +15,21 @@ */ package im.vector.riotx.features.home.room.detail.timeline.action -import android.content.Context import android.view.View import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Success import im.vector.riotx.EmojiCompatFontProvider import im.vector.riotx.R +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.AvatarRenderer +import javax.inject.Inject /** * Epoxy controller for message action list */ -class MessageActionsEpoxyController(private val context: Context, - private val avatarRenderer: AvatarRenderer, - private val fontProvider: EmojiCompatFontProvider) : TypedEpoxyController() { +class MessageActionsEpoxyController @Inject constructor(private val stringProvider: StringProvider, + private val avatarRenderer: AvatarRenderer, + private val fontProvider: EmojiCompatFontProvider) : TypedEpoxyController() { var listener: MessageActionsEpoxyControllerListener? = null @@ -51,13 +52,13 @@ class MessageActionsEpoxyController(private val context: Context, bottomSheetItemSendState { id("send_state") showProgress(true) - text(context.getString(R.string.event_status_sending_message)) + text(stringProvider.getString(R.string.event_status_sending_message)) } } else if (state.informationData.sendState.hasFailed()) { bottomSheetItemSendState { id("send_state") showProgress(false) - text(context.getString(R.string.unable_to_send_message)) + text(stringProvider.getString(R.string.unable_to_send_message)) drawableStart(R.drawable.ic_warning_small) } } @@ -73,7 +74,7 @@ class MessageActionsEpoxyController(private val context: Context, id("quick_reaction") fontProvider(fontProvider) texts(state.quickStates()?.map { it.reaction }.orEmpty()) - selecteds(state.quickStates()?.map { it.isSelected }.orEmpty()) + selecteds(state.quickStates.invoke().map { it.isSelected }) listener(object : BottomSheetItemQuickReactions.Listener { override fun didSelect(emoji: String, selected: Boolean) { listener?.didSelectMenuAction(SimpleAction.QuickReact(state.eventId, emoji, selected)) diff --git a/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml b/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml index 0cc2e16ed9..a5e9c980a3 100644 --- a/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml +++ b/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml @@ -22,7 +22,6 @@ android:layout_height="0dp" android:layout_weight="1" android:fadeScrollbars="false" - android:orientation="vertical" android:scrollbars="vertical" tools:itemCount="15" tools:listitem="@layout/item_simple_reaction_info" /> diff --git a/vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml b/vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml index 5ca952272f..55d3492cf9 100644 --- a/vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml +++ b/vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml @@ -14,7 +14,6 @@ android:layout_height="0dp" android:layout_weight="1" android:fadeScrollbars="false" - android:orientation="vertical" android:scrollbars="vertical" tools:itemCount="5" tools:listitem="@layout/item_bottom_sheet_action" /> diff --git a/vector/src/main/res/layout/item_bottom_sheet_action.xml b/vector/src/main/res/layout/item_bottom_sheet_action.xml index 44145e7bf5..4f56e3c126 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_action.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_action.xml @@ -11,8 +11,7 @@ android:paddingLeft="@dimen/layout_horizontal_margin" android:paddingTop="8dp" android:paddingRight="@dimen/layout_horizontal_margin" - android:paddingBottom="8dp" - tools:layout_height="50dp"> + android:paddingBottom="8dp"> Date: Thu, 10 Oct 2019 16:44:33 +0200 Subject: [PATCH 16/30] Report content: UI menu --- .../timeline/action/BottomSheetItemAction.kt | 14 ++++++++++++++ .../action/MessageActionsBottomSheet.kt | 9 +++++++-- .../action/MessageActionsEpoxyController.kt | 19 +++++++++++++++++++ .../action/MessageActionsViewModel.kt | 13 +++++++++++-- .../detail/timeline/action/SimpleAction.kt | 5 ++++- .../main/res/drawable/ic_report_custom.xml | 8 ++++++++ .../res/drawable/ic_report_inappropriate.xml | 12 ++++++++++++ .../src/main/res/drawable/ic_report_spam.xml | 8 ++++++++ .../res/layout/item_bottom_sheet_action.xml | 19 +++++++++++++++++-- vector/src/main/res/values/strings_riotX.xml | 4 ++++ 10 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_report_custom.xml create mode 100644 vector/src/main/res/drawable/ic_report_inappropriate.xml create mode 100644 vector/src/main/res/drawable/ic_report_spam.xml diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemAction.kt index 8ee7460d53..d0d5b1deea 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/BottomSheetItemAction.kt @@ -19,6 +19,7 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.annotation.DrawableRes +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R @@ -37,6 +38,12 @@ abstract class BottomSheetItemAction : VectorEpoxyModel(R.id.action_start_space) val icon by bind(R.id.action_icon) val text by bind(R.id.action_title) + val expand by bind(R.id.action_expand) } } 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 53ac1bd430..e1ff0ce628 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 @@ -57,8 +57,13 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message } override fun didSelectMenuAction(simpleAction: SimpleAction) { - actionHandlerModel.fireAction(simpleAction) - dismiss() + if (simpleAction is SimpleAction.ReportContent) { + // Toggle report menu + viewModel.toggleReportMenu() + } else { + actionHandlerModel.fireAction(simpleAction) + dismiss() + } } override fun invalidate() = withState(viewModel) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 66c1949631..d9119f08b3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -94,8 +94,27 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid id("action_$index") iconRes(action.iconResId) textRes(action.titleRes) + showExpand(action is SimpleAction.ReportContent) + expanded(state.expendedReportContentMenu) listener(View.OnClickListener { listener?.didSelectMenuAction(action) }) } + + if (action is SimpleAction.ReportContent && state.expendedReportContentMenu) { + // Special case for report content menu: add the submenu + listOf( + SimpleAction.ReportContentSpam(action.eventId), + SimpleAction.ReportContentInappropriate(action.eventId), + SimpleAction.ReportContentCustom(action.eventId) + ).forEachIndexed { indexReport, actionReport -> + bottomSheetItemAction { + id("actionReport_$indexReport") + subMenuItem(true) + iconRes(actionReport.iconResId) + textRes(actionReport.titleRes) + listener(View.OnClickListener { listener?.didSelectMenuAction(actionReport) }) + } + } + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index cc1237a555..3b25a9e908 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -61,7 +61,8 @@ data class MessageActionState( // For quick reactions val quickStates: Async> = Uninitialized, // For actions - val actions: Async> = Uninitialized + val actions: Async> = Uninitialized, + val expendedReportContentMenu: Boolean = false ) : MvRxState { constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData) @@ -111,6 +112,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted observeEventAction() } + fun toggleReportMenu() = withState { + setState { + copy( + expendedReportContentMenu = it.expendedReportContentMenu.not() + ) + } + } + private fun observeEvent() { if (room == null) return RxRoom(room) @@ -253,7 +262,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) { // not sent by me - add(SimpleAction.Flag(eventId)) + add(SimpleAction.ReportContent(eventId)) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt index 9ba1bbb212..5da589d862 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/SimpleAction.kt @@ -35,7 +35,10 @@ sealed class SimpleAction(@StringRes val titleRes: Int, @DrawableRes val iconRes data class ViewSource(val content: String) : SimpleAction(R.string.view_source, R.drawable.ic_view_source) data class ViewDecryptedSource(val content: String) : SimpleAction(R.string.view_decrypted_source, R.drawable.ic_view_source) data class CopyPermalink(val eventId: String) : SimpleAction(R.string.permalink, R.drawable.ic_permalink) - data class Flag(val eventId: String) : SimpleAction(R.string.report_content, R.drawable.ic_flag) + data class ReportContent(val eventId: String) : SimpleAction(R.string.report_content, R.drawable.ic_flag) + data class ReportContentSpam(val eventId: String) : SimpleAction(R.string.report_content_spam, R.drawable.ic_report_spam) + data class ReportContentInappropriate(val eventId: String) : SimpleAction(R.string.report_content_inappropriate, R.drawable.ic_report_inappropriate) + data class ReportContentCustom(val eventId: String) : SimpleAction(R.string.report_content_custom, R.drawable.ic_report_custom) data class QuickReact(val eventId: String, val clickedOn: String, val add: Boolean) : SimpleAction(0, 0) data class ViewReactions(val messageInformationData: MessageInformationData) : SimpleAction(R.string.message_view_reaction, R.drawable.ic_view_reactions) data class ViewEditHistory(val messageInformationData: MessageInformationData) : diff --git a/vector/src/main/res/drawable/ic_report_custom.xml b/vector/src/main/res/drawable/ic_report_custom.xml new file mode 100644 index 0000000000..8e97c4bfb5 --- /dev/null +++ b/vector/src/main/res/drawable/ic_report_custom.xml @@ -0,0 +1,8 @@ + + + diff --git a/vector/src/main/res/drawable/ic_report_inappropriate.xml b/vector/src/main/res/drawable/ic_report_inappropriate.xml new file mode 100644 index 0000000000..47cc0591bd --- /dev/null +++ b/vector/src/main/res/drawable/ic_report_inappropriate.xml @@ -0,0 +1,12 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_report_spam.xml b/vector/src/main/res/drawable/ic_report_spam.xml new file mode 100644 index 0000000000..bd5a46e00a --- /dev/null +++ b/vector/src/main/res/drawable/ic_report_spam.xml @@ -0,0 +1,8 @@ + + + diff --git a/vector/src/main/res/layout/item_bottom_sheet_action.xml b/vector/src/main/res/layout/item_bottom_sheet_action.xml index 4f56e3c126..131ee0e63c 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_action.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_action.xml @@ -6,6 +6,7 @@ android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackground" + android:gravity="center_vertical" android:minHeight="50dp" android:orientation="horizontal" android:paddingLeft="@dimen/layout_horizontal_margin" @@ -13,11 +14,17 @@ android:paddingRight="@dimen/layout_horizontal_margin" android:paddingBottom="8dp"> + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index a66858ae0e..6a2bcef5a7 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -37,4 +37,8 @@ "The file '%1$s' (%2$s) is too large to upload. The limit is %3$s." + + "It's spam" + "It's inappropriate" + "Custom report" From a7a19dab1113fdca339fb3b7f3421a5d50f50316 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Oct 2019 17:37:04 +0200 Subject: [PATCH 17/30] Report content: Service and REST request --- .../matrix/android/api/session/room/Room.kt | 2 + .../room/reporting/ReportingService.kt | 33 +++++++++++++ .../internal/session/room/DefaultRoom.kt | 21 +++++---- .../android/internal/session/room/RoomAPI.kt | 13 ++++++ .../internal/session/room/RoomFactory.kt | 3 ++ .../internal/session/room/RoomModule.kt | 5 ++ .../room/reporting/DefaultReportingService.kt | 46 +++++++++++++++++++ .../room/reporting/ReportContentBody.kt | 33 +++++++++++++ .../room/reporting/ReportContentTask.kt | 39 ++++++++++++++++ .../home/room/detail/RoomDetailActions.kt | 3 ++ .../home/room/detail/RoomDetailFragment.kt | 43 ++++++++++------- .../home/room/detail/RoomDetailViewModel.kt | 32 +++++++++++-- .../home/room/detail/RoomDetailViewState.kt | 3 +- 13 files changed, 245 insertions(+), 31 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/reporting/ReportingService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/DefaultReportingService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentBody.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt index 9f91e5b276..70c9c6e36c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/Room.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.crypto.RoomCryptoService import im.vector.matrix.android.api.session.room.members.MembershipService import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.relation.RelationService +import im.vector.matrix.android.api.session.room.reporting.ReportingService import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.send.DraftService import im.vector.matrix.android.api.session.room.send.SendService @@ -38,6 +39,7 @@ interface Room : ReadService, MembershipService, StateService, + ReportingService, RelationService, RoomCryptoService { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/reporting/ReportingService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/reporting/ReportingService.kt new file mode 100644 index 0000000000..9dbc6d0e9e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/reporting/ReportingService.kt @@ -0,0 +1,33 @@ +/* + * 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.api.session.room.reporting + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.util.Cancelable + +/** + * This interface defines methods to report content of an event. + */ +interface ReportingService { + + /** + * Report content + * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-rooms-roomid-report-eventid + */ + fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback): Cancelable + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index 7d957ccdad..fea827fd25 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.members.MembershipService import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.relation.RelationService +import im.vector.matrix.android.api.session.room.reporting.ReportingService import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.send.DraftService import im.vector.matrix.android.api.session.room.send.SendService @@ -44,18 +45,20 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, private val sendService: SendService, private val draftService: DraftService, private val stateService: StateService, + private val reportingService: ReportingService, private val readService: ReadService, private val cryptoService: CryptoService, private val relationService: RelationService, - private val roomMembersService: MembershipService -) : Room, - TimelineService by timelineService, - SendService by sendService, - DraftService by draftService, - StateService by stateService, - ReadService by readService, - RelationService by relationService, - MembershipService by roomMembersService { + private val roomMembersService: MembershipService) : + Room, + TimelineService by timelineService, + SendService by sendService, + DraftService by draftService, + StateService by stateService, + ReportingService by reportingService, + ReadService by readService, + RelationService by relationService, + MembershipService by roomMembersService { override fun getRoomSummaryLive(): LiveData> { val liveData = monarchy.findAllMappedWithChanges( 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 daee1c914c..797dbed31c 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 @@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody import im.vector.matrix.android.internal.session.room.relation.RelationsResponse +import im.vector.matrix.android.internal.session.room.reporting.ReportContentBody import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse @@ -245,4 +246,16 @@ internal interface RoomAPI { @Path("eventId") parent_id: String, @Body reason: Map ): Call + + /** + * Reports an event as inappropriate to the server, which may then notify the appropriate people. + * + * @param roomId the room id + * @param eventId the event to report content + * @param body body containing score and reason + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}") + fun reportContent(@Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Body body: ReportContentBody): Call } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 65a3624d2c..e2199782f4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.session.room.draft.DefaultDraftService import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService import im.vector.matrix.android.internal.session.room.read.DefaultReadService import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService +import im.vector.matrix.android.internal.session.room.reporting.DefaultReportingService import im.vector.matrix.android.internal.session.room.send.DefaultSendService import im.vector.matrix.android.internal.session.room.state.DefaultStateService import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService @@ -40,6 +41,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona private val sendServiceFactory: DefaultSendService.Factory, private val draftServiceFactory: DefaultDraftService.Factory, private val stateServiceFactory: DefaultStateService.Factory, + private val reportingServiceFactory: DefaultReportingService.Factory, private val readServiceFactory: DefaultReadService.Factory, private val relationServiceFactory: DefaultRelationService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory) : @@ -54,6 +56,7 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona sendServiceFactory.create(roomId), draftServiceFactory.create(roomId), stateServiceFactory.create(roomId), + reportingServiceFactory.create(roomId), readServiceFactory.create(roomId), cryptoService, relationServiceFactory.create(roomId), diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 5755d6b46e..1aca492b94 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -45,6 +45,8 @@ import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkers import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.relation.* +import im.vector.matrix.android.internal.session.room.reporting.DefaultReportContentTask +import im.vector.matrix.android.internal.session.room.reporting.ReportContentTask import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.timeline.* @@ -114,6 +116,9 @@ internal abstract class RoomModule { @Binds abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask + @Binds + abstract fun bindReportContentTask(reportContentTask: DefaultReportContentTask): ReportContentTask + @Binds abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/DefaultReportingService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/DefaultReportingService.kt new file mode 100644 index 0000000000..3b64cce439 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/DefaultReportingService.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.room.reporting + +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.room.reporting.ReportingService +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith + +internal class DefaultReportingService @AssistedInject constructor(@Assisted private val roomId: String, + private val taskExecutor: TaskExecutor, + private val reportContentTask: ReportContentTask +) : ReportingService { + + @AssistedInject.Factory + interface Factory { + fun create(roomId: String): ReportingService + } + + override fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback): Cancelable { + val params = ReportContentTask.Params(roomId, eventId, score, reason) + + return reportContentTask + .configureWith(params) { + this.callback = callback + } + .executeBy(taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentBody.kt new file mode 100644 index 0000000000..2cf33551ce --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentBody.kt @@ -0,0 +1,33 @@ +/* + * 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.room.reporting + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class ReportContentBody( + /** + * Required. The score to rate this content as where -100 is most offensive and 0 is inoffensive. + */ + @Json(name = "score") val score: Int, + + /** + * Required. The reason the content is being reported. May be blank. + */ + @Json(name = "reason") val reason: String +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt new file mode 100644 index 0000000000..60c031158a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/reporting/ReportContentTask.kt @@ -0,0 +1,39 @@ +/* + * 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.room.reporting + +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface ReportContentTask : Task { + data class Params( + val roomId: String, + val eventId: String, + val score: Int, + val reason: String + ) +} + +internal class DefaultReportContentTask @Inject constructor(private val roomAPI: RoomAPI) : ReportContentTask { + override suspend fun execute(params: ReportContentTask.Params) { + return executeRequest { + apiCall = roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason)) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt index d032182994..eec9eef5dc 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt @@ -49,6 +49,9 @@ sealed class RoomDetailActions { data class ResendMessage(val eventId: String) : RoomDetailActions() data class RemoveFailedEcho(val eventId: String) : RoomDetailActions() + + data class ReportContent(val eventId: String, val reason: String) : RoomDetailActions() + object ClearSendQueue : RoomDetailActions() object ResendAll : RoomDetailActions() } 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 7447bf92e6..d26dba44e1 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 @@ -93,7 +93,9 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotx.features.home.room.detail.timeline.action.* +import im.vector.riotx.features.home.room.detail.timeline.action.ActionsHandler +import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet +import im.vector.riotx.features.home.room.detail.timeline.action.SimpleAction import im.vector.riotx.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet import im.vector.riotx.features.home.room.detail.timeline.item.* import im.vector.riotx.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet @@ -968,23 +970,23 @@ class RoomDetailFragment : private fun handleActions(action: SimpleAction) { when (action) { - is SimpleAction.AddReaction -> { + is SimpleAction.AddReaction -> { startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), action.eventId), REACTION_SELECT_REQUEST_CODE) } - is SimpleAction.ViewReactions -> { + is SimpleAction.ViewReactions -> { ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } - is SimpleAction.Copy -> { + is SimpleAction.Copy -> { // I need info about the current selected message :/ copyToClipboard(requireContext(), action.content, false) val msg = requireContext().getString(R.string.copied_to_clipboard) showSnackWithMessage(msg, Snackbar.LENGTH_SHORT) } - is SimpleAction.Delete -> { + is SimpleAction.Delete -> { roomDetailViewModel.process(RoomDetailActions.RedactAction(action.eventId, context?.getString(R.string.event_redacted_by_user_reason))) } - is SimpleAction.Share -> { + is SimpleAction.Share -> { // TODO current data communication is too limited // Need to now the media type // TODO bad, just POC @@ -1012,10 +1014,10 @@ class RoomDetailFragment : } ) } - is SimpleAction.ViewEditHistory -> { + is SimpleAction.ViewEditHistory -> { onEditedDecorationClicked(action.messageInformationData) } - is SimpleAction.ViewSource -> { + is SimpleAction.ViewSource -> { val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null) view.findViewById(R.id.event_content_text_view)?.let { it.text = action.content @@ -1026,7 +1028,7 @@ class RoomDetailFragment : .setPositiveButton(R.string.ok, null) .show() } - is SimpleAction.ViewDecryptedSource -> { + is SimpleAction.ViewDecryptedSource -> { val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null) view.findViewById(R.id.event_content_text_view)?.let { it.text = action.content @@ -1037,31 +1039,38 @@ class RoomDetailFragment : .setPositiveButton(R.string.ok, null) .show() } - is SimpleAction.QuickReact -> { + is SimpleAction.QuickReact -> { // eventId,ClickedOn,Add roomDetailViewModel.process(RoomDetailActions.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) } - is SimpleAction.Edit -> { + is SimpleAction.Edit -> { roomDetailViewModel.process(RoomDetailActions.EnterEditMode(action.eventId, composerLayout.composerEditText.text.toString())) } - is SimpleAction.Quote -> { + is SimpleAction.Quote -> { roomDetailViewModel.process(RoomDetailActions.EnterQuoteMode(action.eventId, composerLayout.composerEditText.text.toString())) } - is SimpleAction.Reply -> { + is SimpleAction.Reply -> { roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(action.eventId, composerLayout.composerEditText.text.toString())) } - is SimpleAction.CopyPermalink -> { + is SimpleAction.CopyPermalink -> { val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) } - is SimpleAction.Resend -> { + is SimpleAction.Resend -> { roomDetailViewModel.process(RoomDetailActions.ResendMessage(action.eventId)) } - is SimpleAction.Remove -> { + is SimpleAction.Remove -> { roomDetailViewModel.process(RoomDetailActions.RemoveFailedEcho(action.eventId)) } - else -> { + is SimpleAction.ReportContentSpam -> { + roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is spam")) + } + is SimpleAction.ReportContentInappropriate -> { + roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is inappropriate")) + } + // TODO Custom + else -> { Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show() } } 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 57a2c76f61..ee60a89bc1 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 @@ -21,10 +21,7 @@ import android.text.TextUtils import androidx.annotation.IdRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.ViewModelContext +import com.airbnb.mvrx.* import com.jakewharton.rxrelay2.BehaviorRelay import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -155,6 +152,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is RoomDetailActions.ResendAll -> handleResendAll() is RoomDetailActions.SetReadMarkerAction -> handleSetReadMarkerAction(action) is RoomDetailActions.MarkAllAsRead -> handleMarkAllAsRead() + is RoomDetailActions.ReportContent -> handleReportContent(action) } } @@ -708,6 +706,32 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro room.markAllAsRead(object : MatrixCallback {}) } + private fun handleReportContent(action: RoomDetailActions.ReportContent) { + setState { + copy( + reportContentRequest = Loading() + ) + } + + room.reportContent(action.eventId, -100, action.reason, object : MatrixCallback { + override fun onSuccess(data: Unit) { + setState { + copy( + reportContentRequest = Success(Unit) + ) + } + } + + override fun onFailure(failure: Throwable) { + setState { + copy( + reportContentRequest = Fail(failure) + ) + } + } + }) + } + private fun observeSyncState() { session.rx() .liveSyncState() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt index 03110858a1..ba20b9ed3f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt @@ -52,7 +52,8 @@ data class RoomDetailViewState( val tombstoneEvent: Event? = null, val tombstoneEventHandling: Async = Uninitialized, val syncState: SyncState = SyncState.IDLE, - val highlightedEventId: String? = null + val highlightedEventId: String? = null, + val reportContentRequest: Async = Uninitialized ) : MvRxState { constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) From 4a6237b50eef50235661d93b326afc540787835a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Oct 2019 18:06:33 +0200 Subject: [PATCH 18/30] Report content: confirmation dialogs --- .../home/room/detail/RoomDetailActions.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 53 ++++++++++++++++++- .../home/room/detail/RoomDetailViewModel.kt | 23 +++----- .../home/room/detail/RoomDetailViewState.kt | 3 +- vector/src/main/res/values/strings_riotX.xml | 11 ++++ 5 files changed, 71 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt index eec9eef5dc..25b526fb8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt @@ -50,7 +50,7 @@ sealed class RoomDetailActions { data class ResendMessage(val eventId: String) : RoomDetailActions() data class RemoveFailedEcho(val eventId: String) : RoomDetailActions() - data class ReportContent(val eventId: String, val reason: String) : RoomDetailActions() + data class ReportContent(val eventId: String, val reason: String, val spam: Boolean = false, val inappropriate: Boolean = false) : RoomDetailActions() object ClearSendQueue : RoomDetailActions() object ResendAll : RoomDetailActions() 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 d26dba44e1..1a6023f37d 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 @@ -268,6 +268,10 @@ class RoomDetailFragment : roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState -> syncStateView.render(syncState) } + + roomDetailViewModel.requestLiveData.observeEvent(this) { + displayRoomDetailActionResult(it) + } } override fun onDestroy() { @@ -777,6 +781,51 @@ class RoomDetailFragment : .show() } + private fun displayRoomDetailActionResult(result: Async) { + when (result) { + is Fail -> { + AlertDialog.Builder(activity!!) + .setTitle(R.string.dialog_title_error) + .setMessage(errorFormatter.toHumanReadable(result.error)) + .setPositiveButton(R.string.ok, null) + .show() + } + is Success -> { + when (val data = result.invoke()) { + is RoomDetailActions.ReportContent -> { + when { + data.spam -> { + AlertDialog.Builder(activity!!) + .setTitle(R.string.content_reported_as_spam_title) + .setMessage(R.string.content_reported_as_spam_content) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } + .show() + } + data.inappropriate -> { + AlertDialog.Builder(activity!!) + .setTitle(R.string.content_reported_as_inappropriate_title) + .setMessage(R.string.content_reported_as_inappropriate_content) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } + .show() + } + else -> { + AlertDialog.Builder(activity!!) + .setTitle(R.string.content_reported_title) + .setMessage(R.string.content_reported_content) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } + .show() + } + } + } + } + } + } + } + + // TimelineEventController.Callback ************************************************************ override fun onUrlClicked(url: String): Boolean { @@ -1064,10 +1113,10 @@ class RoomDetailFragment : roomDetailViewModel.process(RoomDetailActions.RemoveFailedEcho(action.eventId)) } is SimpleAction.ReportContentSpam -> { - roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is spam")) + roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is spam", spam = true)) } is SimpleAction.ReportContentInappropriate -> { - roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is inappropriate")) + roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is inappropriate", inappropriate = true)) } // TODO Custom else -> { 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 ee60a89bc1..0774e48d27 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 @@ -94,6 +94,11 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private var timeline = room.createTimeline(eventId, timelineSettings) + // Can be used for several actions, for a one shot result + private val _requestLiveData = MutableLiveData>>() + val requestLiveData: LiveData>> + get() = _requestLiveData + // Slot to keep a pending action during permission request var pendingAction: RoomDetailActions? = null @@ -707,27 +712,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleReportContent(action: RoomDetailActions.ReportContent) { - setState { - copy( - reportContentRequest = Loading() - ) - } - room.reportContent(action.eventId, -100, action.reason, object : MatrixCallback { override fun onSuccess(data: Unit) { - setState { - copy( - reportContentRequest = Success(Unit) - ) - } + _requestLiveData.postValue(LiveEvent(Success(action))) } override fun onFailure(failure: Throwable) { - setState { - copy( - reportContentRequest = Fail(failure) - ) - } + _requestLiveData.postValue(LiveEvent(Fail(failure))) } }) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt index ba20b9ed3f..03110858a1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt @@ -52,8 +52,7 @@ data class RoomDetailViewState( val tombstoneEvent: Event? = null, val tombstoneEventHandling: Async = Uninitialized, val syncState: SyncState = SyncState.IDLE, - val highlightedEventId: String? = null, - val reportContentRequest: Async = Uninitialized + val highlightedEventId: String? = null ) : MvRxState { constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 6a2bcef5a7..30fae49f13 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -41,4 +41,15 @@ "It's spam" "It's inappropriate" "Custom report" + "Report this content" + "Reason for reporting this content" + "REPORT" + "BLOCK USER" + + "Content reported" + "This content was reported.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages" + "Reported as spam" + "This content was reported as spam.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages" + "Reported as inappropriate" + "This content was reported as inappropriate.\n\nIf you don't want to see any more content from this user, you can block him to hide his messages" From 6ad1932fe5c7819affa372aee53077275372c259 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Oct 2019 18:28:26 +0200 Subject: [PATCH 19/30] Report content: custom reason --- .../home/room/detail/RoomDetailFragment.kt | 32 +++++++++++++++---- .../main/res/layout/dialog_report_content.xml | 28 ++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 vector/src/main/res/layout/dialog_report_content.xml 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 1a6023f37d..264c0e98bc 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 @@ -50,6 +50,7 @@ import com.airbnb.mvrx.* import com.github.piasy.biv.BigImageViewer import com.github.piasy.biv.loader.ImageLoader import com.google.android.material.snackbar.Snackbar +import com.google.android.material.textfield.TextInputEditText import com.jaiselrahman.filepicker.activity.FilePickerActivity import com.jaiselrahman.filepicker.config.Configurations import com.jaiselrahman.filepicker.model.MediaFile @@ -774,17 +775,34 @@ class RoomDetailFragment : } private fun displayCommandError(message: String) { - AlertDialog.Builder(activity!!) + AlertDialog.Builder(requireActivity()) .setTitle(R.string.command_error) .setMessage(message) .setPositiveButton(R.string.ok, null) .show() } + private fun promptReasonToReportContent(action: SimpleAction.ReportContentCustom) { + val inflater = requireActivity().layoutInflater + val layout = inflater.inflate(R.layout.dialog_report_content, null) + + val input = layout.findViewById(R.id.dialog_report_content_input) + + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.report_content_custom_title) + .setView(layout) + .setPositiveButton(R.string.report_content_custom_submit) { _, _ -> + val reason = input.text.toString() + roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, reason)) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + private fun displayRoomDetailActionResult(result: Async) { when (result) { is Fail -> { - AlertDialog.Builder(activity!!) + AlertDialog.Builder(requireActivity()) .setTitle(R.string.dialog_title_error) .setMessage(errorFormatter.toHumanReadable(result.error)) .setPositiveButton(R.string.ok, null) @@ -795,7 +813,7 @@ class RoomDetailFragment : is RoomDetailActions.ReportContent -> { when { data.spam -> { - AlertDialog.Builder(activity!!) + AlertDialog.Builder(requireActivity()) .setTitle(R.string.content_reported_as_spam_title) .setMessage(R.string.content_reported_as_spam_content) .setPositiveButton(R.string.ok, null) @@ -803,7 +821,7 @@ class RoomDetailFragment : .show() } data.inappropriate -> { - AlertDialog.Builder(activity!!) + AlertDialog.Builder(requireActivity()) .setTitle(R.string.content_reported_as_inappropriate_title) .setMessage(R.string.content_reported_as_inappropriate_content) .setPositiveButton(R.string.ok, null) @@ -811,7 +829,7 @@ class RoomDetailFragment : .show() } else -> { - AlertDialog.Builder(activity!!) + AlertDialog.Builder(requireActivity()) .setTitle(R.string.content_reported_title) .setMessage(R.string.content_reported_content) .setPositiveButton(R.string.ok, null) @@ -1118,7 +1136,9 @@ class RoomDetailFragment : is SimpleAction.ReportContentInappropriate -> { roomDetailViewModel.process(RoomDetailActions.ReportContent(action.eventId, "This message is inappropriate", inappropriate = true)) } - // TODO Custom + is SimpleAction.ReportContentCustom -> { + promptReasonToReportContent(action) + } else -> { Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show() } diff --git a/vector/src/main/res/layout/dialog_report_content.xml b/vector/src/main/res/layout/dialog_report_content.xml new file mode 100644 index 0000000000..dda84fe02d --- /dev/null +++ b/vector/src/main/res/layout/dialog_report_content.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file From 36042ed14575247473365cbf994827a18b82aef8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Oct 2019 09:59:21 +0200 Subject: [PATCH 20/30] Report content: red color for "block user" button --- .../vector/riotx/core/dialogs/Extensions.kt | 27 +++++++++++++++++++ .../home/room/detail/RoomDetailFragment.kt | 5 ++++ 2 files changed, 32 insertions(+) create mode 100644 vector/src/main/java/im/vector/riotx/core/dialogs/Extensions.kt diff --git a/vector/src/main/java/im/vector/riotx/core/dialogs/Extensions.kt b/vector/src/main/java/im/vector/riotx/core/dialogs/Extensions.kt new file mode 100644 index 0000000000..1b90e88864 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/dialogs/Extensions.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.core.dialogs + +import androidx.annotation.ColorRes +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import im.vector.riotx.R + +fun AlertDialog.withColoredButton(whichButton: Int, @ColorRes color: Int = R.color.vector_error_color): AlertDialog { + getButton(whichButton)?.setTextColor(ContextCompat.getColor(context, color)) + return this +} 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 264c0e98bc..4149436166 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 @@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.detail import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.graphics.drawable.ColorDrawable import android.net.Uri @@ -69,6 +70,7 @@ import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent +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 @@ -819,6 +821,7 @@ class RoomDetailFragment : .setPositiveButton(R.string.ok, null) .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } .show() + .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } data.inappropriate -> { AlertDialog.Builder(requireActivity()) @@ -827,6 +830,7 @@ class RoomDetailFragment : .setPositiveButton(R.string.ok, null) .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } .show() + .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } else -> { AlertDialog.Builder(requireActivity()) @@ -835,6 +839,7 @@ class RoomDetailFragment : .setPositiveButton(R.string.ok, null) .setNegativeButton(R.string.block_user) { _, _ -> vectorBaseActivity.notImplemented("block user") } .show() + .withColoredButton(DialogInterface.BUTTON_NEGATIVE) } } } From b2f6fb8c916868b823bfa132395d285b40734a77 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Oct 2019 10:57:31 +0200 Subject: [PATCH 21/30] Try to fix the bottom sheet showing expanded by default. The second time it's open, it's not expanded... With this fix, the bug appear only at the third time... --- .../VectorBaseBottomSheetDialogFragment.kt | 20 +++++++++++++++++++ .../action/MessageActionsBottomSheet.kt | 2 ++ 2 files changed, 22 insertions(+) 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 892f7b0daa..168aaf87f0 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 @@ -15,12 +15,17 @@ */ package im.vector.riotx.core.platform +import android.app.Dialog import android.content.Context import android.os.Bundle import android.os.Parcelable +import android.view.View +import android.widget.FrameLayout import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRxView import com.airbnb.mvrx.MvRxViewModelStore +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.ScreenComponent @@ -40,6 +45,8 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() activity as VectorBaseActivity } + open val showExpanded = false + override fun onAttach(context: Context) { screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity) super.onAttach(context) @@ -56,6 +63,19 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() super.onCreate(savedInstanceState) } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return super.onCreateDialog(savedInstanceState).apply { + if (showExpanded) { + setOnShowListener { dialog -> + val d = dialog as BottomSheetDialog + + val bottomSheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout? + BottomSheetBehavior.from(bottomSheet!!).state = BottomSheetBehavior.STATE_EXPANDED + } + } + } + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) mvrxViewModelStore.saveViewModels(outState) 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 e1ff0ce628..95b2170d68 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 @@ -38,6 +38,8 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class) + override val showExpanded = true + private lateinit var actionHandlerModel: ActionsHandler override fun injectWith(screenComponent: ScreenComponent) { From 0bcc84cbd61ee2c6a2cc7f0d00e5f0c701e20ee9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Oct 2019 11:16:38 +0200 Subject: [PATCH 22/30] Try to fix the bottom sheet showing expanded by default #2 Seems ok now --- .../VectorBaseBottomSheetDialogFragment.kt | 14 +++++++++++++- .../readreceipts/DisplayReadReceiptsBottomSheet.kt | 4 +--- .../timeline/action/MessageActionsBottomSheet.kt | 1 + .../edithistory/ViewEditHistoryBottomSheet.kt | 1 + .../timeline/reactions/ViewReactionsBottomSheet.kt | 1 + 5 files changed, 17 insertions(+), 4 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 168aaf87f0..f793795910 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 @@ -21,6 +21,7 @@ import android.os.Bundle import android.os.Parcelable import android.view.View import android.widget.FrameLayout +import androidx.annotation.CallSuper import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRxView import com.airbnb.mvrx.MvRxViewModelStore @@ -41,6 +42,8 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() private lateinit var screenComponent: ScreenComponent final override val mvrxViewId: String by lazy { mvrxPersistedViewId } + private var bottomSheetBehavior: BottomSheetBehavior? = null + val vectorBaseActivity: VectorBaseActivity by lazy { activity as VectorBaseActivity } @@ -70,7 +73,8 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() val d = dialog as BottomSheetDialog val bottomSheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout? - BottomSheetBehavior.from(bottomSheet!!).state = BottomSheetBehavior.STATE_EXPANDED + bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet!!) + bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED } } } @@ -89,6 +93,14 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() postInvalidate() } + @CallSuper + override fun invalidate() { + if (showExpanded) { + // Force the bottom sheet to be expanded + bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED + } + } + protected fun setArguments(args: Parcelable? = null) { arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } } } 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 80539d73e4..6cf5191a6d 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 @@ -73,9 +73,7 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() { epoxyController.setData(displayReadReceiptArgs.readReceipts) } - override fun invalidate() { - // we are not using state for this one as it's static - } + // we are not using state for this one as it's static, so no need to override invalidate() companion object { fun newInstance(readReceipts: List): DisplayReadReceiptsBottomSheet { 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 95b2170d68..e209a3d887 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 @@ -70,6 +70,7 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message override fun invalidate() = withState(viewModel) { messageActionsEpoxyController.setData(it) + super.invalidate() } companion object { 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 acdef2058d..f0da92e1dd 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 @@ -74,6 +74,7 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun invalidate() = withState(viewModel) { epoxyController.setData(it) + super.invalidate() } companion object { 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 deb2a84818..c93c41fa97 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 @@ -71,6 +71,7 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun invalidate() = withState(viewModel) { epoxyController.setData(it) + super.invalidate() } companion object { From abdb83b9fd0bd3e16f416062d4be1eab4f1af0b4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Oct 2019 13:34:04 +0200 Subject: [PATCH 23/30] Report content: change log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index d58b7017d0..1c0e8798de 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements: - Do not upload file too big for the homeserver (#587) - Handle read markers (#84) - Mark all messages as read (#396) + - Add ability to report content (#515) Other changes: - Accessibility improvements to read receipts in the room timeline and reactions emoji chooser From e39c4a79257b80015523e0c8d0c956490f76a9ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Oct 2019 16:41:02 +0200 Subject: [PATCH 24/30] fix ktlint issue --- .../android/api/session/room/reporting/ReportingService.kt | 1 - .../vector/riotx/features/home/room/detail/RoomDetailFragment.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/reporting/ReportingService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/reporting/ReportingService.kt index 9dbc6d0e9e..71ce02ac69 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/reporting/ReportingService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/reporting/ReportingService.kt @@ -29,5 +29,4 @@ interface ReportingService { * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-rooms-roomid-report-eventid */ fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback): 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 4149436166..9aa2f3cccd 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 @@ -848,7 +848,6 @@ class RoomDetailFragment : } } - // TimelineEventController.Callback ************************************************************ override fun onUrlClicked(url: String): Boolean { From 6ccd083451956a27e774ee9e6b012112b03ac219 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 18 Oct 2019 19:29:57 +0200 Subject: [PATCH 25/30] Bottom sheet: fix RecyclerView usage --- .../VectorBaseBottomSheetDialogFragment.kt | 12 +++++----- .../DisplayReadReceiptsBottomSheet.kt | 17 +++++++------- .../action/MessageActionsBottomSheet.kt | 22 ++++++++++++++----- .../edithistory/ViewEditHistoryBottomSheet.kt | 19 ++++++++-------- .../reactions/ViewReactionsBottomSheet.kt | 17 +++++++------- .../res/layout/bottom_sheet_generic_list.xml | 11 ++++++++++ ... bottom_sheet_generic_list_with_title.xml} | 10 ++++----- .../bottom_sheet_generic_recycler_epoxy.xml | 21 ------------------ 8 files changed, 62 insertions(+), 67 deletions(-) create mode 100644 vector/src/main/res/layout/bottom_sheet_generic_list.xml rename vector/src/main/res/layout/{bottom_sheet_epoxylist_with_title.xml => bottom_sheet_generic_list_with_title.xml} (77%) delete mode 100644 vector/src/main/res/layout/bottom_sheet_generic_recycler_epoxy.xml 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 f793795910..f3c15b53ca 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 @@ -30,6 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.ScreenComponent +import im.vector.riotx.core.utils.DimensionConverter import java.util.* /** @@ -68,14 +69,11 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return super.onCreateDialog(savedInstanceState).apply { + val dialog = this as? BottomSheetDialog + bottomSheetBehavior = dialog?.behavior + bottomSheetBehavior?.setPeekHeight(DimensionConverter(resources).dpToPx(400), false) if (showExpanded) { - setOnShowListener { dialog -> - val d = dialog as BottomSheetDialog - - val bottomSheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout? - bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet!!) - bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED - } + bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED } } } 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 6cf5191a6d..031cf06e39 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 @@ -23,9 +23,10 @@ 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 -import com.airbnb.epoxy.EpoxyRecyclerView import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.args import im.vector.riotx.R @@ -33,7 +34,7 @@ import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import kotlinx.android.parcel.Parcelize -import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.* +import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.* import javax.inject.Inject @Parcelize @@ -48,8 +49,8 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() { @Inject lateinit var epoxyController: DisplayReadReceiptsController - @BindView(R.id.bottom_sheet_display_reactions_list) - lateinit var epoxyRecyclerView: EpoxyRecyclerView + @BindView(R.id.bottomSheetRecyclerView) + lateinit var recyclerView: RecyclerView private val displayReadReceiptArgs: DisplayReadReceiptArgs by args() @@ -58,17 +59,15 @@ class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_epoxylist_with_title, container, false) + val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false) ButterKnife.bind(this, view) return view } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - epoxyRecyclerView.setController(epoxyController) - val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, - LinearLayout.VERTICAL) - epoxyRecyclerView.addItemDecoration(dividerItemDecoration) + recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + recyclerView.adapter = epoxyController.adapter bottomSheetTitle.text = getString(R.string.read_at) epoxyController.setData(displayReadReceiptArgs.readReceipts) } 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 e209a3d887..48ab4cee12 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,26 +19,34 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import butterknife.ButterKnife 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.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData -import kotlinx.android.synthetic.main.bottom_sheet_generic_recycler_epoxy.* +import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.* import javax.inject.Inject /** * Bottom sheet fragment that shows a message preview with list of contextual actions */ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), MessageActionsEpoxyController.MessageActionsEpoxyControllerListener { + @Inject lateinit var messageActionViewModelFactory: MessageActionsViewModel.Factory @Inject lateinit var messageActionsEpoxyController: MessageActionsEpoxyController - private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class) + @BindView(R.id.bottomSheetRecyclerView) + lateinit var recyclerView: RecyclerView - override val showExpanded = true + private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class) private lateinit var actionHandlerModel: ActionsHandler @@ -47,14 +55,16 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.bottom_sheet_generic_recycler_epoxy, container, false) + val view = inflater.inflate(R.layout.bottom_sheet_generic_list, container, false) + ButterKnife.bind(this, view) + return view } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) actionHandlerModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) - - bottomSheetEpoxyRecyclerView.setController(messageActionsEpoxyController) + recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) + recyclerView.adapter = messageActionsEpoxyController.adapter messageActionsEpoxyController.listener = this } 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 f0da92e1dd..709bcb53c7 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 @@ -21,9 +21,10 @@ 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 -import com.airbnb.epoxy.EpoxyRecyclerView import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -33,7 +34,7 @@ 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 import im.vector.riotx.features.html.EventHtmlRenderer -import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.* +import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.* import javax.inject.Inject /** @@ -46,8 +47,8 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() { @Inject lateinit var viewEditHistoryViewModelFactory: ViewEditHistoryViewModel.Factory @Inject lateinit var eventHtmlRenderer: EventHtmlRenderer - @BindView(R.id.bottom_sheet_display_reactions_list) - lateinit var epoxyRecyclerView: EpoxyRecyclerView + @BindView(R.id.bottomSheetRecyclerView) + lateinit var recyclerView: RecyclerView private val epoxyController by lazy { ViewEditHistoryEpoxyController(requireContext(), viewModel.dateFormatter, eventHtmlRenderer) @@ -58,17 +59,17 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_epoxylist_with_title, container, false) + val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false) ButterKnife.bind(this, view) return view } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - epoxyRecyclerView.setController(epoxyController) - val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, - LinearLayout.VERTICAL) - epoxyRecyclerView.addItemDecoration(dividerItemDecoration) + recyclerView.adapter = epoxyController.adapter + recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + val dividerItemDecoration = DividerItemDecoration(requireContext(), LinearLayout.VERTICAL) + recyclerView.addItemDecoration(dividerItemDecoration) bottomSheetTitle.text = context?.getString(R.string.message_edits) } 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 c93c41fa97..a966007fc4 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 @@ -22,9 +22,10 @@ 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 -import com.airbnb.epoxy.EpoxyRecyclerView import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -33,7 +34,7 @@ import im.vector.riotx.core.di.ScreenComponent 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 -import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.* +import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.* import javax.inject.Inject /** @@ -45,8 +46,8 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() { @Inject lateinit var viewReactionsViewModelFactory: ViewReactionsViewModel.Factory - @BindView(R.id.bottom_sheet_display_reactions_list) - lateinit var epoxyRecyclerView: EpoxyRecyclerView + @BindView(R.id.bottomSheetRecyclerView) + lateinit var recyclerView: RecyclerView @Inject lateinit var epoxyController: ViewReactionsEpoxyController @@ -55,17 +56,15 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.bottom_sheet_epoxylist_with_title, container, false) + val view = inflater.inflate(R.layout.bottom_sheet_generic_list_with_title, container, false) ButterKnife.bind(this, view) return view } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - epoxyRecyclerView.setController(epoxyController) - val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context, - LinearLayout.VERTICAL) - epoxyRecyclerView.addItemDecoration(dividerItemDecoration) + recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + recyclerView.adapter = epoxyController.adapter bottomSheetTitle.text = context?.getString(R.string.reactions) } diff --git a/vector/src/main/res/layout/bottom_sheet_generic_list.xml b/vector/src/main/res/layout/bottom_sheet_generic_list.xml new file mode 100644 index 0000000000..69b5ce2fac --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_generic_list.xml @@ -0,0 +1,11 @@ + + + diff --git a/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml b/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml similarity index 77% rename from vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml rename to vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml index a5e9c980a3..80d877ac2d 100644 --- a/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml +++ b/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml @@ -3,24 +3,22 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="400dp" android:orientation="vertical"> - - - - - - - - \ No newline at end of file From 053bf7aeac1019f02e55115607ec407261ef9123 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 21 Oct 2019 16:40:08 +0200 Subject: [PATCH 26/30] Improve layout preview a bit --- .../src/main/res/layout/item_timeline_event_base.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index ec5cac245d..fbe3b70551 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -117,7 +117,16 @@ android:layout_marginBottom="4dp" app:dividerDrawable="@drawable/reaction_divider" app:flexWrap="wrap" - app:showDivider="middle" /> + app:showDivider="middle" + tools:background="#F0E0F0" + tools:layout_height="40dp"> + + + + + Date: Mon, 21 Oct 2019 16:40:27 +0200 Subject: [PATCH 27/30] Remove unused import --- .../riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt | 1 - .../room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt | 2 -- .../room/detail/timeline/action/MessageActionsBottomSheet.kt | 3 --- .../room/detail/timeline/reactions/ViewReactionsBottomSheet.kt | 2 -- 4 files changed, 8 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 f3c15b53ca..8d40d55a7a 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 @@ -19,7 +19,6 @@ import android.app.Dialog import android.content.Context import android.os.Bundle import android.os.Parcelable -import android.view.View import android.widget.FrameLayout import androidx.annotation.CallSuper import com.airbnb.mvrx.MvRx 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 031cf06e39..50ade56474 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,8 +21,6 @@ import android.os.Parcelable 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 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 48ab4cee12..63424bba37 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,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.lifecycle.ViewModelProviders -import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import butterknife.BindView @@ -32,7 +30,6 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData -import kotlinx.android.synthetic.main.bottom_sheet_generic_list_with_title.* import javax.inject.Inject /** 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 a966007fc4..d5df8f7b40 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,8 +20,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.LinearLayout -import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import butterknife.BindView From fce576e3a4c66632d4f5682547eaa0a752ff396a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 21 Oct 2019 16:41:14 +0200 Subject: [PATCH 28/30] Message action bottom sheet expanded --- .../room/detail/timeline/action/MessageActionsBottomSheet.kt | 2 ++ 1 file changed, 2 insertions(+) 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 63424bba37..3f46b991de 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 @@ -45,6 +45,8 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message private val viewModel: MessageActionsViewModel by fragmentViewModel(MessageActionsViewModel::class) + override val showExpanded = true + private lateinit var actionHandlerModel: ActionsHandler override fun injectWith(screenComponent: ScreenComponent) { From b253722b9882ade5fda3614242a9438fc5909e34 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 21 Oct 2019 16:54:00 +0200 Subject: [PATCH 29/30] Disable animation --- .../room/detail/timeline/action/MessageActionsBottomSheet.kt | 2 ++ 1 file changed, 2 insertions(+) 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 3f46b991de..8aaa7643c2 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 @@ -64,6 +64,8 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message actionHandlerModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) recyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) recyclerView.adapter = messageActionsEpoxyController.adapter + // Disable item animation + recyclerView.itemAnimator = null messageActionsEpoxyController.listener = this } From 11b5c2c3bae88d28a27cf29d953aa8ead876ec05 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 21 Oct 2019 18:17:03 +0200 Subject: [PATCH 30/30] Restore previous log level --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 35ca815df8..2e2b110f15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx1536m vector.debugPrivateData=false -vector.httpLogLevel=HEADERS +vector.httpLogLevel=NONE # Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above #vector.debugPrivateData=true