mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-07 23:16:08 +03:00
Merge pull request #342 from vector-im/feature/edit_history
Feature/edit history
This commit is contained in:
commit
8901a5e09a
29 changed files with 613 additions and 77 deletions
CHANGES.mdbuild.gradle
matrix-sdk-android/src/main/java/im/vector/matrix/android
api/session/room/model/relation
internal
vector
|
@ -2,7 +2,7 @@ Changes in RiotX 0.2.1 (2019-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
-
|
- Message Editing: View edit history
|
||||||
|
|
||||||
Improvements:
|
Improvements:
|
||||||
- Handle click on redacted events: view source and create permalink
|
- Handle click on redacted events: view source and create permalink
|
||||||
|
|
|
@ -45,6 +45,12 @@ allprojects {
|
||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url 'https://repo.adobe.com/nexus/content/repositories/public/'
|
||||||
|
content {
|
||||||
|
includeGroupByRegex "diff_match_patch"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
package im.vector.matrix.android.api.session.room.model.relation
|
package im.vector.matrix.android.api.session.room.model.relation
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
@ -78,6 +80,11 @@ interface RelationService {
|
||||||
newBodyAutoMarkdown: Boolean,
|
newBodyAutoMarkdown: Boolean,
|
||||||
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
compatibilityBodyText: String = "* $newBodyText"): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's the edit history of the given event
|
||||||
|
*/
|
||||||
|
fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reply to an event in the timeline (must be in same room)
|
* Reply to an event in the timeline (must be in same room)
|
||||||
|
@ -91,4 +98,6 @@ interface RelationService {
|
||||||
autoMarkdown: Boolean = false): Cancelable?
|
autoMarkdown: Boolean = false): Cancelable?
|
||||||
|
|
||||||
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,7 +18,8 @@ package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
internal object NetworkConstants {
|
internal object NetworkConstants {
|
||||||
|
|
||||||
const val URI_API_PREFIX_PATH = "_matrix/client/"
|
private const val URI_API_PREFIX_PATH = "_matrix/client"
|
||||||
const val URI_API_PREFIX_PATH_R0 = "_matrix/client/r0/"
|
const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/"
|
||||||
|
const val URI_API_PREFIX_PATH_UNSTABLE = "$URI_API_PREFIX_PATH/unstable/"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,11 @@ import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
|
||||||
|
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
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.RoomMembersResponse
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody
|
import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody
|
||||||
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
|
import im.vector.matrix.android.internal.session.room.relation.RelationsResponse
|
||||||
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
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.EventContextResponse
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
|
||||||
|
@ -195,6 +196,20 @@ internal interface RoomAPI {
|
||||||
@Body content: Content?
|
@Body content: Content?
|
||||||
): Call<SendResponse>
|
): Call<SendResponse>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paginate relations for event based in normal topological order
|
||||||
|
*
|
||||||
|
* @param relationType filter for this relation type
|
||||||
|
* @param eventType filter for this event type
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}")
|
||||||
|
fun getRelations(@Path("roomId") roomId: String,
|
||||||
|
@Path("eventId") eventId: String,
|
||||||
|
@Path("relationType") relationType: String,
|
||||||
|
@Path("eventType") eventType: String
|
||||||
|
): Call<RelationsResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join the given room.
|
* Join the given room.
|
||||||
*
|
*
|
||||||
|
|
|
@ -30,8 +30,8 @@ import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRo
|
||||||
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
|
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
|
||||||
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
||||||
import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService
|
import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService
|
||||||
|
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.FindReactionEventForUndoTask
|
||||||
import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
|
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
|
||||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
||||||
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
|
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
|
||||||
|
@ -56,7 +56,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
|
||||||
private val setReadMarkersTask: SetReadMarkersTask,
|
private val setReadMarkersTask: SetReadMarkersTask,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||||
private val updateQuickReactionTask: UpdateQuickReactionTask,
|
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
||||||
private val joinRoomTask: JoinRoomTask,
|
private val joinRoomTask: JoinRoomTask,
|
||||||
private val leaveRoomTask: LeaveRoomTask) {
|
private val leaveRoomTask: LeaveRoomTask) {
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
|
||||||
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
||||||
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, credentials)
|
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, credentials)
|
||||||
val relationService = DefaultRelationService(context,
|
val relationService = DefaultRelationService(context,
|
||||||
credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, monarchy, taskExecutor)
|
credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, fetchEditHistoryTask, monarchy, taskExecutor)
|
||||||
|
|
||||||
return DefaultRoom(
|
return DefaultRoom(
|
||||||
roomId,
|
roomId,
|
||||||
|
|
|
@ -142,4 +142,7 @@ internal abstract class RoomModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFileService(fileService: DefaultFileService): FileService
|
abstract fun bindFileService(fileService: DefaultFileService): FileService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindFetchEditHistoryTask(editHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
||||||
private val eventFactory: LocalEchoEventFactory,
|
private val eventFactory: LocalEchoEventFactory,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||||
|
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor)
|
private val taskExecutor: TaskExecutor)
|
||||||
: RelationService {
|
: RelationService {
|
||||||
|
@ -131,6 +132,13 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
|
||||||
|
val params = FetchEditHistoryTask.Params(roomId, eventId)
|
||||||
|
fetchEditHistoryTask.configureWith(params)
|
||||||
|
.dispatchTo(callback)
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun replyToMessage(eventReplied: TimelineEvent, replyText: String, autoMarkdown: Boolean): Cancelable? {
|
override fun replyToMessage(eventReplied: TimelineEvent, replyText: String, autoMarkdown: Boolean): Cancelable? {
|
||||||
val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText, autoMarkdown)?.also {
|
val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText, autoMarkdown)?.also {
|
||||||
saveLocalEcho(it)
|
saveLocalEcho(it)
|
||||||
|
@ -169,7 +177,8 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
||||||
EventAnnotationsSummaryEntity.where(realm, eventId)
|
EventAnnotationsSummaryEntity.where(realm, eventId)
|
||||||
}
|
}
|
||||||
return Transformations.map(liveEntity) { realmResults ->
|
return Transformations.map(liveEntity) { realmResults ->
|
||||||
realmResults.firstOrNull()?.asDomain() ?: EventAnnotationsSummary(eventId, emptyList(), null)
|
realmResults.firstOrNull()?.asDomain()
|
||||||
|
?: EventAnnotationsSummary(eventId, emptyList(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.relation
|
||||||
|
|
||||||
|
import arrow.core.Try
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||||
|
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 FetchEditHistoryTask : Task<FetchEditHistoryTask.Params, List<Event>> {
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val roomId: String,
|
||||||
|
val eventId: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class DefaultFetchEditHistoryTask @Inject constructor(
|
||||||
|
private val roomAPI: RoomAPI
|
||||||
|
) : FetchEditHistoryTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: FetchEditHistoryTask.Params): Try<List<Event>> {
|
||||||
|
return executeRequest<RelationsResponse> {
|
||||||
|
apiCall = roomAPI.getRelations(params.roomId, params.eventId, RelationType.REPLACE, EventType.MESSAGE)
|
||||||
|
}.map { resp ->
|
||||||
|
resp.chunks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.matrix.android.internal.session.room.relation
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class RelationsResponse(
|
||||||
|
@Json(name = "chunk") val chunks: List<Event>,
|
||||||
|
@Json(name = "next_batch") val nextBatch: String?,
|
||||||
|
@Json(name = "prev_batch") val prevBatch: String?
|
||||||
|
)
|
|
@ -254,6 +254,8 @@ dependencies {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementation 'diff_match_patch:diff_match_patch:current'
|
||||||
|
|
||||||
// TESTS
|
// TESTS
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
|
|
@ -344,6 +344,13 @@ SOFTWARE.
|
||||||
<br/>
|
<br/>
|
||||||
Copyright (c) 2018, Jaisel Rahman
|
Copyright (c) 2018, Jaisel Rahman
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>diff-match-patch</b>
|
||||||
|
<br/>
|
||||||
|
Copyright 2018 The diff-match-patch Authors. https://github.com/google/diff-match-patch
|
||||||
|
</li>
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<pre>
|
<pre>
|
||||||
Apache License
|
Apache License
|
||||||
|
|
|
@ -35,10 +35,7 @@ import im.vector.riotx.features.crypto.verification.SASVerificationIncomingFragm
|
||||||
import im.vector.riotx.features.home.*
|
import im.vector.riotx.features.home.*
|
||||||
import im.vector.riotx.features.home.group.GroupListFragment
|
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.RoomDetailFragment
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuFragment
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.QuickReactionFragment
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
|
|
||||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||||
import im.vector.riotx.features.invite.VectorInviteView
|
import im.vector.riotx.features.invite.VectorInviteView
|
||||||
import im.vector.riotx.features.login.LoginActivity
|
import im.vector.riotx.features.login.LoginActivity
|
||||||
|
@ -93,6 +90,8 @@ interface ScreenComponent {
|
||||||
|
|
||||||
fun inject(viewReactionBottomSheet: ViewReactionBottomSheet)
|
fun inject(viewReactionBottomSheet: ViewReactionBottomSheet)
|
||||||
|
|
||||||
|
fun inject(viewEditHistoryBottomSheet: ViewEditHistoryBottomSheet)
|
||||||
|
|
||||||
fun inject(messageMenuFragment: MessageMenuFragment)
|
fun inject(messageMenuFragment: MessageMenuFragment)
|
||||||
|
|
||||||
fun inject(vectorSettingsActivity: VectorSettingsActivity)
|
fun inject(vectorSettingsActivity: VectorSettingsActivity)
|
||||||
|
|
|
@ -29,11 +29,7 @@ import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsVie
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel_AssistedFactory
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||||
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
||||||
import im.vector.riotx.features.home.HomeActivityViewModel
|
import im.vector.riotx.features.home.*
|
||||||
import im.vector.riotx.features.home.HomeActivityViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.home.HomeDetailViewModel
|
|
||||||
import im.vector.riotx.features.home.HomeDetailViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.home.HomeNavigationViewModel
|
|
||||||
import im.vector.riotx.features.home.group.GroupListViewModel
|
import im.vector.riotx.features.home.group.GroupListViewModel
|
||||||
import im.vector.riotx.features.home.group.GroupListViewModel_AssistedFactory
|
import im.vector.riotx.features.home.group.GroupListViewModel_AssistedFactory
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel
|
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel
|
||||||
|
@ -59,7 +55,7 @@ import im.vector.riotx.features.workers.signout.SignOutViewModel
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
interface ViewModelModule {
|
interface ViewModelModule {
|
||||||
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindViewModelFactory(factory: VectorViewModelFactory): ViewModelProvider.Factory
|
fun bindViewModelFactory(factory: VectorViewModelFactory): ViewModelProvider.Factory
|
||||||
|
@ -156,6 +152,9 @@ interface ViewModelModule {
|
||||||
@Binds
|
@Binds
|
||||||
fun bindViewReactionViewModelFactory(factory: ViewReactionViewModel_AssistedFactory): ViewReactionViewModel.Factory
|
fun bindViewReactionViewModelFactory(factory: ViewReactionViewModel_AssistedFactory): ViewReactionViewModel.Factory
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindViewEditHistoryViewModelFactory(factory: ViewEditHistoryViewModel_AssistedFactory): ViewEditHistoryViewModel.Factory
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindCreateRoomViewModelFactory(factory: CreateRoomViewModel_AssistedFactory): CreateRoomViewModel.Factory
|
fun bindCreateRoomViewModelFactory(factory: CreateRoomViewModel_AssistedFactory): CreateRoomViewModel.Factory
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {
|
||||||
var title: String? = null
|
var title: String? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var description: String? = null
|
var description: CharSequence? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var style: STYLE = STYLE.NORMAL_TEXT
|
var style: STYLE = STYLE.NORMAL_TEXT
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* 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.ui.list
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic list item header left aligned with notice color.
|
||||||
|
*/
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_generic_header)
|
||||||
|
abstract class GenericItemHeader : VectorEpoxyModel<GenericItemHeader.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var text: String? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
holder.text.setTextOrHide(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
val text by bind<TextView>(R.id.itemGenericHeaderText)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package im.vector.riotx.core.ui.list
|
||||||
|
|
||||||
|
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 generic list item header left aligned with notice color.
|
||||||
|
*/
|
||||||
|
@EpoxyModelClass(layout = R.layout.item_generic_loader)
|
||||||
|
abstract class GenericLoaderItem : VectorEpoxyModel<GenericLoaderItem.Holder>() {
|
||||||
|
|
||||||
|
//Maybe/Later add some style configuration, SMALL/BIG ?
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {}
|
||||||
|
|
||||||
|
class Holder : VectorEpoxyHolder()
|
||||||
|
}
|
|
@ -32,7 +32,6 @@ sealed class RoomDetailActions {
|
||||||
data class RedactAction(val targetEventId: String, val reason: 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 UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions()
|
||||||
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions()
|
data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions()
|
||||||
data class ShowEditHistoryAction(val event: String, val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions()
|
|
||||||
data class NavigateToEvent(val eventId: String, val position: Int?) : RoomDetailActions()
|
data class NavigateToEvent(val eventId: String, val position: Int?) : RoomDetailActions()
|
||||||
data class DownloadFile(val eventId: String, val messageFileContent: MessageFileContent) : RoomDetailActions()
|
data class DownloadFile(val eventId: String, val messageFileContent: MessageFileContent) : RoomDetailActions()
|
||||||
object AcceptInvite : RoomDetailActions()
|
object AcceptInvite : RoomDetailActions()
|
||||||
|
|
|
@ -85,10 +85,7 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
|
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.composer.TextComposerViewState
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.ActionsHandler
|
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuViewModel
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
|
import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
|
@ -666,10 +663,8 @@ class RoomDetailFragment :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) {
|
override fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) {
|
||||||
editAggregatedSummary?.also {
|
ViewEditHistoryBottomSheet.newInstance(roomDetailArgs.roomId, informationData)
|
||||||
roomDetailViewModel.process(RoomDetailActions.ShowEditHistoryAction(informationData.eventId, it))
|
.show(requireActivity().supportFragmentManager, "DISPLAY_EDITS")
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
// AutocompleteUserPresenter.Callback
|
// AutocompleteUserPresenter.Callback
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
is RoomDetailActions.RedactAction -> handleRedactEvent(action)
|
is RoomDetailActions.RedactAction -> handleRedactEvent(action)
|
||||||
is RoomDetailActions.UndoReaction -> handleUndoReact(action)
|
is RoomDetailActions.UndoReaction -> handleUndoReact(action)
|
||||||
is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
||||||
is RoomDetailActions.ShowEditHistoryAction -> handleShowEditHistoryReaction(action)
|
|
||||||
is RoomDetailActions.EnterEditMode -> handleEditAction(action)
|
is RoomDetailActions.EnterEditMode -> handleEditAction(action)
|
||||||
is RoomDetailActions.EnterQuoteMode -> handleQuoteAction(action)
|
is RoomDetailActions.EnterQuoteMode -> handleQuoteAction(action)
|
||||||
is RoomDetailActions.EnterReplyMode -> handleReplyAction(action)
|
is RoomDetailActions.EnterReplyMode -> handleReplyAction(action)
|
||||||
|
@ -309,22 +308,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||||
return finalText
|
return finalText
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleShowEditHistoryReaction(action: RoomDetailActions.ShowEditHistoryAction) {
|
|
||||||
//TODO temporary implementation
|
|
||||||
val lastReplace = action.editAggregatedSummary.sourceEvents.lastOrNull()?.let {
|
|
||||||
room.getTimeLineEvent(it)
|
|
||||||
} ?: return
|
|
||||||
|
|
||||||
val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
|
|
||||||
_nonBlockingPopAlert.postValue(LiveEvent(
|
|
||||||
Pair(R.string.last_edited_info_message, listOf(
|
|
||||||
lastReplace.getDisambiguatedDisplayName(),
|
|
||||||
dateFormat.format(Date(lastReplace.root.originServerTs ?: 0)))
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
||||||
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
_sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* 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.LinearLayout
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
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
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
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 javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom sheet displaying list of edits for a given event ordered by timestamp
|
||||||
|
*/
|
||||||
|
class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
|
|
||||||
|
private val viewModel: ViewEditHistoryViewModel by fragmentViewModel(ViewEditHistoryViewModel::class)
|
||||||
|
|
||||||
|
@Inject lateinit var viewEditHistoryViewModelFactory: ViewEditHistoryViewModel.Factory
|
||||||
|
@Inject lateinit var eventHtmlRenderer: EventHtmlRenderer
|
||||||
|
|
||||||
|
@BindView(R.id.bottom_sheet_display_reactions_list)
|
||||||
|
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
||||||
|
|
||||||
|
private val epoxyController by lazy {
|
||||||
|
ViewEditHistoryEpoxyController(requireContext(), viewModel.timelineDateFormatter, eventHtmlRenderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
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_epoxylist_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)
|
||||||
|
bottomSheetTitle.text = context?.getString(R.string.message_edits)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) {
|
||||||
|
epoxyController.setData(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(roomId: String, informationData: MessageInformationData): ViewEditHistoryBottomSheet {
|
||||||
|
val args = Bundle()
|
||||||
|
val parcelableArgs = TimelineEventFragmentArgs(
|
||||||
|
informationData.eventId,
|
||||||
|
roomId,
|
||||||
|
informationData
|
||||||
|
)
|
||||||
|
args.putParcelable(MvRx.KEY_ARG, parcelableArgs)
|
||||||
|
return ViewEditHistoryBottomSheet().apply { arguments = args }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* 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.text.Spannable
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Incomplete
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.localDateTime
|
||||||
|
import im.vector.riotx.core.ui.list.genericFooterItem
|
||||||
|
import im.vector.riotx.core.ui.list.genericItem
|
||||||
|
import im.vector.riotx.core.ui.list.genericItemHeader
|
||||||
|
import im.vector.riotx.core.ui.list.genericLoaderItem
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||||
|
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
import name.fraser.neil.plaintext.diff_match_patch
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Epoxy controller for reaction event list
|
||||||
|
*/
|
||||||
|
class ViewEditHistoryEpoxyController(private val context: Context,
|
||||||
|
val timelineDateFormatter: TimelineDateFormatter,
|
||||||
|
val eventHtmlRenderer: EventHtmlRenderer) : TypedEpoxyController<ViewEditHistoryViewState>() {
|
||||||
|
|
||||||
|
override fun buildModels(state: ViewEditHistoryViewState) {
|
||||||
|
when (state.editList) {
|
||||||
|
is Incomplete -> {
|
||||||
|
genericLoaderItem {
|
||||||
|
id("Spinner")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Fail -> {
|
||||||
|
genericFooterItem {
|
||||||
|
id("failure")
|
||||||
|
text(context.getString(R.string.unknown_error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
state.editList()?.let { renderEvents(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderEvents(sourceEvents: List<Event>) {
|
||||||
|
if (sourceEvents.isEmpty()) {
|
||||||
|
genericItem {
|
||||||
|
id("footer")
|
||||||
|
title(context.getString(R.string.no_message_edits_found))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var lastDate: Calendar? = null
|
||||||
|
sourceEvents.sortedByDescending {
|
||||||
|
it.originServerTs ?: 0
|
||||||
|
}.forEachIndexed { index, timelineEvent ->
|
||||||
|
|
||||||
|
val evDate = Calendar.getInstance().apply {
|
||||||
|
timeInMillis = timelineEvent.originServerTs
|
||||||
|
?: System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) {
|
||||||
|
//need to display header with day
|
||||||
|
val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today)
|
||||||
|
else timelineDateFormatter.formatMessageDay(timelineEvent.localDateTime())
|
||||||
|
genericItemHeader {
|
||||||
|
id(evDate.hashCode())
|
||||||
|
text(dateString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastDate = evDate
|
||||||
|
val cContent = getCorrectContent(timelineEvent)
|
||||||
|
val body = cContent.second?.let { eventHtmlRenderer.render(it) }
|
||||||
|
?: cContent.first
|
||||||
|
|
||||||
|
val nextEvent = if (index + 1 <= sourceEvents.lastIndex) sourceEvents[index + 1] else null
|
||||||
|
|
||||||
|
var spannedDiff: Spannable? = null
|
||||||
|
if (nextEvent != null && cContent.second == null /*No diff for html*/) {
|
||||||
|
//compares the body
|
||||||
|
val nContent = getCorrectContent(nextEvent)
|
||||||
|
val nextBody = nContent.second?.let { eventHtmlRenderer.render(it) }
|
||||||
|
?: nContent.first
|
||||||
|
val dmp = diff_match_patch()
|
||||||
|
val diff = dmp.diff_main(nextBody.toString(), body.toString())
|
||||||
|
Timber.e("#### Diff: $diff")
|
||||||
|
dmp.diff_cleanupSemantic(diff)
|
||||||
|
Timber.e("#### Diff: $diff")
|
||||||
|
spannedDiff = span {
|
||||||
|
diff.map {
|
||||||
|
when (it.operation) {
|
||||||
|
diff_match_patch.Operation.DELETE -> {
|
||||||
|
span {
|
||||||
|
text = it.text
|
||||||
|
textColor = ContextCompat.getColor(context, R.color.vector_error_color)
|
||||||
|
textDecorationLine = "line-through"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff_match_patch.Operation.INSERT -> {
|
||||||
|
span {
|
||||||
|
text = it.text
|
||||||
|
textColor = ContextCompat.getColor(context, R.color.vector_success_color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
span {
|
||||||
|
text = it.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
genericItem {
|
||||||
|
id(timelineEvent.eventId)
|
||||||
|
title(timelineDateFormatter.formatMessageHour(timelineEvent.localDateTime()))
|
||||||
|
description(spannedDiff ?: body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCorrectContent(event: Event): Pair<String, String?> {
|
||||||
|
val clearContent = event.getClearContent().toModel<MessageTextContent>()
|
||||||
|
val newContent = clearContent
|
||||||
|
?.newContent
|
||||||
|
?.toModel<MessageTextContent>()
|
||||||
|
return (newContent?.body ?: clearContent?.body ?: "") to (newContent?.formattedBody
|
||||||
|
?: clearContent?.formattedBody)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* 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.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||||
|
|
||||||
|
|
||||||
|
data class ViewEditHistoryViewState(
|
||||||
|
val eventId: String,
|
||||||
|
val roomId: String,
|
||||||
|
val editList: Async<List<Event>> = Uninitialized)
|
||||||
|
: MvRxState {
|
||||||
|
|
||||||
|
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
|
||||||
|
initialState: ViewEditHistoryViewState,
|
||||||
|
val session: Session,
|
||||||
|
val timelineDateFormatter: TimelineDateFormatter
|
||||||
|
) : VectorViewModel<ViewEditHistoryViewState>(initialState) {
|
||||||
|
|
||||||
|
private val roomId = initialState.roomId
|
||||||
|
private val eventId = initialState.eventId
|
||||||
|
private val room = session.getRoom(roomId)
|
||||||
|
?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: ViewEditHistoryViewState): ViewEditHistoryViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<ViewEditHistoryViewModel, ViewEditHistoryViewState> {
|
||||||
|
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: ViewEditHistoryViewState): ViewEditHistoryViewModel? {
|
||||||
|
val fragment: ViewEditHistoryBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.viewEditHistoryViewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadHistory()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadHistory() {
|
||||||
|
setState { copy(editList = Loading()) }
|
||||||
|
room.fetchEditHistory(eventId, object : MatrixCallback<List<Event>> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(editList = Fail(failure))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: List<Event>) {
|
||||||
|
//TODO until supported by API Add original event manually
|
||||||
|
val withOriginal = data.toMutableList()
|
||||||
|
room.getTimeLineEvent(eventId)?.let {
|
||||||
|
withOriginal.add(it.root)
|
||||||
|
}
|
||||||
|
setState {
|
||||||
|
copy(editList = Success(withOriginal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,7 +21,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.ButterKnife
|
import butterknife.ButterKnife
|
||||||
|
@ -33,7 +32,7 @@ import im.vector.riotx.EmojiCompatFontProvider
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import kotlinx.android.synthetic.main.bottom_sheet_display_reactions.*
|
import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,14 +48,16 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
@BindView(R.id.bottom_sheet_display_reactions_list)
|
@BindView(R.id.bottom_sheet_display_reactions_list)
|
||||||
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
||||||
|
|
||||||
private val epoxyController by lazy { ViewReactionsEpoxyController(emojiCompatFontProvider.typeface) }
|
private val epoxyController by lazy {
|
||||||
|
ViewReactionsEpoxyController(requireContext(), emojiCompatFontProvider.typeface)
|
||||||
|
}
|
||||||
|
|
||||||
override fun injectWith(screenComponent: ScreenComponent) {
|
override fun injectWith(screenComponent: ScreenComponent) {
|
||||||
screenComponent.inject(this)
|
screenComponent.inject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
val view = inflater.inflate(R.layout.bottom_sheet_display_reactions, container, false)
|
val view = inflater.inflate(R.layout.bottom_sheet_epoxylist_with_title, container, false)
|
||||||
ButterKnife.bind(this, view)
|
ButterKnife.bind(this, view)
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@ -67,16 +68,11 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||||
val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
|
val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
|
||||||
LinearLayout.VERTICAL)
|
LinearLayout.VERTICAL)
|
||||||
epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
|
epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
|
||||||
|
bottomSheetTitle.text = context?.getString(R.string.reactions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) {
|
override fun invalidate() = withState(viewModel) {
|
||||||
if (it.mapReactionKeyToMemberList() == null) {
|
|
||||||
bottomSheetViewReactionSpinner.isVisible = true
|
|
||||||
bottomSheetViewReactionSpinner.animate()
|
|
||||||
} else {
|
|
||||||
bottomSheetViewReactionSpinner.isVisible = false
|
|
||||||
}
|
|
||||||
epoxyController.setData(it)
|
epoxyController.setData(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,24 +16,47 @@
|
||||||
|
|
||||||
package im.vector.riotx.features.home.room.detail.timeline.action
|
package im.vector.riotx.features.home.room.detail.timeline.action
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Incomplete
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.ui.list.genericFooterItem
|
||||||
|
import im.vector.riotx.core.ui.list.genericLoaderItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Epoxy controller for reaction event list
|
* Epoxy controller for reaction event list
|
||||||
*/
|
*/
|
||||||
class ViewReactionsEpoxyController(private val emojiCompatTypeface: Typeface?) : TypedEpoxyController<DisplayReactionsViewState>() {
|
class ViewReactionsEpoxyController(private val context: Context, private val emojiCompatTypeface: Typeface?)
|
||||||
|
: TypedEpoxyController<DisplayReactionsViewState>() {
|
||||||
|
|
||||||
override fun buildModels(state: DisplayReactionsViewState) {
|
override fun buildModels(state: DisplayReactionsViewState) {
|
||||||
val map = state.mapReactionKeyToMemberList() ?: return
|
when (state.mapReactionKeyToMemberList) {
|
||||||
map.forEach {
|
is Incomplete -> {
|
||||||
reactionInfoSimpleItem {
|
genericLoaderItem {
|
||||||
id(it.eventId)
|
id("Spinner")
|
||||||
emojiTypeFace(emojiCompatTypeface)
|
}
|
||||||
timeStamp(it.timestamp)
|
}
|
||||||
reactionKey(it.reactionKey)
|
is Fail -> {
|
||||||
authorDisplayName(it.authorName ?: it.authorId)
|
genericFooterItem {
|
||||||
|
id("failure")
|
||||||
|
text(context.getString(R.string.unknown_error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
state.mapReactionKeyToMemberList()?.forEach {
|
||||||
|
reactionInfoSimpleItem {
|
||||||
|
id(it.eventId)
|
||||||
|
emojiTypeFace(emojiCompatTypeface)
|
||||||
|
timeStamp(it.timestamp)
|
||||||
|
reactionKey(it.reactionKey)
|
||||||
|
authorDisplayName(it.authorName ?: it.authorId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,23 +7,14 @@
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/bottomSheetTitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:text="@string/reactions"
|
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp"
|
||||||
|
tools:text="@string/reactions" />
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/bottomSheetViewReactionSpinner"
|
|
||||||
style="?android:attr/progressBarStyleSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
|
|
||||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
android:id="@+id/bottom_sheet_display_reactions_list"
|
android:id="@+id/bottom_sheet_display_reactions_list"
|
14
vector/src/main/res/layout/item_generic_header.xml
Normal file
14
vector/src/main/res/layout/item_generic_header.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/itemGenericHeaderText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:textColor="?vctr_notice_text_color"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Today" />
|
6
vector/src/main/res/layout/item_generic_loader.xml
Normal file
6
vector/src/main/res/layout/item_generic_loader.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/genericProgressSpinner"
|
||||||
|
style="?android:attr/progressBarStyleSmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp" />
|
|
@ -21,4 +21,8 @@
|
||||||
<string name="riotx_no_registration_notice_colored_part">Use the old app</string>
|
<string name="riotx_no_registration_notice_colored_part">Use the old app</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<string name="message_edits">Message Edits</string>
|
||||||
|
<string name="no_message_edits_found">No edits found</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Add table
Reference in a new issue